/*
 * Copyright (C) 2019-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
 * api
 * utilBackgroundIsolate
 */

class SettingsDictionaryListUI extends EventDispatcher {
    constructor(container, template, extraContainer, extraTemplate) {
        super();
        this.container = container;
        this.template = template;
        this.extraContainer = extraContainer;
        this.extraTemplate = extraTemplate;
        this.optionsDictionaries = null;
        this.dictionaries = null;
        this.dictionaryEntries = [];
        this.extra = null;

        document.querySelector('#dict-delete-confirm').addEventListener('click', this.onDictionaryConfirmDelete.bind(this), false);
    }

    setOptionsDictionaries(optionsDictionaries) {
        this.optionsDictionaries = optionsDictionaries;
        if (this.dictionaries !== null) {
            this.setDictionaries(this.dictionaries);
        }
    }

    setDictionaries(dictionaries) {
        for (const dictionaryEntry of this.dictionaryEntries) {
            dictionaryEntry.cleanup();
        }

        this.dictionaryEntries = [];
        this.dictionaries = toIterable(dictionaries);

        if (this.optionsDictionaries === null) {
            return;
        }

        let changed = false;
        for (const dictionaryInfo of this.dictionaries) {
            if (this.createEntry(dictionaryInfo)) {
                changed = true;
            }
        }

        this.updateDictionaryOrder();

        const titles = this.dictionaryEntries.map((e) => e.dictionaryInfo.title);
        const removeKeys = Object.keys(this.optionsDictionaries).filter((key) => titles.indexOf(key) < 0);
        if (removeKeys.length > 0) {
            for (const key of toIterable(removeKeys)) {
                delete this.optionsDictionaries[key];
            }
            changed = true;
        }

        if (changed) {
            this.save();
        }
    }

    createEntry(dictionaryInfo) {
        const title = dictionaryInfo.title;
        let changed = false;
        let optionsDictionary;
        const optionsDictionaries = this.optionsDictionaries;
        if (hasOwn(optionsDictionaries, title)) {
            optionsDictionary = optionsDictionaries[title];
        } else {
            optionsDictionary = SettingsDictionaryListUI.createDictionaryOptions();
            optionsDictionaries[title] = optionsDictionary;
            changed = true;
        }

        const content = document.importNode(this.template.content, true).firstChild;

        this.dictionaryEntries.push(new SettingsDictionaryEntryUI(this, dictionaryInfo, content, optionsDictionary));

        return changed;
    }

    static createDictionaryOptions() {
        return utilBackgroundIsolate({
            priority: 0,
            enabled: false,
            allowSecondarySearches: false
        });
    }

    createExtra(totalCounts, remainders, totalRemainder) {
        const content = document.importNode(this.extraTemplate.content, true).firstChild;
        this.extraContainer.appendChild(content);
        return new SettingsDictionaryExtraUI(this, totalCounts, remainders, totalRemainder, content);
    }

    setCounts(dictionaryCounts, totalCounts) {
        const remainders = Object.assign({}, totalCounts);
        const keys = Object.keys(remainders);

        for (let i = 0, ii = Math.min(this.dictionaryEntries.length, dictionaryCounts.length); i < ii; ++i) {
            const counts = dictionaryCounts[i];
            this.dictionaryEntries[i].setCounts(counts);

            for (const key of keys) {
                remainders[key] -= counts[key];
            }
        }

        let totalRemainder = 0;
        for (const key of keys) {
            totalRemainder += remainders[key];
        }

        if (this.extra !== null) {
            this.extra.cleanup();
            this.extra = null;
        }

        if (totalRemainder > 0) {
            this.extra = this.createExtra(totalCounts, remainders, totalRemainder);
        }
    }

    updateDictionaryOrder() {
        const sortInfo = this.dictionaryEntries.map((e, i) => [e, i]);
        sortInfo.sort((a, b) => {
            const i = b[0].optionsDictionary.priority - a[0].optionsDictionary.priority;
            return (i !== 0 ? i : a[1] - b[1]);
        });

        for (const [e] of sortInfo) {
            this.container.appendChild(e.content);
        }
    }

    save() {
        // Overwrite
    }

    preventPageExit() {
        // Overwrite
        return {end: () => {}};
    }

    onDictionaryConfirmDelete(e) {
        e.preventDefault();
        const n = document.querySelector('#dict-delete-modal');
        const title = n.dataset.dict;
        delete n.dataset.dict;
        $(n).modal('hide');

        const index = this.dictionaryEntries.findIndex((entry) => entry.dictionaryInfo.title === title);
        if (index >= 0) {
            this.dictionaryEntries[index].deleteDictionary();
        }
    }
}

class SettingsDictionaryEntryUI {
    constructor(parent, dictionaryInfo, content, optionsDictionary) {
        this.parent = parent;
        this.dictionaryInfo = dictionaryInfo;
        this.optionsDictionary = optionsDictionary;
        this.counts = null;
        this.eventListeners = new EventListenerCollection();
        this.isDeleting = false;

        this.content = content;
        this.enabledCheckbox = this.content.querySelector('.dict-enabled');
        this.allowSecondarySearchesCheckbox = this.content.querySelector('.dict-allow-secondary-searches');
        this.priorityInput = this.content.querySelector('.dict-priority');
        this.deleteButton = this.content.querySelector('.dict-delete-button');
        this.detailsToggleLink = this.content.querySelector('.dict-details-toggle-link');
        this.detailsContainer = this.content.querySelector('.dict-details');
        this.detailsTable = this.content.querySelector('.dict-details-table');

        if (this.dictionaryInfo.version < 3) {
            this.content.querySelector('.dict-outdated').hidden = false;
        }

        this.setupDetails(dictionaryInfo);

        this.content.querySelector('.dict-title').textContent = this.dictionaryInfo.title;
        this.content.querySelector('.dict-revision').textContent = `rev.${this.dictionaryInfo.revision}`;
        this.content.querySelector('.dict-prefix-wildcard-searches-supported').checked = !!this.dictionaryInfo.prefixWildcardsSupported;

        this.applyValues();

        this.eventListeners.addEventListener(this.enabledCheckbox, 'change', this.onEnabledChanged.bind(this), false);
        this.eventListeners.addEventListener(this.allowSecondarySearchesCheckbox, 'change', this.onAllowSecondarySearchesChanged.bind(this), false);
        this.eventListeners.addEventListener(this.priorityInput, 'change', this.onPriorityChanged.bind(this), false);
        this.eventListeners.addEventListener(this.deleteButton, 'click', this.onDeleteButtonClicked.bind(this), false);
        this.eventListeners.addEventListener(this.detailsToggleLink, 'click', this.onDetailsToggleLinkClicked.bind(this), false);
    }

    setupDetails(dictionaryInfo) {
        const targets = [
            ['Author', 'author'],
            ['URL', 'url'],
            ['Description', 'description'],
            ['Attribution', 'attribution']
        ];

        let count = 0;
        for (const [label, key] of targets) {
            const info = dictionaryInfo[key];
            if (typeof info !== 'string') { continue; }

            const n1 = document.createElement('div');
            n1.className = 'dict-details-entry';
            n1.dataset.type = key;

            const n2 = document.createElement('span');
            n2.className = 'dict-details-entry-label';
            n2.textContent = `${label}:`;
            n1.appendChild(n2);

            const n3 = document.createElement('span');
            n3.className = 'dict-details-entry-info';
            n3.textContent = info;
            n1.appendChild(n3);

            this.detailsTable.appendChild(n1);

            ++count;
        }

        if (count === 0) {
            this.detailsContainer.hidden = true;
            this.detailsToggleLink.hidden = true;
        }
    }

    cleanup() {
        if (this.content !== null) {
            if (this.content.parentNode !== null) {
                this.content.parentNode.removeChild(this.content);
            }
            this.content = null;
        }
        this.dictionaryInfo = null;
        this.eventListeners.removeAllEventListeners();
    }

    setCounts(counts) {
        this.counts = counts;
        const node = this.content.querySelector('.dict-counts');
        node.textContent = JSON.stringify({
            info: this.dictionaryInfo,
            counts
        }, null, 4);
        node.removeAttribute('hidden');
    }

    save() {
        this.parent.save();
    }

    applyValues() {
        this.enabledCheckbox.checked = this.optionsDictionary.enabled;
        this.allowSecondarySearchesCheckbox.checked = this.optionsDictionary.allowSecondarySearches;
        this.priorityInput.value = `${this.optionsDictionary.priority}`;
    }

    async deleteDictionary() {
        if (this.isDeleting) {
            return;
        }

        const progress = this.content.querySelector('.progress');
        progress.hidden = false;
        const progressBar = this.content.querySelector('.progress-bar');
        this.isDeleting = true;

        const prevention = this.parent.preventPageExit();
        try {
            const onProgress = ({processed, count, storeCount, storesProcesed}) => {
                let percent = 0.0;
                if (count > 0 && storesProcesed > 0) {
                    percent = (processed / count) * (storesProcesed / storeCount) * 100.0;
                }
                progressBar.style.width = `${percent}%`;
            };

            await api.deleteDictionary(this.dictionaryInfo.title, onProgress);
        } catch (e) {
            this.dictionaryErrorsShow([e]);
        } finally {
            prevention.end();
            this.isDeleting = false;
            progress.hidden = true;

            this.parent.trigger('databaseUpdated');
        }
    }

    onEnabledChanged(e) {
        this.optionsDictionary.enabled = !!e.target.checked;
        this.save();
    }

    onAllowSecondarySearchesChanged(e) {
        this.optionsDictionary.allowSecondarySearches = !!e.target.checked;
        this.save();
    }

    onPriorityChanged(e) {
        let value = Number.parseFloat(e.target.value);
        if (Number.isNaN(value)) {
            value = this.optionsDictionary.priority;
        } else {
            this.optionsDictionary.priority = value;
            this.save();
        }

        e.target.value = `${value}`;

        this.parent.updateDictionaryOrder();
    }

    onDeleteButtonClicked(e) {
        e.preventDefault();

        if (this.isDeleting) {
            return;
        }

        const title = this.dictionaryInfo.title;
        const n = document.querySelector('#dict-delete-modal');
        n.dataset.dict = title;
        document.querySelector('#dict-remove-modal-dict-name').textContent = title;
        $(n).modal('show');
    }

    onDetailsToggleLinkClicked(e) {
        e.preventDefault();

        this.detailsContainer.hidden = !this.detailsContainer.hidden;
    }
}

class SettingsDictionaryExtraUI {
    constructor(parent, totalCounts, remainders, totalRemainder, content) {
        this.parent = parent;
        this.content = content;

        this.content.querySelector('.dict-total-count').textContent = `${totalRemainder} item${totalRemainder !== 1 ? 's' : ''}`;

        const node = this.content.querySelector('.dict-counts');
        node.textContent = JSON.stringify({
            counts: totalCounts,
            remainders: remainders
        }, null, 4);
        node.removeAttribute('hidden');
    }

    cleanup() {
        if (this.content !== null) {
            if (this.content.parentNode !== null) {
                this.content.parentNode.removeChild(this.content);
            }
            this.content = null;
        }
    }
}

class DictionaryController {
    constructor(settingsController) {
        this._settingsController = settingsController;
        this._dictionaryUI = null;
    }

    async prepare() {
        this._dictionaryUI = new SettingsDictionaryListUI(
            document.querySelector('#dict-groups'),
            document.querySelector('#dict-template'),
            document.querySelector('#dict-groups-extra'),
            document.querySelector('#dict-extra-template')
        );
        this._dictionaryUI.save = () => this._settingsController.save();
        this._dictionaryUI.preventPageExit = this._preventPageExit.bind(this);
        this._dictionaryUI.on('databaseUpdated', this._onDatabaseUpdated.bind(this));

        document.querySelector('#dict-main').addEventListener('change', this._onDictionaryMainChanged.bind(this), false);
        document.querySelector('#database-enable-prefix-wildcard-searches').addEventListener('change', this._onDatabaseEnablePrefixWildcardSearchesChanged.bind(this), false);

        this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));
        this._settingsController.on('databaseUpdated', this._onDatabaseUpdated.bind(this));

        await this._onOptionsChanged();
        await this._onDatabaseUpdated();
    }

    // Private

    async _onOptionsChanged() {
        const options = await this._settingsController.getOptionsMutable();

        this._dictionaryUI.setOptionsDictionaries(options.dictionaries);

        const optionsFull = await this._settingsController.getOptionsFull();
        document.querySelector('#database-enable-prefix-wildcard-searches').checked = optionsFull.global.database.prefixWildcardsSupported;

        await this._updateMainDictionarySelectValue();
    }

    _updateMainDictionarySelectOptions(dictionaries) {
        const select = document.querySelector('#dict-main');
        select.textContent = ''; // Empty

        let option = document.createElement('option');
        option.className = 'text-muted';
        option.value = '';
        option.textContent = 'Not selected';
        select.appendChild(option);

        for (const {title, sequenced} of toIterable(dictionaries)) {
            if (!sequenced) { continue; }

            option = document.createElement('option');
            option.value = title;
            option.textContent = title;
            select.appendChild(option);
        }
    }

    async _updateMainDictionarySelectValue() {
        const options = await this._settingsController.getOptions();

        const value = options.general.mainDictionary;

        const select = document.querySelector('#dict-main');
        let selectValue = null;
        for (const child of select.children) {
            if (child.nodeName.toUpperCase() === 'OPTION' && child.value === value) {
                selectValue = value;
                break;
            }
        }

        let missingNodeOption = select.querySelector('option[data-not-installed=true]');
        if (selectValue === null) {
            if (missingNodeOption === null) {
                missingNodeOption = document.createElement('option');
                missingNodeOption.className = 'text-muted';
                missingNodeOption.value = value;
                missingNodeOption.textContent = `${value} (Not installed)`;
                missingNodeOption.dataset.notInstalled = 'true';
                select.appendChild(missingNodeOption);
            }
        } else {
            if (missingNodeOption !== null) {
                missingNodeOption.parentNode.removeChild(missingNodeOption);
            }
        }

        select.value = value;
    }

    async _onDatabaseUpdated() {
        try {
            const dictionaries = await api.getDictionaryInfo();
            this._dictionaryUI.setDictionaries(dictionaries);

            document.querySelector('#dict-warning').hidden = (dictionaries.length > 0);

            this._updateMainDictionarySelectOptions(dictionaries);
            await this._updateMainDictionarySelectValue();

            const {counts, total} = await api.getDictionaryCounts(dictionaries.map((v) => v.title), true);
            this._dictionaryUI.setCounts(counts, total);
        } catch (e) {
            yomichan.logError(e);
        }
    }

    async _onDictionaryMainChanged(e) {
        const select = e.target;
        const value = select.value;

        const missingNodeOption = select.querySelector('option[data-not-installed=true]');
        if (missingNodeOption !== null && missingNodeOption.value !== value) {
            missingNodeOption.parentNode.removeChild(missingNodeOption);
        }

        const options = await this._settingsController.getOptionsMutable();
        options.general.mainDictionary = value;
        await this._settingsController.save();
    }

    async _onDatabaseEnablePrefixWildcardSearchesChanged(e) {
        const optionsFull = await this._settingsController.getOptionsFullMutable();
        const v = !!e.target.checked;
        if (optionsFull.global.database.prefixWildcardsSupported === v) { return; }
        optionsFull.global.database.prefixWildcardsSupported = !!e.target.checked;
        await this._settingsController.save();
    }

    _preventPageExit() {
        return this._settingsController.preventPageExit();
    }
}