import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm";
import React, { useState } from "react";
import { IReactionDisposer, observable, reaction } from "mobx";
import { NumCast } from "../../../fields/Types";

type Movement = {
    time: number,
    panX: number,
    panY: number,
}

type Presentation = {
    movements: Array<Movement>
    meta: Object,
    startDate: Date | null,
}

export class RecordingApi {

    private static NULL_PRESENTATION: Presentation = {
        movements: [],
        meta: {},
        startDate: null,
    }

    // instance variables
    private currentPresentation: Presentation;
    private isRecording: boolean;
    private absoluteStart: number;


    // create static instance and getter for global use
    @observable static _instance: RecordingApi;
    public static get instance(): RecordingApi { return RecordingApi._instance }
    constructor() {
        // init the global instance
        RecordingApi._instance = this;

        // init the instance variables
        this.currentPresentation = RecordingApi.NULL_PRESENTATION
        this.isRecording = false;
        this.absoluteStart = -1;

        // used for tracking movements in the view frame
        this.disposeFunc = null;

        // for now, set playFFView
        this.playFFView = null;
    }

    // little helper :)
    private get isInitPresenation(): boolean {
        return this.currentPresentation.startDate === null
    }

    public start = (view: CollectionFreeFormView, meta?: Object): Error | undefined => {
        // check if already init a presentation
        if (!this.isInitPresenation) {
            console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.')
            return new Error('[recordingApi.ts] start()')
        }

        // (1a) get start date for presenation
        const startDate = new Date()
        // (1b) set start timestamp to absolute timestamp
        this.absoluteStart = startDate.getTime()

        // (2) assign meta content if it exists
        this.currentPresentation.meta = meta || {}
        // (3) assign start date to currentPresenation
        this.currentPresentation.startDate = startDate
        // (4) set isRecording true to allow trackMovements
        this.isRecording = true
    }

    public clear = (): Error | undefined => {
        // TODO: maybe archive the data?
        if (this.isRecording) {
            console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() or finish() first')
            return new Error('[recordingApi.ts] clear()')
        }

        // clear presenation data
        this.currentPresentation = RecordingApi.NULL_PRESENTATION
        // clear isRecording
        this.isRecording = false
        // clear absoluteStart
        this.absoluteStart = -1

        // clear the disposeFunc
        this.disposeFunc = null
    }

    public pause = (): Error | undefined => {
        if (this.currentPresentation.startDate === null) {
            console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first')
            return new Error('[recordingApi.ts] pause(): no presenation')
        }
        // don't allow track movments
        this.isRecording = false

        // set relativeStart to the pausedTimestamp
        const timestamp = new Date().getTime()
        this.absoluteStart = timestamp
    }

    public resume = () => {
        const timestamp = new Date().getTime()
        const startTimestamp = this.currentPresentation.startDate?.getTime()
        if (!startTimestamp) {
            console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first')
            return new Error('[recordingApi.ts] pause()')
        }

        // update absoluteStart to bridge the paused time
        const absoluteTimePaused = timestamp - this.absoluteStart
        this.absoluteStart = absoluteTimePaused
    }

    public finish = (): Error | Presentation => {
        if (this.isInitPresenation) {
            console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first')
            return new Error('[recordingApi.ts] finish()')
        }

        // return a copy of the the presentation data
        return { ...this.currentPresentation }
    }

    private trackMovements = (panX: number, panY: number): Error | undefined => {
        // ensure we are recording
        if (!this.isRecording) {
            console.error('[recordingApi.ts] pause() failed: recording is paused()')
            return new Error('[recordingApi.ts] pause()')
        }

        // get the relative time
        const timestamp = new Date().getTime()
        const relativeTime = timestamp - this.absoluteStart

        // make new movement struct
        const movement: Movement = { time: relativeTime, panX, panY }
        
        // add that movement to the current presentation data's movement array
        this.currentPresentation.movements.push(movement)
    }

    // instance variable for the FFView
    private disposeFunc: IReactionDisposer | null;
    
    // set the FFView that will be used in a reaction to track the movements
    public setRecordingFFView = (view: CollectionFreeFormView): void => {
        // set the view to the current view
        if (view === null) return;
        
        //this.recordingFFView = view;
        // set the reaction to track the movements
        this.disposeFunc = reaction(
            () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }),
            (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y)
        )

        // for now, set the most recent recordingFFView to the playFFView
        this.playFFView = view;
    }

    // call on dispose function to stop tracking movements
    public removeRecordingFFView = (): void => {
        this.disposeFunc?.();
        this.disposeFunc = null;
    }

    // TODO: extract this into different class with pause and resume recording
    private playFFView: CollectionFreeFormView | null;

    public followMovements = (presentation: Presentation): undefined | Error => {
        if (presentation.startDate === null || this.playFFView === null) {
            return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view')
        }

        const document = this.playFFView.Document
        const { movements } = presentation
        movements.forEach(movement => {
            const { panX, panY, time } = movement
            // set the pan to what was stored
            setTimeout(() => {
                document._panX = panX;
                document._panY = panY;
            }, time)
        })
    }

    // Unfinished code for tracing multiple free form views
    // export let pres: Map<CollectionFreeFormView, IReactionDisposer> = new Map()

    // export function AddRecordingFFView(ffView: CollectionFreeFormView): void {
    //     pres.set(ffView,
    //          reaction(() => ({ x: ffView.panX, y: ffView.panY }),
    //     (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y)))
    //     )
    // }

    // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void {
    //     const disposer = pres.get(ffView);
    //     disposer?.();
    //     pres.delete(ffView)
    // }
}