import React = require("react");
import { FieldViewProps, FieldView } from './FieldView';
import { observer } from "mobx-react";
import "./AudioBox.scss";
import { Cast, DateCast, NumCast } from "../../../new_fields/Types";
import { AudioField, nullAudio } from "../../../new_fields/URLField";
import { DocExtendableComponent } from "../DocComponent";
import { makeInterface, createSchema } from "../../../new_fields/Schema";
import { documentSchema } from "../../../new_fields/documentSchemas";
import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils";
import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
import { DateField } from "../../../new_fields/DateField";
import { SelectionManager } from "../../util/SelectionManager";
import { Doc, DocListCast } from "../../../new_fields/Doc";
import { ContextMenuProps } from "../ContextMenuItem";
import { ContextMenu } from "../ContextMenu";
import { Id } from "../../../new_fields/FieldSymbols";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DocumentView } from "./DocumentView";

interface Window {
    MediaRecorder: MediaRecorder;
}

declare class MediaRecorder {
    // whatever MediaRecorder has
    constructor(e: any);
}
export const audioSchema = createSchema({
    playOnSelect: "boolean"
});

type AudioDocument = makeInterface<[typeof documentSchema, typeof audioSchema]>;
const AudioDocument = makeInterface(documentSchema, audioSchema);

@observer
export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
    public static Enabled = false;

    _linkPlayDisposer: IReactionDisposer | undefined;
    _reactionDisposer: IReactionDisposer | undefined;
    _scrubbingDisposer: IReactionDisposer | undefined;
    _ele: HTMLAudioElement | null = null;
    _recorder: any;
    _recordStart = 0;

    @observable private static _scrubTime = 0;
    @observable private _audioState: "unrecorded" | "recording" | "recorded" = "unrecorded";
    @observable private _playing = false;
    public static SetScrubTime = action((timeInMillisFrom1970: number) => AudioBox._scrubTime = timeInMillisFrom1970);
    public static ActiveRecordings: Doc[] = [];

    componentDidMount() {
        runInAction(() => this._audioState = this.path ? "recorded" : "unrecorded");
        this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID,
            scrollLinkId => {
                scrollLinkId && DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => {
                    let la1 = l.anchor1 as Doc;
                    let linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode);
                    setTimeout(() => { this.playFrom(linkTime); Doc.linkFollowHighlight(l); }, 250);
                });
                scrollLinkId && Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
            }, { fireImmediately: true });
        this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(),
            selected => {
                let sel = selected.length ? selected[0].props.Document : undefined;
                this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime());
            });
        this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, timeInMillisecondsFrom1970 => {
            let start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart);
            start && this.playFrom((timeInMillisecondsFrom1970 - start.date.getTime()) / 1000);
        });
    }

    timecodeChanged = () => {
        const htmlEle = this._ele;
        if (this._audioState === "recorded" && htmlEle) {
            htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration);
            DocListCast(this.dataDoc.links).map(l => {
                let la1 = l.anchor1 as Doc;
                let linkTime = NumCast(l.anchor2Timecode);
                if (Doc.AreProtosEqual(la1, this.dataDoc)) {
                    la1 = l.anchor2 as Doc;
                    linkTime = NumCast(l.anchor1Timecode);
                }
                if (linkTime > NumCast(this.Document.currentTimecode) && linkTime < htmlEle.currentTime) {
                    Doc.linkFollowHighlight(la1);
                }
            });
            this.Document.currentTimecode = htmlEle.currentTime;
        }
    }

    pause = action(() => {
        this._ele!.pause();
        this._playing = false;
    });

    playFrom = (seekTimeInSeconds: number) => {
        if (this._ele && AudioBox.Enabled) {
            if (seekTimeInSeconds < 0) {
                this.pause();
            } else if (seekTimeInSeconds <= this._ele.duration) {
                this._ele.currentTime = seekTimeInSeconds;
                this._ele.play();
                runInAction(() => this._playing = true);
            } else {
                this.pause();
            }
        }
    }

    componentWillUnmount() {
        this._reactionDisposer && this._reactionDisposer();
        this._linkPlayDisposer && this._linkPlayDisposer();
        this._scrubbingDisposer && this._scrubbingDisposer();
    }


    updateRecordTime = () => {
        if (this._audioState === "recording") {
            setTimeout(this.updateRecordTime, 30);
            this.Document.currentTimecode = (new Date().getTime() - this._recordStart) / 1000;
        }
    }

    recordAudioAnnotation = () => {
        let gumStream: any;
        let self = this;
        const extensionDoc = this.extensionDoc;
        extensionDoc && navigator.mediaDevices.getUserMedia({
            audio: true
        }).then(function (stream) {
            gumStream = stream;
            self._recorder = new MediaRecorder(stream);
            extensionDoc.recordingStart = new DateField(new Date());
            AudioBox.ActiveRecordings.push(self.props.Document);
            self._recorder.ondataavailable = async function (e: any) {
                const formData = new FormData();
                formData.append("file", e.data);
                const res = await fetch(Utils.prepend("/upload"), {
                    method: 'POST',
                    body: formData
                });
                const files = await res.json();
                const url = Utils.prepend(files[0].path);
                // upload to server with known URL 
                self.props.Document[self.props.fieldKey] = new AudioField(url);
            };
            runInAction(() => self._audioState = "recording");
            self._recordStart = new Date().getTime();
            setTimeout(self.updateRecordTime, 0);
            self._recorder.start();
            setTimeout(() => {
                self.stopRecording();
                gumStream.getAudioTracks()[0].stop();
            }, 60 * 60 * 1000); // stop after an hour?
        });
    }

    specificContextMenu = (e: React.MouseEvent): void => {
        let funcs: ContextMenuProps[] = [];
        funcs.push({ description: (this.Document.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.Document.playOnSelect = !this.Document.playOnSelect, icon: "expand-arrows-alt" });

        ContextMenu.Instance.addItem({ description: "Audio Funcs...", subitems: funcs, icon: "asterisk" });
    }

    stopRecording = action(() => {
        this._recorder.stop();
        this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000;
        this._audioState = "recorded";
        let ind = AudioBox.ActiveRecordings.indexOf(this.props.Document);
        ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1));
    });

    recordClick = (e: React.MouseEvent) => {
        if (e.button === 0 && !e.ctrlKey) {
            this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
            e.stopPropagation();
        }
    }

    onPlay = (e: any) => {
        this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1);
        e.stopPropagation();
    }
    onStop = (e: any) => {
        this.pause();
        this._ele!.currentTime = 0;
        e.stopPropagation();
    }

    setRef = (e: HTMLAudioElement | null) => {
        e && e.addEventListener("timeupdate", this.timecodeChanged);
        e && e.addEventListener("ended", this.pause);
        this._ele = e;
    }

    @computed get path() {
        let field = Cast(this.props.Document[this.props.fieldKey], AudioField);
        let path = (field instanceof AudioField) ? field.url.href : "";
        return path === nullAudio ? "" : path;
    }

    @computed get audio() {
        let interactive = this.active() ? "-interactive" : "";
        return <audio ref={this.setRef} className={`audiobox-control${interactive}`}>
            <source src={this.path} type="audio/mpeg" />
            Not supported.
        </audio>;
    }

    render() {
        let interactive = this.active() ? "-interactive" : "";
        return (!this.extensionDoc ? (null) :
            <div className={`audiobox-container`} onContextMenu={this.specificContextMenu}
                onClick={!this.path ? this.recordClick : undefined}>
                <div className="audiobox-handle"></div>
                {!this.path ?
                    <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this._audioState === "recording" ? "red" : "black" }}>
                        {this._audioState === "recording" ? "STOP" : "RECORD"}
                    </button> :
                    <div className="audiobox-controls">
                        <div className="audiobox-player" onClick={this.onPlay}>
                            <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
                            <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
                            <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
                                onPointerDown={e => {
                                    if (e.button === 0 && !e.ctrlKey) {
                                        let rect = (e.target as any).getBoundingClientRect();
                                        this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
                                        this.pause();
                                        e.stopPropagation();
                                    }
                                }} >
                                {DocListCast(this.dataDoc.links).map((l, i) => {
                                    let la1 = l.anchor1 as Doc;
                                    let la2 = l.anchor2 as Doc;
                                    let linkTime = NumCast(l.anchor2Timecode);
                                    if (Doc.AreProtosEqual(la1, this.dataDoc)) {
                                        la1 = l.anchor2 as Doc;
                                        la2 = l.anchor1 as Doc;
                                        linkTime = NumCast(l.anchor1Timecode);
                                    }
                                    return !linkTime ? (null) :
                                        <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
                                            <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
                                                <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
                                                    parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
                                                    backgroundColor={returnTransparent} />
                                            </div>
                                            <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
                                                onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }}
                                                onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} />
                                        </div>;
                                })}
                                <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
                                {this.audio}
                            </div>
                        </div>
                    </div>
                }
            </div>
        );
    }
}