/** @module D3Charts */

import * as zrender from 'zrender/src/zrender';
import View from './view/View';
import ChartView from './view/Chart';
import ComponentView from './view/Component';
import ComponentModel from './model/Component';
import SeriesModel from './model/Series';
import * as util from './util';
import log from './util/track';
import * as graphic from './util/graphic';
import * as symbol from './util/symbol';
import GlobalModel from './model/Global';
import Eventful from 'zrender/src/mixin/Eventful';
import * as colorTool from 'zrender/src/tool/color';
import env from 'zrender/src/core/env';
import * as d3Time from 'd3-time';
import * as action from './action/event';

let { isString, each, mixin, findIndex, isFunction, isArray } = util;
const PREFIX = '__';

let instances = {}; // init生成的实例
let idBase = new Date() - 0;

let event = new Eventful();

let stat = {};

function doStat(option) {
    each(option, function(item, name) {
        if (name === 'series') {
            if (SeriesModel.hasClass && SeriesModel.hasClass(name)) {
                if (isArray(item)) {
                    each(item, function(seriesItem) {
                        if (!stat[seriesItem.type]) {
                            stat[seriesItem.type] = 0;
                        }

                        stat[seriesItem.type]++;
                    });
                } else {
                    if (!stat[item.type]) {
                        stat[item.type] = 0;
                    }

                    stat[item.type]++;
                }
            }
        } else {
            if (ComponentModel.hasClass(name)) {
                if (!stat[name]) {
                    stat[name] = 0;
                }

                if (isArray(item)) {
                    stat[name] += item.length;
                } else {
                    stat[name]++;
                }
            }
        }
    });
}

function statToString() {
    let str = '';

    each(stat, function(value, name) {
        str += `${name}-${value}|`;
    });

    str = str.slice(0, -1);

    return str;
}

/**
 * Charts类
 *
 * @class Charts
 * @extends {Eventful}
 */
class Charts extends Eventful {
    id = PREFIX + idBase++;

    // view视图存储对象
    _viewMap = {};

    // 全局global model
    _model = undefined;

    // 是否开启编辑
    isEnableEdit = false;

    // 是否开启绘图
    isEnableSketch = false;

    constructor(dom, theme, opts = {}) {
        super(...arguments);

        this._dom = isString(dom) ? document.getElementById(dom) : dom;

        let zr = (this._zr = zrender.init(this._dom, opts));

        opts.backgroundColor && zr.setBackgroundColor(opts.backgroundColor);

        this._model = new GlobalModel(null, null, null, this);

        this._actionQueue = [];

        initEvents.call(this);

        // 每一帧
        zr.animation.on(
            'frame',
            function() {
                if (this.isError()) return;

                try {
                    let actionQueue = this._actionQueue;

                    // 事件在每帧进行调用
                    while (actionQueue.length) {
                        let action = actionQueue.shift();
                        this.trigger(action[0], action[1]);
                    }

                    if (this._model.isDirty()) {
                        // 根据是否为脏来进行更新
                        this.update.call(this);
                    }
                } catch (e) {
                    this.error();
                    throw e;
                }
            },
            this
        );
    }

    /**
     * 设置参数
     *
     * @param {Object} option 配置参数
     * @param {Object} [lazyUpdate=true] 配置参数
     * @returns this
     */
    setOption(option, lazyUpdate = true) {
        doStat(option);
        this.clear();
        this._model.setOption(option);
        this.toggleEdit(this.isEnableEdit);
        prepareView.call(this);
        this.__AFTER_SET_OPTION = true;

        if (!lazyUpdate) {
            this.update();
        }

        return this;
    }

    /**
     * 调用globalModel的更新对标记为脏的model进行更新,并对有更新的model对应的view重新进行渲染
     *
     * @param {boolean} [force] 是否强制更新
     */
    update(force) {
        this._model.__update(force);
        doRender.call(this);

        let backgroundColor = this._model.get('backgroundColor');
        backgroundColor && this._zr.setBackgroundColor(backgroundColor);
    }

    /**
     * 清除所有view、model以及事件
     *
     * @returns this
     */
    clear() {
        let zr = this._zr;

        // 清除所有model
        this._model.clear();

        // 删除所有视图
        each(this._viewMap, function(view) {
            view.remove();
            zr.remove(view.group);
        });
        this._viewMap = {};

        // 解绑全部事件
        this.off();

        this.error(false);

        return this;
    }

    /**
     * 标记错误状态
     *
     * @param {bool} [bool] true或false
     * @private
     */
    error(bool) {
        this._isError = typeof bool !== 'undefined' ? bool : true;
    }

    /**
     * 是否错误状态
     *
     * @returns {boolean} 是否是错误状态
     */
    isError() {
        return this._isError;
    }

    /**
     * Resize the canvas.
     * Should be invoked when container size is changed
     *
     * @param {Object} [opts]
     * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
     * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
     */
    resize(opts) {
        this._zr.resize(opts);
        this.update(true);
    }

    /**
     * 触发事件
     *
     * @param {Object} model 组件model
     * @param {string} type 事件名称
     * @param {any} payload
     * @param {boolean} [throttle=true]
     */
    dispatchAction(model, type, payload, throttle = true) {
        if (throttle) {
            let index = findIndex(this._actionQueue, function(ele) {
                return ele[0] === model.id && ele[1] === type;
            });

            index > -1 && this._actionQueue.splice(index, 1); // 过滤了之前的相同模块的相同事件,仅触发最新的事件
        }
        this._actionQueue.push([`${model.id}_${type}`, payload]);
    }

    /**
     * 注册事件
     *
     * @param {Object} model 组件model
     * @param {string} type 事件名称
     * @param {function} callback 回调函数
     * @param {any} context
     */
    registerAction(model, type, callback, context) {
        this.on(`${model.id}_${type}`, callback, context || model);
    }

    /**
     * Get container width
     *
     * @returns {number} width of container
     */
    getWidth() {
        return this._zr.getWidth();
    }

    /**
     * Get container height
     *
     * @returns {number} height of container
     */
    getHeight() {
        return this._zr.getHeight();
    }

    /**
     * 获取指定的model,或者globalModel(无参数时)
     *
     * @param {string} [type] 组件名称
     * @param {number|string|Function} [index] 对应组件的index、name或方法选取
     * @returns {Object} 返回指定的model
     */
    getModel(type, index) {
        if (arguments.length === 2) {
            if (typeof index === 'number') {
                return this._model.getComponentByIndex(type, index);
            } else if (isString(index)) {
                return this._model.getComponentById(type, index);
            } else if (isFunction(index)) {
                return this._model.getComponent(type, index, false);
            }
        } else if (arguments.length === 1) {
            return this._model._componentsMap[type];
        } else {
            return this._model;
        }
    }

    /**
     * 获取指定的dataModel
     *
     * @deprecated
     * @param {number} index dataModel的序数
     * @returns data model
     */
    getDataModel(index) {
        return this._model.getComponentByIndex('data', index);
    }

    /**
     * 获取zr实例
     *
     * @returns instance of zr
     */
    getZr() {
        return this._zr;
    }

    /**
     * 获取dom
     *
     * @returns dom
     */
    getDom() {
        return this._dom;
    }

    /**
     * 获取model对应的view
     *
     * @param {Object} componentModel 组件model
     * @returns {Object} 返回model对应的view
     */
    getViewOfComponentModel(componentModel) {
        return this._viewMap[componentModel.id];
    }

    hideSeries(index) {
        let seriesModel = this._model.getComponentByIndex('series', index);

        if (seriesModel) {
            seriesModel.set('selected', false);
            seriesModel.__dirty = false;
            this._viewMap[seriesModel.id].remove();
        }
    }

    showSeries(index) {
        let seriesModel = this._model.getComponentByIndex('series', index);

        if (seriesModel) {
            seriesModel.set('selected', true);
        }
    }

    getRenderedCanvas(opts) {
        if (!env.canvasSupported) {
            return;
        }
        opts = opts || {};
        opts.pixelRatio = opts.pixelRatio || 1;
        opts.backgroundColor = opts.backgroundColor;
        this.stopAnimation();

        return this._zr.painter.getRenderedCanvas(opts);
    }

    /**
     * 停止所有动画
     */
    stopAnimation() {
        let zr = this._zr;
        let list = zr.storage.getDisplayList();
        // Stop animations
        each(list, function(el) {
            el.stopAnimation(true);
        });
    }

    /**
     * 生成dataUrl
     *
     * @param {Object} [opts]
     * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
     * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
     * @param {string} [opts.type] jpg | png
     * @param {string} [opts.backgroundColor]
     * @returns data url
     */
    getDataUrl(opts) {
        let lastSize = { width: this.getWidth(), height: this.getHeight() };

        opts = opts || {};

        if (opts.size) {
            this.resize(opts.size);
            this.stopAnimation();
        }

        let url = this.getRenderedCanvas(opts).toDataURL(
            'image/' + ((opts && opts.type) || 'png')
        );

        if (opts.size) {
            this.resize(lastSize);
            this.stopAnimation();
        }

        return url;
    }

    /**
     * 切换编辑状态
     *
     * @param {boolean} [bool] true或false
     */
    toggleEdit(bool) {
        bool === undefined && (bool = !this.isEnableEdit);

        this._model.eachComponent(function(name, model) {
            model.set('enableEdit', bool);
        });

        this.isEnableEdit = bool;
    }
}

// let updateMethods = {
//     update: function () {
//         this._model.__update();
//         doRender.call(this);
//     },

//     prepareAndUpdate: function () {
//         prepareView.call(this);

//         updateMethods.update.call(this);
//     }
// }

function prepareOneView(model) {
    let { _viewMap: viewMap, _model: globalModel, _zr: zr } = this;
    let _View, view;

    _View = View.getClass(model.type);

    if (_View) {
        view = new _View(model, globalModel, this);
        view.type = model.type;
        viewMap[model.id] = view;
        zr.add(view.group);
    }

    return view;
}

// 准备视图
function prepareView() {
    this._model.eachComponent((componentType, model) => {
        prepareOneView.call(this, model);
    });
}

// 进行渲染
function doRender() {
    let { _viewMap: viewMap, _model: globalModel } = this;

    globalModel.eachComponent((componentType, model) => {
        let view = viewMap[model.id];

        if (!view) {
            view = prepareOneView.call(this, model);
        }

        if (view) {
            view.__render(model, globalModel, this);
            updateZ(model, view);
        }
    });

    if (this.__AFTER_SET_OPTION) {
        this.__AFTER_SET_OPTION = false;

        this.trigger('RENDER_END_AFTER_SET_OPTION');
        this.trigger('VIEW_DID_RENDER_AFTER_SET_OPTION');
    }

    this.trigger('RENDER_END');
    this.trigger('VIEW_DID_RENDER');
}

// 统一更新组件的z和zlevel
function updateZ(model, view) {
    var z = model.get('z');
    var zlevel = model.get('zlevel');

    view.group.traverse(function(el) {
        if (el.type !== 'group') {
            !isNaN(z) && (el.z = z);
            !isNaN(zlevel) && (el.zlevel = zlevel);
        }
    });
}

const MOUSE_EVENT_NAMES = [
    'click',
    'dblclick',
    'mouseover',
    'mouseout',
    'mousemove',
    'mousedown',
    'mouseup',
    'globalout',
    'mousewheel',
    'pinch'
];

// 代理zrender事件
function initEvents() {
    each(MOUSE_EVENT_NAMES, eveName => {
        this._zr.on(eveName, e => {
            this.trigger(eveName, e);
        });
    });
}

/**
 * 初始化Charts
 *
 * @param {string|dom} dom 接收dom的id或者dom
 * @returns {Object} 返回{@link module:D3Charts~Charts}实例
 */
function init(dom, opts) {
    let chart = new Charts(dom, undefined, opts);

    instances[chart.id] = chart;

    return chart;
}

/**
 * 注册Model
 *
 * @param {Object} Model 要注册的Model
 */
function registerModel(Model_) {
    ComponentModel.registerClass(Model_, Model_.type);
}

/**
 * 注册view
 *
 * @param {Object} View Model对应的View
 */
function registerView(View_) {
    View.registerClass(View_, View_.type);
}

/**
 * 注册Model及View
 *
 * @param {Object} Model 要注册的Model
 * @param {Object} View Model对应的View
 */
function registerComponent(Model_, View_) {
    Model_ && registerModel(Model_);
    View_ && registerView(View_);

    if (!Model_ && View_) {
        registerModel(
            class extends ComponentModel {
                static type = View_.type;
            }
        );
    }
}

function initMap(dom) {
    var map = document.createElement('div');
    map.id = dom;
    map.width = '100%';
    map.height = '600px';
    return map;
}

let tracking = true;

/**
 * 是否开启统计
 *
 * @param {boolean} bool true或false
 */
function track(bool) {
    tracking = bool;
}

/**
 * version of d3-charts
 *
 * @static
 * @constant
 */
const VERSION = '0.3.4';

setTimeout(function() {
    if (tracking) {
        log({
            _sid: 'iwencai_datav_d3charts.load',
            id: 'iwencai_datav_d3charts.load',
            version: VERSION,
            widgetN: statToString(),
            fid: 'iwencai_datav'
        });
    }
}, 3e3);

export {
    VERSION,
    init,
    track,
    event,
    initMap,
    registerModel,
    registerView,
    registerComponent,
    /**
     * 绘图引擎zrender
     *
     * @see https://ecomfe.github.io/zrender-doc/public/
     */
    zrender,
    /**
     * 工具函数
     *
     * @see module:util/index
     */
    util,
    /**
     * 图形相关函数
     *
     * @see module:util/graphic
     */
    graphic,
    colorTool,
    symbol,
    /**
     * @see ChartView
     */
    ChartView,
    /**
     * @see ComponentView
     */
    ComponentView,
    d3Time,
    /**
     * @see SeriesModel
     */
    SeriesModel as ChartModel,
    /**
     * @see ComponentModel
     */
    ComponentModel,
    action
};