define(function(require) {

    var SeriesModel = require('../../model/Series');
    var Tree = require('../../data/Tree');
    var zrUtil = require('zrender/core/util');
    var Model = require('../../model/Model');
    var formatUtil = require('../../util/format');
    var encodeHTML = formatUtil.encodeHTML;
    var addCommas = formatUtil.addCommas;


    return SeriesModel.extend({

        type: 'series.treemap',

        dependencies: ['grid', 'polar'],

        /**
         * @type {module:echarts/data/Tree~Node}
         */
        _viewRoot: null,

        defaultOption: {
            // Disable progressive rendering
            progressive: 0,
            hoverLayerThreshold: Infinity,
            // center: ['50%', '50%'],          // not supported in ec3.
            // size: ['80%', '80%'],            // deprecated, compatible with ec2.
            left: 'center',
            top: 'middle',
            right: null,
            bottom: null,
            width: '80%',
            height: '80%',
            sort: true,                         // Can be null or false or true
                                                // (order by desc default, asc not supported yet (strange effect))
            clipWindow: 'origin',               // Size of clipped window when zooming. 'origin' or 'fullscreen'
            squareRatio: 0.5 * (1 + Math.sqrt(5)), // golden ratio
            leafDepth: null,                    // Nodes on depth from root are regarded as leaves.
                                                // Count from zero (zero represents only view root).
            drillDownIcon: '▶',                 // Use html character temporarily because it is complicated
                                                // to align specialized icon. ▷▶❒❐▼✚
            visualDimension: 0,                 // Can be 0, 1, 2, 3.
            zoomToNodeRatio: 0.32 * 0.32,       // Be effective when using zoomToNode. Specify the proportion of the
                                                // target node area in the view area.
            roam: true,                         // true, false, 'scale' or 'zoom', 'move'.
            nodeClick: 'zoomToNode',            // Leaf node click behaviour: 'zoomToNode', 'link', false.
                                                // If leafDepth is set and clicking a node which has children but
                                                // be on left depth, the behaviour would be changing root. Otherwise
                                                // use behavious defined above.
            animation: true,
            animationDurationUpdate: 900,
            animationEasing: 'quinticInOut',
            breadcrumb: {
                show: true,
                height: 22,
                left: 'center',
                top: 'bottom',
                // right
                // bottom
                emptyItemWidth: 25,             // Width of empty node.
                itemStyle: {
                    normal: {
                        color: 'rgba(0,0,0,0.7)', //'#5793f3',
                        borderColor: 'rgba(255,255,255,0.7)',
                        borderWidth: 1,
                        shadowColor: 'rgba(150,150,150,1)',
                        shadowBlur: 3,
                        shadowOffsetX: 0,
                        shadowOffsetY: 0,
                        textStyle: {
                            color: '#fff'
                        }
                    },
                    emphasis: {
                        textStyle: {}
                    }
                }
            },
            label: {
                normal: {
                    show: true,
                    position: 'inside', // Can be [5, '5%'] or position stirng like 'insideTopLeft', ...
                    textStyle: {
                        color: '#fff',
                        ellipsis: true
                    }
                }
            },
            itemStyle: {
                normal: {
                    color: null,            // Can be 'none' if not necessary.
                    colorAlpha: null,       // Can be 'none' if not necessary.
                    colorSaturation: null,  // Can be 'none' if not necessary.
                    borderWidth: 0,
                    gapWidth: 0,
                    borderColor: '#fff',
                    borderColorSaturation: null // If specified, borderColor will be ineffective, and the
                                                // border color is evaluated by color of current node and
                                                // borderColorSaturation.
                },
                emphasis: {

                }
            },
            color: [],                  // + treemapSeries.color should not be modified. Please only modified
                                        // level[n].color (if necessary).
                                        // + Specify color list of each level. level[0].color would be global
                                        // color list if not specified. (see method `setDefault`).
                                        // + But set as a empty array to forbid fetch color from global palette
                                        // when using nodeModel.get('color'), otherwise nodes on deep level
                                        // will always has color palette set and are not able to inherit color
                                        // from parent node.
                                        // + TreemapSeries.color can not be set as 'none', otherwise effect
                                        // legend color fetching (see seriesColor.js).
            colorAlpha: null,           // Array. Specify color alpha range of each level, like [0.2, 0.8]
            colorSaturation: null,      // Array. Specify color saturation of each level, like [0.2, 0.5]
            colorMappingBy: 'index',    // 'value' or 'index' or 'id'.
            visibleMin: 10,             // If area less than this threshold (unit: pixel^2), node will not
                                        // be rendered. Only works when sort is 'asc' or 'desc'.
            childrenVisibleMin: null,   // If area of a node less than this threshold (unit: pixel^2),
                                        // grandchildren will not show.
                                        // Why grandchildren? If not grandchildren but children,
                                        // some siblings show children and some not,
                                        // the appearance may be mess and not consistent,
            levels: []                  // Each item: {
                                        //     visibleMin, itemStyle, visualDimension, label
                                        // }
            // data: {
            //      value: [],
            //      children: [],
            //      link: 'http://xxx.xxx.xxx',
            //      target: 'blank' or 'self'
            // }
        },

        /**
         * @override
         */
        getInitialData: function (option, ecModel) {
            var data = option.data || [];
            var rootName = option.name;
            rootName == null && (rootName = option.name);

            // Create a virtual root.
            var root = {name: rootName, children: option.data};
            var value0 = (data[0] || {}).value;

            completeTreeValue(root, zrUtil.isArray(value0) ? value0.length : -1);

            // FIXME
            // sereis.mergeOption 的 getInitData是否放在merge后，从而能直接获取merege后的结果而非手动判断。
            var levels = option.levels || [];

            levels = option.levels = setDefault(levels, ecModel);

            // Make sure always a new tree is created when setOption,
            // in TreemapView, we check whether oldTree === newTree
            // to choose mappings approach among old shapes and new shapes.
            return Tree.createTree(root, this, levels).data;
        },

        optionUpdated: function () {
            this.resetViewRoot();
        },

        /**
         * @override
         * @param {number} dataIndex
         * @param {boolean} [mutipleSeries=false]
         */
        formatTooltip: function (dataIndex) {
            var data = this.getData();
            var value = this.getRawValue(dataIndex);
            var formattedValue = zrUtil.isArray(value)
                ? addCommas(value[0]) : addCommas(value);
            var name = data.getName(dataIndex);

            return encodeHTML(name) + ': ' + formattedValue;
        },

        /**
         * Add tree path to tooltip param
         *
         * @override
         * @param {number} dataIndex
         * @return {Object}
         */
        getDataParams: function (dataIndex) {
            var params = SeriesModel.prototype.getDataParams.apply(this, arguments);

            var data = this.getData();
            var node = data.tree.getNodeByDataIndex(dataIndex);
            var treePathInfo = params.treePathInfo = [];

            while (node) {
                var nodeDataIndex = node.dataIndex;
                treePathInfo.push({
                    name: node.name,
                    dataIndex: nodeDataIndex,
                    value: this.getRawValue(nodeDataIndex)
                });
                node = node.parentNode;
            }

            treePathInfo.reverse();

            return params;
        },

        /**
         * @public
         * @param {Object} layoutInfo {
         *                                x: containerGroup x
         *                                y: containerGroup y
         *                                width: containerGroup width
         *                                height: containerGroup height
         *                            }
         */
        setLayoutInfo: function (layoutInfo) {
            /**
             * @readOnly
             * @type {Object}
             */
            this.layoutInfo = this.layoutInfo || {};
            zrUtil.extend(this.layoutInfo, layoutInfo);
        },

        /**
         * @param  {string} id
         * @return {number} index
         */
        mapIdToIndex: function (id) {
            // A feature is implemented:
            // index is monotone increasing with the sequence of
            // input id at the first time.
            // This feature can make sure that each data item and its
            // mapped color have the same index between data list and
            // color list at the beginning, which is useful for user
            // to adjust data-color mapping.

            /**
             * @private
             * @type {Object}
             */
            var idIndexMap = this._idIndexMap;

            if (!idIndexMap) {
                idIndexMap = this._idIndexMap = {};
                /**
                 * @private
                 * @type {number}
                 */
                this._idIndexMapCount = 0;
            }

            var index = idIndexMap[id];
            if (index == null) {
                idIndexMap[id] = index = this._idIndexMapCount++;
            }

            return index;
        },

        getViewRoot: function () {
            return this._viewRoot;
        },

        /**
         * @param {module:echarts/data/Tree~Node} [viewRoot]
         */
        resetViewRoot: function (viewRoot) {
            viewRoot
                ? (this._viewRoot = viewRoot)
                : (viewRoot = this._viewRoot);

            var root = this.getData().tree.root;

            if (!viewRoot
                || (viewRoot !== root && !root.contains(viewRoot))
            ) {
                this._viewRoot = root;
            }
        }
    });

    /**
     * @param {Object} dataNode
     */
    function completeTreeValue(dataNode, arrValueLength) {
        // Postorder travel tree.
        // If value of none-leaf node is not set,
        // calculate it by suming up the value of all children.
        var sum = 0;

        zrUtil.each(dataNode.children, function (child) {

            completeTreeValue(child, arrValueLength);

            var childValue = child.value;
            zrUtil.isArray(childValue) && (childValue = childValue[0]);

            sum += childValue;
        });

        var thisValue = dataNode.value;

        if (arrValueLength >= 0) {
            if (!zrUtil.isArray(thisValue)) {
                dataNode.value = new Array(arrValueLength);
            }
            else {
                thisValue = thisValue[0];
            }
        }

        if (thisValue == null || isNaN(thisValue)) {
            thisValue = sum;
        }
        // Value should not less than 0.
        if (thisValue < 0) {
            thisValue = 0;
        }

        arrValueLength >= 0
            ? (dataNode.value[0] = thisValue)
            : (dataNode.value = thisValue);
    }

    /**
     * set default to level configuration
     */
    function setDefault(levels, ecModel) {
        var globalColorList = ecModel.get('color');

        if (!globalColorList) {
            return;
        }

        levels = levels || [];
        var hasColorDefine;
        zrUtil.each(levels, function (levelDefine) {
            var model = new Model(levelDefine);
            var modelColor = model.get('color');

            if (model.get('itemStyle.normal.color')
                || (modelColor && modelColor !== 'none')
            ) {
                hasColorDefine = true;
            }
        });

        if (!hasColorDefine) {
            var level0 = levels[0] || (levels[0] = {});
            level0.color = globalColorList.slice();
        }

        return levels;
    }

});