import Model from './Model';
import Series from './Series';
import globalDefault from './globalDefault';
import { clone, merge, each, isString, filter, isArray, map } from '../util';
import { formatTextStyle } from '../util/graphic';
import ComponentModel from './Component';
import dirtyActions from '../action/dirty';

const TYPE = 'global';

/**
 * 全局管理调度Model类
 * 
 * @class GlobalModel
 * @extends {Model}
 */
class GlobalModel extends Model {

    type = TYPE;

    static type = TYPE;

    _option = {};

    _componentsMap = {};

    /**
     * 设置新的option
     * 
     * @param {Object} newOption 
     */
    setOption(newOption) {
        let { _componentsMap: componentsMap, global } = this;
        this._option = merge(newOption, clone(globalDefault), false);

        each(newOption, (componentsOption, componentType) => {

            if (ComponentModel.hasClass(componentType)) {
                let isSeries = componentType === 'series'
                componentsMap[componentType] = [];

                !isArray(componentsOption) && (componentsOption = [componentsOption]);

                each(componentsOption, (componentOption, index) => {
                    let { id, name, type } = componentOption;
                    let ComponentModelClass, componentModel;
                    let classType = isSeries ? type : componentType;
                    ComponentModelClass = ComponentModel.getClass(classType);

                    if (ComponentModelClass) {
                        componentModel = new ComponentModelClass(componentOption, this, this, global, {
                            id: `${componentType}_${index}_${isSeries ? type : ' '}_${id || name || ' '}`,
                            index
                        });
                        componentModel.type = classType;
                        componentsMap[componentType][index] = componentModel;
                    } else {
                        console.warn(`图表类型${classType}未注册!`);
                    }
                });
            } else {
                !globalDefault.hasOwnProperty(componentType) && console.warn(`组件类型${componentType}未注册!`);
            }
        });

        this._afterSetOption();
    }

    /**
     * 清除所有model
     */
    clear() {
        this._componentsMap = {};
    }

    /**
     * setOption后执行
     * 
     * @private
     */
    _afterSetOption() {
        this._setDepAndSup();
        this._setAxisIndex();
        this.updateData();
        this._setSeriesSelected();
        this.dirty();
    }

    /**
     * 设置所有组件/图表的依赖和被依赖
     * 
     * @private
     */
    _setDepAndSup() {
        if (this.global.isError()) return;

        let { _componentsMap: componentsMap, global } = this;
        let reg = /^\$(.*)Index$/;
        let sup = {};

        // 使y轴依赖于x轴
        this.eachComponent('series', component => {
            let { $axisIndex } = component._option;

            if ($axisIndex && $axisIndex.length === 2) {
                let axisModel0 = this.getComponentByIndex('axis', $axisIndex[0]);
                let axisModel1 = this.getComponentByIndex('axis', $axisIndex[1]);

                if (axisModel0.get('xOrY') === 'y') {
                    axisModel0._option.$axisIndex = $axisIndex[1];
                } else {
                    axisModel1._option.$axisIndex = $axisIndex[0];
                }
            }
        })

        this.eachComponent((componentType, component, index) => {
            let componentOption = component._option;
            let dep = {};

            each(componentOption, (value, key) => {
                let matched = key.match(reg);

                if (matched && typeof value !== 'undefined') {

                    if (isArray(value)) {
                        value = map(value, (v) => {
                            if (isString(v)) {
                                let matchedModel = this.getComponentById(matched[1], v);

                                if (matchedModel) {
                                    return matchedModel.index;
                                } else {
                                    return v;
                                }
                            } else {
                                return v;
                            }
                        })
                    } else {
                        if (isString(value)) {
                            let matchedModel = this.getComponentById(matched[1], value);

                            matchedModel && (value = matchedModel.index);
                        }
                    }

                    dep[matched[1]] = value;
                    !sup[matched[1]] && (sup[matched[1]] = []);

                    if (isArray(value)) {
                        each(value, function (v) {
                            !sup[matched[1]][v] && (sup[matched[1]][v] = {});
                            !sup[matched[1]][v][componentType] && (sup[matched[1]][v][componentType] = []);
                            sup[matched[1]][v][componentType].push(index);
                        });
                    } else {
                        !sup[matched[1]][value] && (sup[matched[1]][value] = {});
                        !sup[matched[1]][value][componentType] && (sup[matched[1]][value][componentType] = []);
                        sup[matched[1]][value][componentType].push(index);
                    }
                }
            });

            component.dependencies = dep;
        }, false);

        for (let type in sup) {
            for (let i = sup[type].length; i--;) {
                if (sup[type][i]) {
                    if (componentsMap[type] && componentsMap[type][i]) {
                        componentsMap[type][i].supports = sup[type][i];
                    } else {
                        let warnInfo = '';

                        // global.error();
                        each(sup[type][i], function (indexs, supType) {
                            warnInfo += `${supType}[${indexs.toString()}], `;
                        });
                        warnInfo += `所依赖的${type}[${i}]未在setOption方法中进行配置!`;
                        console.warn('依赖错误:', warnInfo)

                        return;
                    }
                }
            }
        }
    }

    /**
     * 设置gridIndex,在_setSeriesIndex中使用
     * 
     * @private
     */
    _setAxisIndex() {
        if (this.global.isError()) return;

        this.eachComponent('series', (model) => {
            let xAxisModel = model.getAxisModel('x');

            if (xAxisModel) {
                model.axisIndex = xAxisModel.index;

                let yAxisModel = model.getAxisModel('y');

                if (yAxisModel) {
                    model.axisIndex += '/' + (model.get('stack') ? yAxisModel.index : '_');
                    model.axisIndex += '/' + (model.get('overlap') ? yAxisModel.index : '_');
                }
            }
        });
    }

    /**
     * 设置series的selected状态
     * 
     * @private
     */
    _setSeriesSelected() {
        if (this.global.isError()) return;

        this.eachComponent('series', (seriesModel) => {
            this.eachComponent('legend', (legendModel) => {
                let legendSelected = legendModel.get('selected');

                if (legendSelected) {
                    each(legendSelected, (bool, name) => {
                        if (seriesModel.get('name') === name) {
                            seriesModel._option.selected = bool;
                        } else if (seriesModel.findDataNamed(name)) {
                            !seriesModel._option.selected && (seriesModel._option.selected = {});
                            seriesModel._option.selected[name] = bool;
                        }
                    });
                } else {
                    let legendData = legendModel.get('data')

                    each(legendData, (name) => {
                        if (seriesModel.get('name') === name) {
                            seriesModel._option.selected = true;
                        } else if (seriesModel.findDataNamed(name)) {
                            !seriesModel._option.selected && (seriesModel._option.selected = {});
                            seriesModel._option.selected[name] = true;
                        }
                    });
                }
            });
        })
    }

    /**
     * 更新所有model或依赖某个dataModel的所有model的数据
     * 
     * @param {Object} [dataModel] 要更新的dataModel
     */
    updateData(dataModel) {
        if (this.global.isError()) return;

        let { _componentsMap: componentsMap } = this;
        let dataUpdated = [];

        if (dataModel) {
            if (dataModel.type === 'data' && dataModel.isDirty()) {
                dataModel.__update(); // 更新该dataModel
                updateSupportsData(componentsMap, dataModel.supports); // 以及依赖该dataModel的所有model的数据
            }
        } else {
            // 更新所有data模块及data模块所依赖的模块
            this.eachComponent('data', function (component) {
                if (component.isDirty()) {
                    updateDependencies(componentsMap, component.dependencies, component.type, component.index, true);
                    component.__update();
                }
            });

            // 更新所有model的数据
            this.eachComponent(function (componentType, component) {
                componentType !== 'data' && component.isDataDirty() && component.updateData();
            });
        }
    }

    /**
     * 在更新前执行
     */
    beforeUpdate() {
        this._setSeriesIndex('bar');
        this._setSeriesIndex('line');
        this._setStackData();
    }

    /**
     * 设置bar类型图表的barIndex和barTotal,用于后续计算绘图
     * 
     * @private
     */
    _setSeriesIndex(type) {
        if (this.global.isError()) return;

        let gridTotal = {};
        let gridIndex = {};

        this.eachComponent('series', function (model) {
            if (model.type === type && model.axisIndex !== undefined) {
                let overlap = model.get('overlap');
                let stack = model.get('stack');
                let modelAxisIndex = model.axisIndex;
                let indexName = `${type}Index`;
                let Type = type.slice(0, 1).toUpperCase() + type.slice(1); // 大写首字母
                let yAxisModel = model.getAxisModel('y');

                if (yAxisModel) {
                    let $gridIndex = yAxisModel.get('$gridIndex');
                    let axisIndex = gridIndex[$gridIndex] = gridIndex[$gridIndex] || {};

                    !gridTotal[$gridIndex] && (gridTotal[$gridIndex] = 0);

                    if (stack || overlap) {
                        axisIndex[modelAxisIndex] === undefined &&
                            (axisIndex[modelAxisIndex] = gridTotal[$gridIndex]++);
                    } else {
                        axisIndex[modelAxisIndex] = gridTotal[$gridIndex]++;
                    }

                    model[`last${Type}Index`] = model[indexName];
                    model[indexName] = axisIndex[modelAxisIndex];
                }
            }
        });

        this.eachComponent('series', function (model) {
            if (model.type === type) {
                let yAxisModel = model.getAxisModel('y');

                if (yAxisModel) {
                    let $gridIndex = yAxisModel.get('$gridIndex');
                    let Type = type.slice(0, 1).toUpperCase() + type.slice(1); // 大写首字母
    
                    if (model.get('selected') === false) {
                        model[`last${Type}Total`] = undefined;
                        model[`${type}Total`] = undefined;
                    } else {
                        model[`last${Type}Total`] = model[`${type}Total`];
                        model[`${type}Total`] = gridTotal[$gridIndex];
                    }
                }
            }
        }, false);
    }

    /**
     * 计算设置堆积类型数据
     * 
     * @private
     */
    _setStackData() {
        if (this.global.isError()) return;

        let stacks = {};
        let xList = []

        this.eachComponent('series', function (model) {
            let data = model.getData();

            if (!model.get('stack') || model.get('selected') === false || !data) return;

            if (!xList.length) {
                xList = map(data, d => d[0]);
            } else {
                let newXList = [];
                let i, j;

                for (i = 0, j = 0; i < data.length && j < xList.length;) {
                    let cur = data[i][0];
                    let last = xList[j];

                    if (cur < last) {
                        newXList.push(data[i][0]);
                        i++;
                    } else if (cur === last) {
                        newXList.push(data[i][0]);
                        i++;
                        j++;
                    } else {
                        newXList.push(xList[j]);
                        j++;
                    }
                }

                while (i < data.length) {
                    newXList.push(data[i][0]);
                    i++;
                }

                while (j < xList.length) {
                    newXList.push(xList[j]);
                    j++;
                }

                xList = newXList;
            }
        });

        this.eachComponent('series', function (model) {
            let stackName = model.get('stack');

            if (stackName) {
                let data = model.getData();

                model._stackName = stackName = stackName + '|' + model.axisIndex;

                !stacks[stackName] && (stacks[stackName] = []);

                if (model.get('selected') === false || !data) {
                    model.stackTotal = undefined;
                    return;
                }

                let stack = stacks[stackName];
                let len = stack.length;
                let stackData = [];
                let newData = [];
                let i, j;

                for (i = 0, j = 0; i < data.length && j < xList.length;) {
                    let cur = data[i][0];
                    let last = xList[j];

                    if (cur < last) {
                        newData.push(data[i]);
                        i++;
                    } else if (cur === last) {
                        newData.push(data[i]);
                        i++;
                        j++;
                    } else {
                        if (i === 0) {
                            newData.push([xList[j], 0]);
                        } else {
                            newData.push([xList[j], data[i - 1][1]]);
                        }
                        j++;
                    }
                }

                while (j < xList.length) {
                    if (i === 0) {
                        newData.push([xList[j], 0]);
                    } else {
                        newData.push([xList[j], data[i - 1][1]]);
                    }
                    j++;
                }

                if (len) {
                    let lastStack = stack[len - 1];
                    let i, j;

                    stackData = map(lastStack, function (d, index) {
                        let startValue = 0;

                        if (newData[index][1] >= 0) {
                            for (let k = stack.length; k--;) {
                                if (stack[k][index][1][1] >= 0) {
                                    startValue = stack[k][index][1][1];
                                    break;
                                }
                            }
                        } else {
                            for (let k = stack.length; k--;) {
                                if (stack[k][index][1][1] < 0) {
                                    startValue = stack[k][index][1][1];
                                    break;
                                }
                            }
                        }

                        return [d[0], [startValue, startValue + (typeof newData[index][1] === 'number' ? newData[index][1] : 0)], newData[index]];
                    });
                } else {
                    stackData = map(newData, d => [d[0], [0, typeof d[1] === 'number' ? d[1] : 0], d]);
                }

                stack.push(stackData);
                model.lastStackIndex = model.stackIndex;
                model.stackIndex = len;
                model.stackData = stackData;
            }
        }, false);

        this.eachComponent('series', function (model) {
            let stackName = model._stackName;

            if (stackName) {
                model.lastStackTotal = model.stackTotal;
                model.stackTotal = stacks[stackName].length;
            }
        });
    }

    /**
     * 更新所有被标记为脏的model
     * 
     * @param {bool} force 是否强制更新
     */
    update(force) {
        if (this.global.isError()) return;

        let { _componentsMap: componentsMap } = this;

        if (force) {
            this.eachComponent(function (componentType, component) {
                component.__dirty = true;
            });
        }

        this.eachComponent(function (componentType, component) {
            if (component.isDirty() && !component.isPending) {
                updateDependencies(componentsMap, component.dependencies, component.type, component.index); // 先更新其依赖
                component.__update();
            }
        });
    }

    /**
     * 更新某个模model及依赖该model的model
     * 
     * @param {Object} model 
     * @param {Object}
     */
    dirty(model, action) {
        if (this.global.isError()) return;

        let { _componentsMap: componentsMap } = this;

        if (model) {
            if (model.type === 'data') { // 若是dataModel则直接更新数据
                dirtySupports(componentsMap, model, action, 'dataDirty'); // 标记所有依赖该dataModel的model的__dataDirty为脏
                this.updateData(model); // 更新该dataModel及依赖该dataModel的数据
            } else {
                model.isDataDirty() && model.updateData(); // TODO
                dirtySupports(componentsMap, model, action); // 标记所有依赖该model的__dirty为脏
            }

            // model继承自Series的特殊处理
            if (model instanceof Series && model.get('$axisIndex')) {
                let xAxisModel = model.getAxisModel('x');
                let yAxisModel = model.getAxisModel('y');
                let axisAction = {
                    type: dirtyActions.seriesWillUpdate,
                    payload: {
                        series: model,
                        action
                    }
                };

                !xAxisModel.isDirty() && xAxisModel.dirty(axisAction);
                yAxisModel && !yAxisModel.isDirty() && yAxisModel.dirty(axisAction);
            }

        }

        this.__dirty = true;
    }

    /**
     * 遍历所有component类型
     * 
     * @param {Function} callback 回调函数
     * @param {any} context 
     */
    eachComponentType(callback, context) {
        let { _componentsMap: componentsMap } = this;

        each(componentsMap, (components, componentType) => {
            callback.call(context, componentType, filterHideSeries(components));
        });
    }

    /**
     * 遍历所有component
     * 
     * @param {string} mainType 要遍历的类型,不传将直接遍历所有component
     * @param {Function} callback 回调函数
     * @param {boolean} filterHide 是否过滤隐藏的series
     * @param {any} context 
     */
    eachComponent(mainType, cb, filterHide, context) {
        let { _componentsMap: componentsMap } = this;

        if (typeof mainType === 'function') {
            context = filterHide;
            filterHide = typeof cb === 'undefined' ? true : cb;
            cb = mainType;
            each(componentsMap, (components, componentType) => {
                each(filterHide ? filterHideSeries(components) : components, (component, index) => {
                    cb.call(context, componentType, component, index);
                });
            });
        } else if (isString(mainType)) {
            typeof filterHide === 'undefined' && (filterHide = true);
            each(filterHide ? filterHideSeries(componentsMap[mainType]) : componentsMap[mainType], cb, context);
        }
    }

    /**
     * 通过组件类型和index获取model
     * 
     * @param {string} type 组件类型名称
     * @param {number|string} index 组件的index或id
     * @returns {Object} model
     */
    getComponentByIndex(type, index) {
        if (typeof index === 'string') {
            return this.getComponentById(type, index);
        } else {
            return this._componentsMap[type] && this._componentsMap[type][index];
        }
    }

    /**
     * 通过name获取model
     * 
     * @param {string} type 组件类型
     * @param {string} name 组件name
     * @param {boolean} [filterHide=false] 是否过滤隐藏的series
     * @returns {Object[]} model数组
     */
    getComponentByName(type, name, filterHide = false) {
        return this.getComponent(type, seriesModel => seriesModel.get('name') === name, filterHide);
    }

    /**
     * 通过id获取model
     * 
     * @param {string} type 组件类型
     * @param {string} id 组件id
     * @returns {Object[]} model数组
     */
    getComponentById(type, id) {
        return this.getComponent(type, seriesModel => seriesModel.get('id') === id, false)[0];
    }

    /**
     * 通过axis的index获取相应series model
     * 
     * @param {any} index axis的index
     * @param {boolean} [filterHide=true] 是否过滤隐藏的series
     * @returns {Object[]} series model数组
     */
    getSeriesByAxis(index, filterHide = true) {
        return filter(filterHide ? filterHideSeries(this._componentsMap.series) : this._componentsMap.series, item => {
            let $axisIndex = item.get('$axisIndex');

            if ($axisIndex) {
                for (let i = 0; i < $axisIndex.length; i++) {
                    let axisModel = this.getComponentByIndex('axis', $axisIndex[i]);

                    if (axisModel && axisModel.index === index) {
                        return true;
                    }
                }
            }
        });
    }

    /**
     * 获取model
     * 
     * @param {string} type 
     * @param {any} [func=m => m] 筛选函数
     * @param {boolean} [filterHide=true] 是否过滤隐藏的series
     * @returns {Object[]} model数组
     */
    getComponent(type, func = m => m, filterHide = true) {
        return filter(filterHide ? filterHideSeries(this._componentsMap[type]) : this._componentsMap[type], func);
    }

    /**
     * 通过name获取series
     * 
     * @param {string} name series的name
     * @param {boolean} [filterHide=false] 是否过滤隐藏的series
     * @returns {Object[]} series model数组
     */
    getSeriesByName(name, filterHide = false) {
        return this.getComponent('series', seriesModel => seriesModel.get('name') === name, filterHide);
    }

    /**
     * 获取格式化后的text
     * 
     * @param {Object} text 
     * @returns {Object} new text
     */
    getFormattedText(text) {
        let style = text.style ? text.style : text;
        let globalTextStyle = this.get('textStyle');

        (style.color && !style.fill) && (style.fill = style.color);

        each(globalTextStyle, function (value, key) {
            typeof style[key] === 'undefined' && (style[key] = value);
        });

        (style.color && !style.fill) && (style.fill = style.color);

        style = formatTextStyle(style);

        return text;
    }
}

function filterHideSeries(seriesModes) {
    return filter(seriesModes, (seriesModel) => !(seriesModel instanceof Series) || seriesModel.get('selected') !== false);
}

/**
 * 递归更新依赖
 * 
 * @param {any} componentsMap 
 * @param {any} dependencies 
 * @private
 */
function updateDependencies(componentsMap, dependencies, componentType, componentIndex, needUpdateData) {
    each(dependencies, function (index, type) {
        if (isArray(index)) {
            for (let i = index.length; i--;) {
                let component = componentsMap[type][index[i]];

                if (component && component.isDirty()) {
                    updateDependencies(componentsMap, component.dependencies, component.type, component.index, needUpdateData);
                    needUpdateData && component.updateData();
                    !component.isPending && component.__update();
                }
            }
        } else {
            let component = componentsMap[type][index];

            if (component.isDirty()) {
                updateDependencies(componentsMap, component.dependencies, component.type, component.index, needUpdateData);
                needUpdateData && component.updateData();
                !component.isPending && component.__update();
            }
        }
    })
}

/**
 * 递归设置被依赖model为dirty或dataDirty
 * 
 * @param {any} componentsMap 
 * @param {any} supports 
 * @private
 */
function dirtySupports(componentsMap, model, action, updateType = 'dirty') {
    let supports = model.supports;
    let _action = {
        type: dirtyActions.dependentWillUpdate,
        payload: {
            dependent: model,
            action: action
        }
    };

    each(supports, (indexs, type) => {
        for (let i = indexs.length; i--;) {
            let component = componentsMap[type][indexs[i]];

            if (component && !component[updateType === 'dirty' ? 'isDirty' : 'isDataDirty']()) { // 且未被标记为dirty
                component[updateType](_action) && dirtySupports(componentsMap, component, _action, updateType);
            }
        }
    })
}

/**
 * 递归更新被依赖model的数据
 * 
 * @param {any} componentsMap 
 * @param {any} supports 
 * @private
 */
function updateSupportsData(componentsMap, supports) {
    each(supports, (indexs, type) => {
        for (let i = indexs.length; i--;) {
            let component = componentsMap[type][indexs[i]];

            if (component && component.isDataDirty()) {
                if (component.type === 'data') {
                    component.__update();
                    updateSupportsData(componentsMap, component.supports);
                } else {
                    component.updateData();
                }
            }
        }
    })
}

export default GlobalModel;