import ngModule from '../../module';
import ModelProxy from '../model-proxy/model-proxy';

/**
 * Model that uses an array as datasource.
 */
export default class ArrayModel extends ModelProxy {
    constructor($q, $filter, $element, $scope) {
        super($element, $scope);
        this.$q = $q;
        this.$filter = $filter;
        this.autoLoad = true;
        this.limit = 0;
        this.userFilter = [];

        this.clear();
    }

    $onInit() {
        this.autoRefresh();
    }

    get isLoading() {
        return false;
    }

    autoRefresh() {
        if (this.autoLoad)
            return this.refresh();
        return this.$q.resolve();
    }

    refresh() {
        this.data = this.proccessData(0);
        return this.$q.resolve();
    }

    proccessData(skip) {
        if (!this.proxiedData) return null;
        let data = this.proxiedData.slice();

        let filter = {
            order: this.order,
            limit: this.limit
        };

        if (this.where)
            filter.where = [this.where];

        filter = this.mergeFilters(this.userFilter, filter);

        let where = filter.where;

        if (where) {
            if (!Array.isArray(where))
                where = [where];

            for (let subWhere of where)
                data = this.$filter('filter')(data, subWhere);
        }

        let order = filter.order;

        if (typeof order === 'string')
            order = order.split(/\s*,\s*/);

        if (Array.isArray(order)) {
            let orderComp = [];

            for (let field of order) {
                let split = field.split(/\s+/);
                orderComp.push({
                    field: split[0],
                    way: split[1] === 'DESC' ? -1 : 1
                });
            }

            data = data.sort((a, b) => this.sortFunc(a, b, orderComp));
        } else if (typeof order === 'function')
            data = data.sort(order);

        this.skip = skip;

        if (filter.limit) {
            let end = skip + filter.limit;
            this.moreRows = end < data.length;
            data = data.slice(this.skip, end);
        } else
            this.moreRows = false;

        this.currentFilter = filter;
        return data;
    }

    applyFilter(user, params) {
        this.userFilter = user;
        this.userParams = params;
        return this.refresh();
    }

    addFilter(user, params) {
        this.userFilter = this.mergeFilters(user, this.userFilter);
        this.userParams = Object.assign({}, this.userParams, params);
        return this.refresh();
    }

    removeFilter() {
        return applyFilter(null, null);
    }

    loadMore() {
        if (!this.moreRows)
            return this.$q.resolve();

        let data = this.proccessData(this.skip + this.currentFilter.limit);
        this.data = this.data.concat(data);
        return this.$q.resolve();
    }

    clear() {
        this.data = null;
        this.userFilter = null;
        this.moreRows = false;
        this.skip = 0;
    }

    /**
     * Saves current changes on the server.
     *
     * @return {Promise} The save request promise
     */
    save() {
        if (this.getChanges())
            this.orgData = this.data;

        super.save();

        return this.$q.resolve();
    }

    sortFunc(a, b, order) {
        for (let i of order) {
            let compRes = this.compareFunc(a[i.field], b[i.field]) * i.way;
            if (compRes !== 0)
                return compRes;
        }

        return 0;
    }

    compareFunc(a, b) {
        if (a === b)
            return 0;

        let aType = typeof a;

        if (aType === typeof b) {
            switch (aType) {
            case 'string':
                return a.localeCompare(b);
            case 'number':
                return a - b;
            case 'boolean':
                return a ? 1 : -1;
            case 'object':
                if (a instanceof Date && b instanceof Date)
                    return a.getTime() - b.getTime();
            }
        }

        if (a === undefined)
            return -1;
        if (b === undefined)
            return 1;
        if (a === null)
            return -1;
        if (b === null)
            return 1;

        return a > b ? 1 : -1;
    }

    mergeFilters(src, dst) {
        let mergedWhere = [];
        let wheres = [dst.where, src.where];

        for (let where of wheres) {
            if (Array.isArray(where))
                mergedWhere = mergedWhere.concat(where);
            else if (where)
                mergedWhere.push(where);
        }

        switch (mergedWhere.length) {
        case 0:
            mergedWhere = undefined;
            break;
        case 1:
            mergedWhere = mergedWhere[0];
            break;
        }

        return {
            where: mergedWhere,
            order: src.order || dst.order,
            limit: src.limit || dst.limit
        };
    }

    undoChanges() {
        super.undoChanges();
        this.refresh();
    }
}
ArrayModel.$inject = ['$q', '$filter', '$element', '$scope'];

ngModule.vnComponent('vnArrayModel', {
    controller: ArrayModel,
    bindings: {
        orgData: '<?',
        data: '=?',
        link: '<?',
        order: '@?',
        limit: '<?',
        autoLoad: '<?',
        autoSave: '<?'
    }
});