import * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { IconContext } from 'react-icons';
import { FaCheckCircle } from 'react-icons/fa';
import { MdBackspace } from 'react-icons/md';
import { Upload } from '../../../../server/SharedMediaTypes';
import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
import { Networking } from '../../../Network';
import { Presentation, TrackMovements } from '../../../util/TrackMovements';
import { ProgressBar } from './ProgressBar';
import './RecordingView.scss';

export interface MediaSegment {
    videoChunks: any[];
    endTime: number;
    startTime: number;
    presentation?: Presentation;
}

interface IRecordingViewProps {
    setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void;
    setDuration: (seconds: number) => void;
    id: string;
    getControls: (record: () => void, pause: () => void, finish: () => void) => void;
    forceTrackScreen: boolean;
}

const MAXTIME = 100000;

export function RecordingView(props: IRecordingViewProps) {
    const [recording, setRecording] = useState(false);
    const recordingTimerRef = useRef<number>(0);
    const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second
    const [playing, setPlaying] = useState(false);
    const [progress, setProgress] = useState(0);

    // acts as a "refresh state" to tell progressBar when to undo
    const [doUndo, setDoUndo] = useState(false);
    // whether an undo can occur or not
    const [canUndo, setCanUndo] = useState(false);

    const [videos, setVideos] = useState<MediaSegment[]>([]);
    const [orderVideos, setOrderVideos] = useState<boolean>(false);
    const videoRecorder = useRef<MediaRecorder | null>(null);
    const videoElementRef = useRef<HTMLVideoElement | null>(null);

    const [finished, setFinished] = useState<boolean>(false);
    const [trackScreen, setTrackScreen] = useState<boolean>(false);

    const DEFAULT_MEDIA_CONSTRAINTS = {
        video: {
            width: 1280,
            height: 720,
        },
        audio: {
            echoCancellation: true,
            noiseSuppression: true,
            sampleRate: 44100,
        },
    };

    useEffect(() => {
        if (finished) {
            // make the total presentation that'll match the concatted video
            let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));

            // this async function uses the server to create the concatted video and then sets the result to it's accessPaths
            (async () => {
                const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() }));

                // upload the segments to the server and get their server access paths
                const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles.map(file => ({ file })))).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server));

                // concat the segments together using post call
                const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths);
                !(result instanceof Error) ? props.setResult(result, concatPres || undefined) : console.error('video conversion failed');
            })();
        }
    }, [videos]);

    // this will call upon the progress bar to edit videos to be in the correct order
    useEffect(() => {
        finished && setOrderVideos(true);
    }, [finished]);

    // check if the browser supports media devices on first load
    useEffect(() => {
        if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.');
    }, []);

    useEffect(() => {
        let interval: any = null;
        if (recording) {
            interval = setInterval(() => {
                setRecordingTimer(unit => unit + 1);
            }, 10);
        } else if (!recording && recordingTimer !== 0) {
            clearInterval(interval);
        }
        return () => clearInterval(interval);
    }, [recording]);

    useEffect(() => {
        setVideoProgressHelper(recordingTimer);
        recordingTimerRef.current = recordingTimer;
    }, [recordingTimer]);

    const setVideoProgressHelper = (progress: number) => {
        const newProgress = (progress / MAXTIME) * 100;
        setProgress(newProgress);
    };

    const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => {
        const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);

        videoElementRef.current!.src = '';
        videoElementRef.current!.srcObject = stream;
        videoElementRef.current!.muted = true;

        return stream;
    };

    const record = async () => {
        // don't need to start a new stream every time we start recording a new segment
        if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream());

        // temporary chunks of video
        let videoChunks: any = [];

        videoRecorder.current.ondataavailable = (event: any) => {
            if (event.data.size > 0) videoChunks.push(event.data);
        };

        videoRecorder.current.onstart = (event: any) => {
            setRecording(true);
            // start the recording api when the video recorder starts
            (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start();
        };

        videoRecorder.current.onstop = () => {
            // if we have a last portion
            if (videoChunks.length > 1) {
                // append the current portion to the video pieces
                const nextVideo = {
                    videoChunks,
                    endTime: recordingTimerRef.current,
                    startTime: videos?.lastElement()?.endTime || 0,
                };

                // depending on if a presenation exists, add it to the video
                const presentation = TrackMovements.Instance.yieldPresentation();
                setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
            }

            // reset the temporary chunks
            videoChunks = [];
            setRecording(false);
        };

        videoRecorder.current.start(200);
    };

    // if this is called, then we're done recording all the segments
    const finish = () => {
        // call stop on the video recorder if active
        videoRecorder.current?.state !== 'inactive' && videoRecorder.current?.stop();
        // end the streams (audio/video) to remove recording icon
        const stream = videoElementRef.current!.srcObject;
        stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop());

        // finish/clear the recoringApi
        TrackMovements.Instance.finish();

        // this will call upon progessbar to update videos to be in the correct order
        setFinished(true);
    };

    const pause = () => {
        // if recording, then this is just a new segment
        videoRecorder.current?.state === 'recording' && videoRecorder.current.stop();
    };

    const start = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            {},
            e,
            returnTrue,
            returnFalse,
            e => {
                // start recording if not already recording
                if (!videoRecorder.current || videoRecorder.current.state === 'inactive') record();

                return true; // cancels propagation to documentView to avoid selecting it.
            },
            false,
            false
        );
    };

    const undoPrevious = (e: React.PointerEvent) => {
        e.stopPropagation();
        setDoUndo(prev => !prev);
    };

    const handleOnTimeUpdate = () => {
        playing && setVideoProgressHelper(videoElementRef.current!.currentTime);
    };

    const millisecondToMinuteSecond = (milliseconds: number) => {
        const toTwoDigit = (digit: number) => {
            return String(digit).length == 1 ? '0' + digit : digit;
        };
        const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
        const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
        return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds);
    };

    useEffect(() => {
        props.getControls(record, pause, finish);
    }, []);

    return (
        <div className="recording-container">
            <div className="video-wrapper">
                <video id={`video-${props.id}`} autoPlay muted onTimeUpdate={() => handleOnTimeUpdate()} ref={videoElementRef} />
                <div className="recording-sign">
                    <span className="dot" />
                    <p className="timer">{millisecondToMinuteSecond(recordingTimer * 10)}</p>
                </div>
                <div className="controls">
                    <div className="controls-inner-container">
                        <div className="record-button-wrapper">
                            {recording ? (
                                <button
                                    className="stop-button"
                                    onPointerDown={e => {
                                        e.stopPropagation();
                                        pause();
                                    }}
                                />
                            ) : (
                                <button className="record-button" onPointerDown={start} />
                            )}
                        </div>

                        {!recording &&
                            (videos.length > 0 ? (
                                <div className="options-wrapper video-edit-wrapper">
                                    <IconContext.Provider value={{ color: 'grey', className: 'video-edit-buttons', style: { display: canUndo ? 'inherit' : 'none' } }}>
                                        <MdBackspace onPointerDown={undoPrevious} />
                                    </IconContext.Provider>
                                    <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}>
                                        <FaCheckCircle
                                            onPointerDown={e => {
                                                e.stopPropagation();
                                                finish();
                                            }}
                                        />
                                    </IconContext.Provider>
                                </div>
                            ) : (
                                <div className="options-wrapper track-screen-wrapper">
                                    <label className="track-screen">
                                        <input
                                            type="checkbox"
                                            checked={trackScreen || props.forceTrackScreen}
                                            onChange={e => {
                                                setTrackScreen(e.target.checked);
                                            }}
                                        />
                                        <span className="checkmark"></span>
                                        Track Screen
                                    </label>
                                </div>
                            ))}
                    </div>
                </div>

                <ProgressBar videos={videos} setVideos={setVideos} orderVideos={orderVideos} progress={progress} recording={recording} doUndo={doUndo} setCanUndo={setCanUndo} />
            </div>
        </div>
    );
}
