import {PeekCanvasConfig} from "../canvas/PeekCanvasConfig.web";
import {PeekCanvasModel} from "../canvas/PeekCanvasModel.web";

import * as $ from "jquery";
import {PeekDispRenderFactory} from "../canvas-render/PeekDispRenderFactory.web";
import {PeekCanvasInputDelegate} from "./PeekCanvasInputDelegate.web";
import {
    CanvasInputPos,
    disableContextMenu,
    InputDelegateConstructorEditArgs,
    InputDelegateConstructorViewArgs
} from "./PeekCanvasInputDelegateUtil.web";

import {ComponentLifecycleEventEmitter} from "@synerty/vortexjs";
import {PeekCanvasInputSelectDelegate} from "./PeekCanvasInputSelectDelegate.web";
import {EditorToolType} from "../canvas/PeekCanvasEditorToolType.web";
import {PointI} from "../canvas-shapes/DispBase";
import {DrawModeE} from "../canvas-render/PeekDispRenderDelegateABC.web";
import {DocDbPopupService} from "@peek/peek_plugin_docdb";


/** Peek Canvas Input
 *
 * This class manages the user input of the canvas
 *
 */
export class PeekCanvasInput {
    private _delegate: PeekCanvasInputDelegate = null;

    private canvas = null;

    // These offsets are calculated on the size and position of the canvas in the HTML
    // page. When added to the mouse event coordinates, they convert the mouse event
    // coordinates to be relative to the center of the canvas.
    private mouseOffsetX: number = 0;
    private mouseOffsetY: number = 0;

    constructor(private config: PeekCanvasConfig,
                private model: PeekCanvasModel,
                private renderFactory: PeekDispRenderFactory,
                private lifecycleEventEmitter: ComponentLifecycleEventEmitter,
                private objectPopupService: DocDbPopupService) {
        this.delegateFinished();
    }


    setDelegate(Delegate, editArgs: InputDelegateConstructorEditArgs | null = null) {
        if (this._delegate)
            this._delegate.shutdown();

        let viewDelegateArgs: InputDelegateConstructorViewArgs = {
            input: this,
            config: this.config,
            model: this.model,
            renderFactory: this.renderFactory,
            objectPopupService:this.objectPopupService
        };

        this._delegate = new Delegate(viewDelegateArgs, editArgs);

        this.config.mouse.currentDelegateName = Delegate.TOOL_NAME;

        // console.log(`Delegate = ${Delegate.TOOL_NAME}`);
    };


    delegateFinished() {

        // let PeekCanvasInputSelectDelegate =
        //     require("PeekCanvasInputSelectDelegate")["PeekCanvasInputSelectDelegate"];
        this.setDelegate(PeekCanvasInputSelectDelegate);
    };

    selectedDelegateType(): EditorToolType {
        return this.config.mouse.currentDelegateName;
    }

    selectedDelegate(): PeekCanvasInputDelegate {
        return this._delegate;
    }

// Creates an object with x and y defined, set to the mouse position relative to
// the state's canvas
// If you want to be super-correct this can be tricky, we have to worry about
// padding and borders
    _getMouse(e): CanvasInputPos {

        let pageX = e.pageX;
        let pageY = e.pageY;

        if (pageX == null) {
            if (e.changedTouches != null && e.changedTouches.length >= 0) {
                let touch = e.changedTouches[0];
                pageX = touch.pageX;
                pageY = touch.pageY;
            } else {
                console.log("ERROR: Failed to determine pan coordinates");
            }
        }

        let mx = pageX - this.mouseOffsetX;
        let my = pageY - this.mouseOffsetY;

        let clientX = mx;
        let clientY = my;

        // Apply canvas scale and pan
        let zoom = this.config.viewPort.zoom;
        let pan = this.config.viewPort.pan;
        mx = mx / zoom + pan.x;
        my = my / zoom + pan.y;

        if (isNaN(mx))
            console.log("mx IS NaN");


        this.config.mouse.currentViewPortPosition = {x: mx, y: my};
        this.config.mouse.currentCanvasPosition = {x: clientX, y: clientY};

        // We return a simple javascript object (a hash) with x and y defined
        return {
            x: mx,
            y: my,
            clientX: clientX,
            clientY: clientY,
            time: new Date()
        };
    };

    setCanvas(canvas) {


        this.canvas = canvas;

        canvas.addEventListener('keydown', (e) => {
            this._delegate.keyDown(e);

        }, true);

        canvas.addEventListener('keypress', (e) => {
            this._delegate.keyPress(e);

        }, true);

        canvas.addEventListener('keyup', (e) => {
            this._delegate.keyUp(e);

        }, true);

        canvas.addEventListener('mousedown', (e) => {
            if (!(e instanceof MouseEvent)) return;
            this._delegate.mouseDown(e, this._getMouse(e));

        }, true);

        canvas.addEventListener('mousemove', (e) => {
            if (!(e instanceof MouseEvent)) return;
            this._delegate.mouseMove(e, this._getMouse(e));

        }, true);

        canvas.addEventListener('mouseup', (e) => {
            if (!(e instanceof MouseEvent)) return;
            this._delegate.mouseUp(e, this._getMouse(e));

        }, true);

        canvas.addEventListener('dblclick', (e) => {
            if (!(e instanceof MouseEvent)) return;
            this._delegate.mouseDoubleClick(e, this._getMouse(e));

        }, true);

        canvas.addEventListener('mousewheel', (e) => {
            if (!(e instanceof MouseEvent)) return;
            this._delegate.mouseWheel(e, this._getMouse(e));

            e.preventDefault();
            return false;
        }, true);

        canvas.addEventListener('touchstart', (e) => {
            if (!(e instanceof TouchEvent)) return;
            this._delegate.touchStart(e, this._getMouse(e));
            disableContextMenu(e);

        }, true);

        canvas.addEventListener('touchmove', (e) => {
            if (!(e instanceof TouchEvent)) return;
            this._delegate.touchMove(e, this._getMouse(e));
            disableContextMenu(e);

        }, true);

        canvas.addEventListener('touchend', (e) => {
            if (!(e instanceof TouchEvent)) return;
            this._delegate.touchEnd(e, this._getMouse(e));
            disableContextMenu(e);

        }, true);

        canvas.addEventListener('selectstart', (e) => {
            //this_._delegate.mouseSelectStart(e, this_._getMouse(e));
            e.preventDefault();
            return false;
        }, true);

        canvas.addEventListener('contextmenu', disableContextMenu, true);

        this.config.canvas.windowChange
            .takeUntil(this.lifecycleEventEmitter.onDestroyEvent)
            .subscribe(() => this.updateCanvasSize());

    };

    private updateCanvasSize() {

        let jqCanvas = $(this.canvas);
        let element: any = this.canvas;

        let width = jqCanvas.width();
        let height = jqCanvas.height();

        // This complicates things a little but but fixes mouse co-ordinate
        // problems
        // when there's a border or padding. See getMouse for more detail
        let stylePaddingLeft = parseInt(jqCanvas.css('padding-left')) || 0;
        let stylePaddingTop = parseInt(jqCanvas.css('padding-top')) || 0;
        let styleBorderLeft = parseInt(jqCanvas.css('border-left-width')) || 0;
        let styleBorderTop = parseInt(jqCanvas.css('border-top-width')) || 0;

        // Some pages have fixed-position bars (like the stumbleupon bar) at the
        // top or left of the page
        // They will mess up mouse coordinates and this fixes that
        let html: any = document.body.parentNode;
        let htmlTop = html.offsetTop;
        let htmlLeft = html.offsetLeft;

        this.mouseOffsetX = 0;
        this.mouseOffsetY = 0;

        // Compute the total offset
        if (element.offsetParent != null) {
            do {
                this.mouseOffsetX += element.offsetLeft;
                this.mouseOffsetY += element.offsetTop;
            } while ((element = element.offsetParent));
        }

        // Add padding and border style widths to offset
        // Also add the <html> offsets in case there's a position:fixed bar
        this.mouseOffsetX += stylePaddingLeft + styleBorderLeft + htmlLeft + width / 2;
        this.mouseOffsetY += stylePaddingTop + styleBorderTop + htmlTop + height / 2;

    };

    /**
     * Draw Called by the renderer during a redraw.
     */
    draw(ctx, zoom: number, pan: PointI, drawMode: DrawModeE) {
        if (this._delegate)
            this._delegate.draw(ctx, zoom, pan, drawMode);
    };

}