import { fabric } from 'fabric';
import { SimpleView } from './simpleView';
import { SideToSideView } from './sideToSideView';
import { lockImage } from './helpers';
import { errorMsg, successMsg } from '../../../../../../../shared/utils/utils';
import config from '../../../../../../../config';
import { log } from '../../../../../../../shared/utils/Logger';

/* eslint-disable dot-notation,no-underscore-dangle */
interface IRectParams {
    name: any;
    fill: any;
    stroke: any;
    strokeWidth: any;
    top: any;
    left: any;
    width: any;
    height: any;
}

interface Props {
    canvasElementWidth: number;
    canvasElementHeight: number;
    canvasId: string;
    // url: string
    actual: any;
    expectedImage: any;
    actualImage: any;
    diffImage: any;
}

export class MainView {
    canvasElementWidth: number;

    canvasElementHeight: number;

    sliderView: SideToSideView;

    canvas: fabric.Canvas;

    actualImage: any;

    currentMode: any;

    defaultMode: string;

    // currentView: string;

    actualView: SimpleView;

    expectedView: SimpleView;

    diffView: SimpleView;

    expectedImage: any;

    diffImage: any;

    // _currentView: string;
    //
    // public get currentView() {
    //     return this._currentView;
    // }
    //
    // public set currentView(value: string) {
    //     this._currentView = value;
    // }

    constructor(
        {
            canvasElementWidth,
            canvasElementHeight,
            canvasId,
            actual,
            expectedImage,
            actualImage,
            diffImage,
        }: Props,
    ) {
        fabric.Object.prototype.objectCaching = false;
        // init properties
        this.canvasElementWidth = canvasElementWidth;
        this.canvasElementHeight = canvasElementHeight;

        this.actualImage = lockImage(actualImage);
        this.expectedImage = lockImage(expectedImage);
        this.diffImage = diffImage ? lockImage(diffImage) : null;

        this.canvas = new fabric.Canvas(canvasId, {
            width: this.canvasElementWidth,
            height: this.canvasElementHeight,
            preserveObjectStacking: true,
            uniformScaling: false,
        });

        // this._currentView = 'actual';
        // this.expectedCanvasViewportAreaSize = MainView.calculateExpectedCanvasViewportAreaSize();

        this.defaultMode = '';

        if (actual) {
            this.sliderView = new SideToSideView(
                {
                    mainView: this,
                },
            );
        }

        // events
        this.selectionEvents();
        this.zoomEvents();
        this.panEvents();

        // views
        this.expectedView = new SimpleView(this, 'expected');
        this.actualView = new SimpleView(this, 'actual');
        this.diffView = new SimpleView(this, 'diff');
        this.actualView.render();
        // this.sideToSideView.render()
    }

    /*
     this is the area from the left top canvas corner till the end of the viewport
     ┌──────┬─────────────┐
     │      │xxxxxxxx     │
     │      │xxxxxxxx     │
     │      │xxxxxxxx     │
     │      │xxxxxxxx     │
     │      │             │
     │      │             │
     │      │  the area   │
     │      │             │
     │      │             │
     └──────┴─────────────┘
    */

    static calculateExpectedCanvasViewportAreaSize() {
        const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
        const viewportHeight = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
        const canvasDimensions = document.getElementById('snapshoot')!
            .getBoundingClientRect();
        return {
            width: Number(viewportWidth - canvasDimensions.x),
            height: Number(viewportHeight - canvasDimensions.y),
        };
    }

    zoomEvents() {
        this.canvas.on('mouse:wheel', (opt: any) => {
            if (!opt.e.ctrlKey) return;
            const delta = opt.e.deltaY;
            let zoomVal = this.canvas.getZoom();

            zoomVal *= 0.999 ** delta;
            if (zoomVal > 9) zoomVal = 9;
            if (zoomVal < 0.01) zoomVal = 0.01;
            this.canvas.zoomToPoint({
                x: opt.e.offsetX,
                y: opt.e.offsetY,
            }, zoomVal);

            // setZoomPercent(() => zoomVal * 100);
            document.dispatchEvent(new Event('zoom'));
            opt.e.preventDefault();
            opt.e.stopPropagation();
        });
    }

    panEvents() {
        this.canvas.on(
            'mouse:move', (e) => {
                // log.debug(e.e.buttons);
                if ((e.e.buttons === 4)) {
                    this.canvas.setCursor('grab');

                    const mEvent = e.e;
                    const delta = new fabric.Point(mEvent.movementX, mEvent.movementY);
                    this.canvas.relativePan(delta);
                    this.canvas.renderAll();
                }
            },
        );

        this.canvas.on('mouse:wheel', (opt) => {
            if (opt.e.ctrlKey) return;
            const delta = new fabric.Point(-opt.e.deltaX / 2, -opt.e.deltaY / 2);
            this.canvas.relativePan(delta);
            // this.canvas.dispatchEvent(new Event('pan'));
            this.canvas.fire('pan', opt);
            this.canvas.renderAll();
            opt.e.preventDefault();
            opt.e.stopPropagation();
        });
    }

    selectionEvents() {
        // disable rotation point for selections
        this.canvas.on('selection:created', (e) => {
            const activeSelection: any = e.target;
            if (!activeSelection?._objects?.length || (activeSelection?._objects?.length < 2)) return;
            activeSelection.hasControls = false;
            this.canvas.renderAll();
        });

        // fired e.g. when you select one object first,
        // then add another via shift+click
        this.canvas.on('selection:updated', (e) => {
            const activeSelection: any = e.target;
            if (!activeSelection?._objects?.length || (activeSelection?._objects?.length < 2)) return;
            if (activeSelection.hasControls) {
                activeSelection.hasControls = false;
            }
        });
    }

    // get objects() {
    //     return this.canvas.getObjects();
    // }

    async destroyAllViews() {
        this.expectedView.destroy();
        this.actualView.destroy();
        this.diffView.destroy();
        await this.sliderView.destroy();
    }

    async switchView(view: string) {
        // this.currentView = view;
        await this.destroyAllViews();
        this.sliderView = new SideToSideView(
            {
                mainView: this,
            },
        );
        this[`${view}View`].render();
    }

    panToCanvasWidthCenter(imageName: string) {
        // if (this.pannedOnInit) return;
        // this.pannedOnInit = true;

        this.canvas.absolutePan(new fabric.Point(0, 0));
        const delta = new fabric.Point(
            ((this.canvas.width / 2)
                - ((this[imageName].width * this.canvas.getZoom()) / 2)
            ),
            // ((this.canvas.width / 2) - (this[imageName].getScaledWidth() / 2)),
            0,
        );
        this.canvas.relativePan(delta);
        // this.canvas.renderAll(); console.log('render!!!');
    }

    removeActiveIgnoreRegions() {
        const els = this.canvas.getActiveObjects()
            .filter((x) => x.name === 'ignore_rect');
        this.canvas.discardActiveObject()
            .renderAll();
        if (els.length === 0) {
            // eslint-disable-next-line no-undef,no-alert
            alert('there is no active regions for removing');
            return;
        }
        els.forEach((el) => {
            this.canvas.remove(el);
        });
        this.canvas.renderAll();
    }

    addRect(params: IRectParams) {
        // eslint-disable-next-line no-param-reassign
        params.name = params.name ? params.name : 'default_rect';
        let lastLeft = null;
        let lastTop = null;
        let width = null;
        let height = null;
        if ((this.getLastRegion() !== undefined) && (this.getLastRegion().name === 'ignore_rect')) {
            lastLeft = this.getLastRegion().left || 50;
            lastTop = this.getLastRegion().top;
            width = this.getLastRegion()
                .getScaledWidth();
            height = this.getLastRegion()
                .getScaledHeight();
        }
        // if last elements fit in current viewport create new region near this region
        const top = (lastTop > document.documentElement.scrollTop
            && lastTop < document.documentElement.scrollTop + window.innerHeight)
            ? lastTop + 20
            : document.documentElement.scrollTop + 50;
        const left = (lastLeft < (this.canvas.width - 80)) ? lastLeft + 20 : lastLeft - 50;
        return new fabric.Rect({
            left: params.left || left,
            top: params.top || top,
            fill: params.fill || 'blue',
            width: params.width || width || 200,
            height: params.height || height || 100,
            strokeWidth: params.strokeWidth || 2,
            // stroke: params.stroke || 'rgba(100,200,200,0.5)',
            stroke: params.stroke || 'black',
            opacity: 0.5,
            name: params.name,
            // uniformScaling: true,
            strokeUniform: true,
            noScaleCache: false,
            cornerSize: 9,
            transparentCorners: false,
            cornerColor: 'rgb(26, 115, 232)',
            cornerStrokeColor: 'rgb(255, 255, 255)',
        });
    }

    addIgnoreRegion(params) {
        Object.assign(params, { fill: 'MediumVioletRed' });
        const r = this.addRect(params);
        r.setControlsVisibility({
            bl: true,
            br: true,
            tl: true,
            tr: true,
            mt: true,
            mb: true,
            mtr: false,
        });

        this.canvas.add(r);
        r.bringToFront();
        // become selected
        if (params.noSelect) {
            return;
        }
        this.canvas.setActiveObject(r);
    }

    addBoundingRegion(name) {
        const params = {
            name,
            fill: 'rgba(0,0,0,0)',
            stroke: 'green',
            strokeWidth: 3,
            top: 1,
            left: 1,
            width: this.expectedImage.getScaledWidth() - 10,
            height: this.expectedImage.getScaledHeight() - 10,
        };
        const r = this.addRect(params);
        this.canvas.add(r);
        r.bringToFront();
    }

    removeAllRegions() {
        const regions = this.allRects;
        regions.forEach((region) => {
            this.canvas.remove(region);
        });
    }

    get allRects() {
        return this.canvas.getObjects()
            .filter((r) => (r.name === 'ignore_rect') || (r.name === 'bound_rect'));
    }

    getLastRegion() {
        return this.canvas.item(this.canvas.getObjects().length - 1);
    }

    /**
     * 1. collect data about all rects
     * 2. convert the data to resemble.js format
     * 3. return json string
     */
    getRectData() {
        const rects = this.allRects;
        const data = [];
        const coef = parseFloat(this.coef);

        rects.forEach((reg) => {
            const right = reg.left + reg.getScaledWidth();
            const bottom = reg.top + reg.getScaledHeight();
            if (coef) {
                data.push({
                    name: reg.name,
                    top: reg.top * coef,
                    left: reg.left * coef,
                    bottom: bottom * coef,
                    right: right * coef,
                });
            }
        });
        return JSON.stringify(data);
    }

    get coef() {
        return this.expectedImage.height / this.expectedImage.getScaledHeight();
    }

    static async sendIgnoreRegions(id: string, regionsData) {
        try {
            const response = await fetch(`${config.baseUri}/v1/baselines/${id}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ id, ignoreRegions: regionsData }),
            });
            const text = await response.text();
            if (response.status === 200) {
                log.debug(`Successful send baseline ignored regions, id: '${id}'  resp: '${text}'`);
                successMsg({ message: 'ignored regions was saved' });
                // MainView.showToaster('ignored regions was saved');
                return;
            }
            log.error(`Cannot set baseline ignored regions , status: '${response.status}',  resp: '${text}'`);
            errorMsg({ error: 'Cannot set baseline ignored regions' });
            // MainView.showToaster('Cannot set baseline ignored regions', 'Error');
        } catch (e) {
            log.error(`Cannot set baseline ignored regions: ${e.stack  || e.toString()}`);
            errorMsg({ error: 'Cannot set baseline ignored regions' });
            // MainView.showToaster('Cannot set baseline ignored regions', 'Error');
        }
    }

    /**
     * convert json to fabric.js format
     * @param {string} regions       JSON string that contain data about regions in resemble.js format
     * @returns {object}             region data in fabric.js format
     */
    convertRegionsDataFromServer(regions) {
        const data = [];
        const coef = parseFloat(this.coef);
        regions
            .forEach((reg) => {
                const width = reg.right - reg.left;
                const height = reg.bottom - reg.top;
                if (coef) {
                    data.push({
                        name: reg.name,
                        top: reg.top / coef,
                        left: reg.left / coef,
                        width: width / coef,
                        height: height / coef,
                    });
                }
            });
        return data;
    }

    drawRegions(data) {
        // log.debug({ data });
        if (!data || data === 'undefined') {
            return;
            // log.error('The regions data is empty')
        }
        const regs = this.convertRegionsDataFromServer(JSON.parse(data));
        // log.debug('converted:', regs.length, regs);
        const classThis = this;
        regs.forEach((regParams) => {
            // eslint-disable-next-line no-param-reassign
            regParams['noSelect'] = true;
            classThis.addIgnoreRegion(regParams);
        });
    }

    static async getRegionsData(baselineId: string) {
        try {
            if (!baselineId) {
                // log.debug('Cannot get regions, baseline id is empty');
                return [];
            }
            const url = `${config.baseUri}/v1/baselines?filter={"_id":"${baselineId}"}`;
            // log.debug({ url });
            const response = await fetch(url);
            const text = await response.text();
            if (response.status === 200) {
                log.debug(`Successfully got ignored regions, id: '${baselineId}'  resp: '${text}'`);
                return JSON.parse(text).results[0];
            }
            if (response.status === 202) {
                log.debug('No regions');
                return [];
            }
            log.error(`Cannot get baseline ignored regions , status: '${response.status}',  resp: '${text}'`);
            // MainView.showToaster('Cannot get baseline ignored regions', 'Error');
            errorMsg({ error: 'Cannot get baseline ignored regions' });
        } catch (e) {
            log.error(`Cannot get baseline ignored regions: ${e.stack  || e.toString()}`);
            // MainView.showToaster('Cannot get baseline ignored regions', 'Error');
            errorMsg({ error: 'Cannot get baseline ignored regions' });
        }
        return null;
    }

    async getSnapshotIgnoreRegionsDataAndDrawRegions(id: string) {
        this.removeAllRegions();
        const regionData = await MainView.getRegionsData(id);
        this.drawRegions(regionData.ignoreRegions);
    }
}
