import kdbxweb from 'kdbxweb';
import { IconMap } from 'const/icon-map';
import { EntryModel } from 'models/entry-model';
import { MenuItemModel } from 'models/menu/menu-item-model';
import { IconUrlFormat } from 'util/formatting/icon-url-format';
import { GroupCollection } from 'collections/group-collection';
import { EntryCollection } from 'collections/entry-collection';

const KdbxIcons = kdbxweb.Consts.Icons;

const DefaultAutoTypeSequence = '{USERNAME}{TAB}{PASSWORD}{ENTER}';

class GroupModel extends MenuItemModel {
    setGroup(group, file, parentGroup) {
        const isRecycleBin = group.uuid.equals(file.db.meta.recycleBinUuid);
        const id = file.subId(group.uuid.id);
        this.set(
            {
                id,
                uuid: group.uuid.id,
                expanded: group.expanded,
                visible: !isRecycleBin,
                items: new GroupCollection(),
                entries: new EntryCollection(),
                filterValue: id,
                enableSearching: group.enableSearching,
                enableAutoType: group.enableAutoType,
                autoTypeSeq: group.defaultAutoTypeSeq,
                top: !parentGroup,
                drag: !!parentGroup,
                collapsible: !!parentGroup
            },
            { silent: true }
        );
        this.group = group;
        this.file = file;
        this.parentGroup = parentGroup;
        this._fillByGroup(true);
        const items = this.items;
        const entries = this.entries;

        const itemsArray = group.groups.map(subGroup => {
            let g = file.getGroup(file.subId(subGroup.uuid.id));
            if (g) {
                g.setGroup(subGroup, file, this);
            } else {
                g = GroupModel.fromGroup(subGroup, file, this);
            }
            return g;
        }, this);
        items.push(...itemsArray);

        const entriesArray = group.entries.map(entry => {
            let e = file.getEntry(file.subId(entry.uuid.id));
            if (e) {
                e.setEntry(entry, this, file);
            } else {
                e = EntryModel.fromEntry(entry, this, file);
            }
            return e;
        }, this);
        entries.push(...entriesArray);
    }

    _fillByGroup(silent) {
        this.set(
            {
                title: this.parentGroup ? this.group.name : this.file.name,
                iconId: this.group.icon,
                icon: this._iconFromId(this.group.icon),
                customIcon: this._buildCustomIcon(),
                customIconId: this.group.customIcon ? this.group.customIcon.toString() : null,
                expanded: this.group.expanded !== false
            },
            { silent }
        );
    }

    _iconFromId(id) {
        if (id === KdbxIcons.Folder || id === KdbxIcons.FolderOpen) {
            return undefined;
        }
        return IconMap[id];
    }

    _buildCustomIcon() {
        this.customIcon = null;
        if (this.group.customIcon) {
            return IconUrlFormat.toDataUrl(this.file.db.meta.customIcons[this.group.customIcon]);
        }
        return null;
    }

    _groupModified() {
        if (this.isJustCreated) {
            this.isJustCreated = false;
        }
        this.file.setModified();
        this.group.times.update();
    }

    forEachGroup(callback, filter) {
        let result = true;
        this.items.forEach(group => {
            if (group.matches(filter)) {
                result =
                    callback(group) !== false && group.forEachGroup(callback, filter) !== false;
            }
        });
        return result;
    }

    forEachOwnEntry(filter, callback) {
        this.entries.forEach(function(entry) {
            if (entry.matches(filter)) {
                callback(entry, this);
            }
        });
    }

    matches(filter) {
        return (
            ((filter && filter.includeDisabled) ||
                (this.group.enableSearching !== false &&
                    !this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup))) &&
            (!filter || !filter.autoType || this.group.enableAutoType !== false)
        );
    }

    getOwnSubGroups() {
        return this.group.groups;
    }

    addEntry(entry) {
        this.entries.push(entry);
    }

    addGroup(group) {
        this.items.push(group);
    }

    setName(name) {
        this._groupModified();
        this.group.name = name;
        this._fillByGroup();
    }

    setIcon(iconId) {
        this._groupModified();
        this.group.icon = iconId;
        this.group.customIcon = undefined;
        this._fillByGroup();
    }

    setCustomIcon(customIconId) {
        this._groupModified();
        this.group.customIcon = new kdbxweb.KdbxUuid(customIconId);
        this._fillByGroup();
    }

    setExpanded(expanded) {
        // this._groupModified(); // it's not good to mark the file as modified when a group is collapsed
        this.group.expanded = expanded;
        this.expanded = expanded;
    }

    setEnableSearching(enabled) {
        this._groupModified();
        let parentEnableSearching = true;
        let parentGroup = this.parentGroup;
        while (parentGroup) {
            if (typeof parentGroup.enableSearching === 'boolean') {
                parentEnableSearching = parentGroup.enableSearching;
                break;
            }
            parentGroup = parentGroup.parentGroup;
        }
        if (enabled === parentEnableSearching) {
            enabled = null;
        }
        this.group.enableSearching = enabled;
        this.enableSearching = this.group.enableSearching;
    }

    getEffectiveEnableSearching() {
        let grp = this;
        while (grp) {
            if (typeof grp.enableSearching === 'boolean') {
                return grp.enableSearching;
            }
            grp = grp.parentGroup;
        }
        return true;
    }

    setEnableAutoType(enabled) {
        this._groupModified();
        let parentEnableAutoType = true;
        let parentGroup = this.parentGroup;
        while (parentGroup) {
            if (typeof parentGroup.enableAutoType === 'boolean') {
                parentEnableAutoType = parentGroup.enableAutoType;
                break;
            }
            parentGroup = parentGroup.parentGroup;
        }
        if (enabled === parentEnableAutoType) {
            enabled = null;
        }
        this.group.enableAutoType = enabled;
        this.enableAutoType = this.group.enableAutoType;
    }

    getEffectiveEnableAutoType() {
        let grp = this;
        while (grp) {
            if (typeof grp.enableAutoType === 'boolean') {
                return grp.enableAutoType;
            }
            grp = grp.parentGroup;
        }
        return true;
    }

    setAutoTypeSeq(seq) {
        this._groupModified();
        this.group.defaultAutoTypeSeq = seq || undefined;
        this.autoTypeSeq = this.group.defaultAutoTypeSeq;
    }

    getEffectiveAutoTypeSeq() {
        let grp = this;
        while (grp) {
            if (grp.autoTypeSeq) {
                return grp.autoTypeSeq;
            }
            grp = grp.parentGroup;
        }
        return DefaultAutoTypeSequence;
    }

    getParentEffectiveAutoTypeSeq() {
        return this.parentGroup
            ? this.parentGroup.getEffectiveAutoTypeSeq()
            : DefaultAutoTypeSequence;
    }

    isEntryTemplatesGroup() {
        return this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup);
    }

    moveToTrash() {
        this.file.setModified();
        this.file.db.remove(this.group);
        if (this.group.uuid.equals(this.file.db.meta.entryTemplatesGroup)) {
            this.file.db.meta.entryTemplatesGroup = undefined;
        }
        this.file.reload();
    }

    deleteFromTrash() {
        this.file.db.move(this.group, null);
        this.file.reload();
    }

    removeWithoutHistory() {
        const ix = this.parentGroup.group.groups.indexOf(this.group);
        if (ix >= 0) {
            this.parentGroup.group.groups.splice(ix, 1);
        }
        this.file.reload();
    }

    moveHere(object) {
        if (!object || object.id === this.id) {
            return;
        }
        if (object.file === this.file) {
            this.file.setModified();
            if (object instanceof GroupModel) {
                for (let parent = this; parent; parent = parent.parentGroup) {
                    if (object === parent) {
                        return;
                    }
                }
                if (this.group.groups.indexOf(object.group) >= 0) {
                    return;
                }
                this.file.db.move(object.group, this.group);
                this.file.reload();
            } else if (object instanceof EntryModel) {
                if (this.group.entries.indexOf(object.entry) >= 0) {
                    return;
                }
                this.file.db.move(object.entry, this.group);
                this.file.reload();
            }
        } else {
            if (object instanceof EntryModel) {
                this.file.setModified();
                const detachedEntry = object.detach();
                this.file.db.importEntry(detachedEntry, this.group, object.file.db);
                this.file.reload();
            } else {
                // moving groups between files is not supported for now
            }
        }
    }

    moveToTop(object) {
        if (
            !object ||
            object.id === this.id ||
            object.file !== this.file ||
            !(object instanceof GroupModel)
        ) {
            return;
        }
        this.file.setModified();
        for (let parent = this; parent; parent = parent.parentGroup) {
            if (object === parent) {
                return;
            }
        }
        let atIndex = this.parentGroup.group.groups.indexOf(this.group);
        const selfIndex = this.parentGroup.group.groups.indexOf(object.group);
        if (selfIndex >= 0 && selfIndex < atIndex) {
            atIndex--;
        }
        if (atIndex >= 0) {
            this.file.db.move(object.group, this.parentGroup.group, atIndex);
        }
        this.file.reload();
    }

    static fromGroup(group, file, parentGroup) {
        const model = new GroupModel();
        model.setGroup(group, file, parentGroup);
        return model;
    }

    static newGroup(group, file) {
        const model = new GroupModel();
        const grp = file.db.createGroup(group.group);
        model.setGroup(grp, file, group);
        model.group.times.update();
        model.isJustCreated = true;
        group.addGroup(model);
        file.setModified();
        file.reload();
        return model;
    }
}

GroupModel.defineModelProperties({
    id: '',
    uuid: '',
    iconId: 0,
    entries: null,
    filterKey: 'group',
    editable: true,
    top: false,
    drag: true,
    drop: true,
    enableSearching: true,
    enableAutoType: null,
    autoTypeSeq: null,
    group: null,
    file: null,
    parentGroup: null,
    customIconId: null,
    isJustCreated: false
});

export { GroupModel };
