/*
 * Copyright (C) 2016-2020  Yomichan Authors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/* global
 * Display
 * FrameEndpoint
 * api
 */

class DisplayFloat extends Display {
    constructor() {
        super(document.querySelector('#spinner'), document.querySelector('#definitions'));
        this._nestedPopupsPrepared = false;
        this._frameEndpoint = new FrameEndpoint();
        this._windowMessageHandlers = new Map([
            ['extensionUnloaded', {async: false, handler: this._onMessageExtensionUnloaded.bind(this)}]
        ]);

        this.registerActions([
            ['copyHostSelection', () => this._copySelection()]
        ]);
        this.registerHotkeys([
            {key: 'C', modifiers: ['ctrl'], action: 'copyHostSelection'}
        ]);

        this.autoPlayAudioDelay = 400;
    }

    async prepare() {
        await super.prepare();

        this.registerDirectMessageHandlers([
            ['configure',       {async: true,  handler: this._onMessageConfigure.bind(this)}],
            ['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}]
        ]);
        window.addEventListener('message', this._onWindowMessage.bind(this), false);
        document.documentElement.addEventListener('mouseup', this._onMouseUp.bind(this), false);
        document.documentElement.addEventListener('click', this._onClick.bind(this), false);
        document.documentElement.addEventListener('auxclick', this._onClick.bind(this), false);

        this.initializeState();

        this._frameEndpoint.signal();
    }

    onEscape() {
        this._invokeOwner('closePopup');
    }

    async setOptionsContext(optionsContext) {
        super.setOptionsContext(optionsContext);
        await this.updateOptions();
    }

    async getDocumentTitle() {
        try {
            const targetFrameId = 0;
            const {title} = await api.crossFrame.invoke(targetFrameId, 'getDocumentInformation');
            return title;
        } catch (e) {
            return '';
        }
    }

    authenticateMessageData(data) {
        if (!this._frameEndpoint.authenticate(data)) {
            throw new Error('Invalid authentication');
        }
        return data.data;
    }

    // Message handling

    _onWindowMessage(e) {
        const data = e.data;
        if (!this._frameEndpoint.authenticate(data)) { return; }

        const {action, params} = data.data;
        const messageHandler = this._windowMessageHandlers.get(action);
        if (typeof messageHandler === 'undefined') { return; }

        const callback = () => {}; // NOP
        yomichan.invokeMessageHandler(messageHandler, params, callback);
    }

    async _onMessageConfigure({frameId, ownerFrameId, popupId, optionsContext, childrenSupported, scale}) {
        this.ownerFrameId = ownerFrameId;
        this.setOptionsContext(optionsContext);

        await this.updateOptions();

        if (childrenSupported && !this._nestedPopupsPrepared) {
            const {depth} = optionsContext;
            this._prepareNestedPopups(depth + 1, popupId, frameId);
            this._nestedPopupsPrepared = true;
        }

        this._setContentScale(scale);
    }

    _onMessageSetContentScale({scale}) {
        this._setContentScale(scale);
    }

    _onMessageExtensionUnloaded() {
        if (yomichan.isExtensionUnloaded) { return; }
        yomichan.triggerExtensionUnloaded();
    }

    // Private

    _onMouseUp(e) {
        switch (e.button) {
            case 3: // Back
                if (this._history.hasPrevious()) {
                    e.preventDefault();
                }
                break;
            case 4: // Forward
                if (this._history.hasNext()) {
                    e.preventDefault();
                }
                break;
        }
    }

    _onClick(e) {
        switch (e.button) {
            case 3: // Back
                if (this._history.hasPrevious()) {
                    e.preventDefault();
                    this._history.back();
                }
                break;
            case 4: // Forward
                if (this._history.hasNext()) {
                    e.preventDefault();
                    this._history.forward();
                }
                break;
        }
    }

    _copySelection() {
        if (window.getSelection().toString()) { return false; }
        this._invokeOwner('copySelection');
        return true;
    }

    _setContentScale(scale) {
        const body = document.body;
        if (body === null) { return; }
        body.style.fontSize = `${scale}em`;
    }

    async _prepareNestedPopups(depth, parentPopupId, parentFrameId) {
        let complete = false;

        const onOptionsUpdated = async () => {
            const optionsContext = this.getOptionsContext();
            const options = await api.optionsGet(optionsContext);
            const maxPopupDepthExceeded = !(typeof depth === 'number' && depth <= options.scanning.popupNestingMaxDepth);
            if (maxPopupDepthExceeded || complete) { return; }

            complete = true;
            yomichan.off('optionsUpdated', onOptionsUpdated);

            try {
                await this.setupNestedPopups({
                    depth,
                    parentPopupId,
                    parentFrameId,
                    useProxyPopup: true
                });
            } catch (e) {
                yomichan.logError(e);
            }
        };

        yomichan.on('optionsUpdated', onOptionsUpdated);

        await onOptionsUpdated();
    }

    _invokeOwner(action, params={}) {
        return api.crossFrame.invoke(this.ownerFrameId, action, params);
    }
}