/*
 * 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
 * ConditionsUI
 * api
 * conditionsClearCaches
 * getOptionsFullMutable
 * getProfileIndex
 * onOptionsUpdated
 * profileConditionsDescriptor
 * profileConditionsDescriptorPromise
 * setProfileIndex
 * settingsSaveOptions
 * utilBackgroundIsolate
 */

class ProfileController {
    constructor() {
        this._conditionsContainer = null;
    }

    async prepare() {
        const optionsFull = await getOptionsFullMutable();
        setProfileIndex(optionsFull.profileCurrent);

        this._setupEventListeners();
        await this._updateTarget(optionsFull);
    }

    // Private

    _setupEventListeners() {
        $('#profile-target').change(this._onTargetProfileChanged.bind(this));
        $('#profile-name').change(this._onNameChanged.bind(this));
        $('#profile-add').click(this._onAdd.bind(this));
        $('#profile-remove').click(this._onRemove.bind(this));
        $('#profile-remove-confirm').click(this._onRemoveConfirm.bind(this));
        $('#profile-copy').click(this._onCopy.bind(this));
        $('#profile-copy-confirm').click(this._onCopyConfirm.bind(this));
        $('#profile-move-up').click(() => this._onMove(-1));
        $('#profile-move-down').click(() => this._onMove(1));
        $('.profile-form').find('input, select, textarea').not('.profile-form-manual').change(this._onInputChanged.bind(this));
    }

    _tryGetIntegerValue(selector, min, max) {
        const value = parseInt($(selector).val(), 10);
        return (
            typeof value === 'number' &&
            Number.isFinite(value) &&
            Math.floor(value) === value &&
            value >= min &&
            value < max
        ) ? value : null;
    }

    async _formRead(optionsFull) {
        const currentProfileIndex = getProfileIndex();
        const profile = optionsFull.profiles[currentProfileIndex];

        // Current profile
        const index = this._tryGetIntegerValue('#profile-active', 0, optionsFull.profiles.length);
        if (index !== null) {
            optionsFull.profileCurrent = index;
        }

        // Profile name
        profile.name = $('#profile-name').val();
    }

    async _formWrite(optionsFull) {
        const currentProfileIndex = getProfileIndex();
        const profile = optionsFull.profiles[currentProfileIndex];

        this._populateSelect($('#profile-active'), optionsFull.profiles, optionsFull.profileCurrent, null);
        this._populateSelect($('#profile-target'), optionsFull.profiles, currentProfileIndex, null);
        $('#profile-remove').prop('disabled', optionsFull.profiles.length <= 1);
        $('#profile-copy').prop('disabled', optionsFull.profiles.length <= 1);
        $('#profile-move-up').prop('disabled', currentProfileIndex <= 0);
        $('#profile-move-down').prop('disabled', currentProfileIndex >= optionsFull.profiles.length - 1);

        $('#profile-name').val(profile.name);

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

        await profileConditionsDescriptorPromise;
        this._conditionsContainer = new ConditionsUI.Container(
            profileConditionsDescriptor,
            'popupLevel',
            profile.conditionGroups,
            $('#profile-condition-groups'),
            $('#profile-add-condition-group')
        );
        this._conditionsContainer.save = () => {
            settingsSaveOptions();
            conditionsClearCaches(profileConditionsDescriptor);
        };
        this._conditionsContainer.isolate = utilBackgroundIsolate;
    }

    _populateSelect(select, profiles, currentValue, ignoreIndices) {
        select.empty();


        for (let i = 0; i < profiles.length; ++i) {
            if (ignoreIndices !== null && ignoreIndices.indexOf(i) >= 0) {
                continue;
            }
            const profile = profiles[i];
            select.append($(`<option value="${i}">${profile.name}</option>`));
        }

        select.val(`${currentValue}`);
    }

    async _updateTarget(optionsFull) {
        await this._formWrite(optionsFull);
        await onOptionsUpdated({source: null});
    }

    _createCopyName(name, profiles, maxUniqueAttempts) {
        let space, index, prefix, suffix;
        const match = /^([\w\W]*\(Copy)((\s+)(\d+))?(\)\s*)$/.exec(name);
        if (match === null) {
            prefix = `${name} (Copy`;
            space = '';
            index = '';
            suffix = ')';
        } else {
            prefix = match[1];
            suffix = match[5];
            if (typeof match[2] === 'string') {
                space = match[3];
                index = parseInt(match[4], 10) + 1;
            } else {
                space = ' ';
                index = 2;
            }
        }

        let i = 0;
        while (true) {
            const newName = `${prefix}${space}${index}${suffix}`;
            if (i++ >= maxUniqueAttempts || profiles.findIndex((profile) => profile.name === newName) < 0) {
                return newName;
            }
            if (typeof index !== 'number') {
                index = 2;
                space = ' ';
            } else {
                ++index;
            }
        }
    }

    async _onInputChanged(e) {
        if (!e.originalEvent && !e.isTrigger) {
            return;
        }

        const optionsFull = await getOptionsFullMutable();
        await this._formRead(optionsFull);
        await settingsSaveOptions();
    }

    async _onTargetProfileChanged() {
        const optionsFull = await getOptionsFullMutable();
        const currentProfileIndex = getProfileIndex();
        const index = this._tryGetIntegerValue('#profile-target', 0, optionsFull.profiles.length);
        if (index === null || currentProfileIndex === index) {
            return;
        }

        setProfileIndex(index);

        await this._updateTarget(optionsFull);

        yomichan.trigger('modifyingProfileChange');
    }

    async _onAdd() {
        const optionsFull = await getOptionsFullMutable();
        const currentProfileIndex = getProfileIndex();
        const profile = utilBackgroundIsolate(optionsFull.profiles[currentProfileIndex]);
        profile.name = this._createCopyName(profile.name, optionsFull.profiles, 100);
        optionsFull.profiles.push(profile);

        setProfileIndex(optionsFull.profiles.length - 1);

        await this._updateTarget(optionsFull);
        await settingsSaveOptions();

        yomichan.trigger('modifyingProfileChange');
    }

    async _onRemove(e) {
        if (e.shiftKey) {
            return await this._onRemoveConfirm();
        }

        const optionsFull = await api.optionsGetFull();
        if (optionsFull.profiles.length <= 1) {
            return;
        }

        const currentProfileIndex = getProfileIndex();
        const profile = optionsFull.profiles[currentProfileIndex];

        $('#profile-remove-modal-profile-name').text(profile.name);
        $('#profile-remove-modal').modal('show');
    }

    async _onRemoveConfirm() {
        $('#profile-remove-modal').modal('hide');

        const optionsFull = await getOptionsFullMutable();
        if (optionsFull.profiles.length <= 1) {
            return;
        }

        const currentProfileIndex = getProfileIndex();
        optionsFull.profiles.splice(currentProfileIndex, 1);

        if (currentProfileIndex >= optionsFull.profiles.length) {
            setProfileIndex(optionsFull.profiles.length - 1);
        }

        if (optionsFull.profileCurrent >= optionsFull.profiles.length) {
            optionsFull.profileCurrent = optionsFull.profiles.length - 1;
        }

        await this._updateTarget(optionsFull);
        await settingsSaveOptions();

        yomichan.trigger('modifyingProfileChange');
    }

    _onNameChanged() {
        const currentProfileIndex = getProfileIndex();
        $('#profile-active, #profile-target').find(`[value="${currentProfileIndex}"]`).text(this.value);
    }

    async _onMove(offset) {
        const optionsFull = await getOptionsFullMutable();
        const currentProfileIndex = getProfileIndex();
        const index = currentProfileIndex + offset;
        if (index < 0 || index >= optionsFull.profiles.length) {
            return;
        }

        const profile = optionsFull.profiles[currentProfileIndex];
        optionsFull.profiles.splice(currentProfileIndex, 1);
        optionsFull.profiles.splice(index, 0, profile);

        if (optionsFull.profileCurrent === currentProfileIndex) {
            optionsFull.profileCurrent = index;
        }

        setProfileIndex(index);

        await this._updateTarget(optionsFull);
        await settingsSaveOptions();

        yomichan.trigger('modifyingProfileChange');
    }

    async _onCopy() {
        const optionsFull = await api.optionsGetFull();
        if (optionsFull.profiles.length <= 1) {
            return;
        }

        const currentProfileIndex = getProfileIndex();
        this._populateSelect($('#profile-copy-source'), optionsFull.profiles, currentProfileIndex === 0 ? 1 : 0, [currentProfileIndex]);
        $('#profile-copy-modal').modal('show');
    }

    async _onCopyConfirm() {
        $('#profile-copy-modal').modal('hide');

        const optionsFull = await getOptionsFullMutable();
        const index = this._tryGetIntegerValue('#profile-copy-source', 0, optionsFull.profiles.length);
        const currentProfileIndex = getProfileIndex();
        if (index === null || index === currentProfileIndex) {
            return;
        }

        const profileOptions = utilBackgroundIsolate(optionsFull.profiles[index].options);
        optionsFull.profiles[currentProfileIndex].options = profileOptions;

        await this._updateTarget(optionsFull);
        await settingsSaveOptions();
    }
}