jui.define("util.treemap", [], function() {

    return {
        sumArray: function (arr) {
            var sum = 0;

            for (var i = 0; i < arr.length; i++) {
                sum += arr[i];
            }

            return sum;
        }
    }
});

jui.define("chart.brush.treemap.node", [], function() {

    /**
     * @class chart.brush.treemap.node
     *
     */
    var Node = function(data) {
        var self = this;

        this.text = data.text;
        this.value = data.value;
        this.x = data.x;
        this.y = data.y;
        this.width = data.width;
        this.height = data.height;

        /** @property {Integer} [index=null] Index of a specified node */
        this.index = null;

        /** @property {Integer} [nodenum=null] Unique number of a specifiede node at the current depth */
        this.nodenum = null;

        /** @property {ui.tree.node} [parent=null] Variable that refers to the parent of the current node */
        this.parent = null;

        /** @property {Array} [children=null] List of child nodes of a specified node */
        this.children = [];

        /** @property {Integer} [depth=0] Depth of a specified node */
        this.depth = 0;

        function setIndexChild(node) {
            var clist = node.children;

            for(var i = 0; i < clist.length; i++) {
                if(clist[i].children.length > 0) {
                    setIndexChild(clist[i]);
                }
            }
        }

        this.reload = function(nodenum) {
            this.nodenum = (!isNaN(nodenum)) ? nodenum : this.nodenum;

            if(self.parent) {
                if(this.parent.index == null) this.index = "" + this.nodenum;
                else this.index = self.parent.index + "." + this.nodenum;
            }

            // 뎁스 체크
            if(this.parent && typeof(self.index) == "string") {
                this.depth = this.index.split(".").length;
            }

            // 자식 인덱스 체크
            if(this.children.length > 0) {
                setIndexChild(this);
            }
        }

        this.isLeaf = function() {
            return (this.children.length == 0) ? true : false;
        }

        this.appendChild = function(node) {
            this.children.push(node);
        }

        this.insertChild = function(nodenum, node) {
            var preNodes = this.children.splice(0, nodenum);
            preNodes.push(node);

            this.children = preNodes.concat(this.children);
        }

        this.removeChild = function(index) {
            for(var i = 0; i < this.children.length; i++) {
                var node = this.children[i];

                if(node.index == index) {
                    this.children.splice(i, 1); // 배열에서 제거
                }
            }
        }

        this.lastChild = function() {
            if(this.children.length > 0)
                return this.children[this.children.length - 1];

            return null;
        }

        this.lastChildLeaf = function(lastRow) {
            var row = (!lastRow) ? this.lastChild() : lastRow;

            if(row.isLeaf()) return row;
            else {
                return this.lastChildLeaf(row.lastChild());
            }
        }
    }

    return Node;
});

jui.define("chart.brush.treemap.nodemanager", [ "util.base", "chart.brush.treemap.node" ], function(_, Node) {
    
   var NodeManager = function() {
       var self = this,
           root = new Node({
               text: null,
               value: -1,
               x: -1,
               y: -1,
               width: -1,
               height: -1
           }),
           iParser = _.index();

       function createNode(data, no, pNode) {
           var node = new Node(data);

           node.parent = (pNode) ? pNode : null;
           node.reload(no);

           return node;
       }

       function setNodeChildAll(dataList, node) {
           var c_nodes = node.children;

           if(c_nodes.length > 0) {
               for(var i = 0; i < c_nodes.length; i++) {
                   dataList.push(c_nodes[i]);

                   if(c_nodes[i].children.length > 0) {
                       setNodeChildAll(dataList, c_nodes[i]);
                   }
               }
           }
       }

       function getNodeChildLeaf(keys, node) {
           if(!node) return null;
           var tmpKey = keys.shift();

           if(tmpKey == undefined) {
               return node;
           } else {
               return getNodeChildLeaf(keys, node.children[tmpKey]);
           }
       }

       function insertNodeDataChild(index, data) {
           var keys = iParser.getIndexList(index);

           var pNode = self.getNodeParent(index),
               nodenum = keys[keys.length - 1],
               node = createNode(data, nodenum, pNode);

           // 데이터 갱신
           pNode.insertChild(nodenum, node);

           return node;
       }

       function appendNodeData(data) {
           var node = createNode(data, root.children.length, root);
           root.appendChild(node);

           return node;
       }

       function appendNodeDataChild(index, data) {
           var pNode = self.getNode(index),
               cNode = createNode(data, pNode.children.length, pNode);

           pNode.appendChild(cNode);

           return cNode;
       }

       this.appendNode = function() {
           var index = arguments[0],
               data = arguments[1];

           if(!data) {
               return appendNodeData(index);
           } else {
               return appendNodeDataChild(index, data);
           }
       }

       this.insertNode = function(index, data) {
           if(root.children.length == 0 && parseInt(index) == 0) {
               return this.appendNode(data);
           } else {
               return insertNodeDataChild(index, data);
           }
       }

       this.updateNode = function(index, data) {
           var node = this.getNode(index);

           for(var key in data) {
               node.data[key] = data[key];
           }

           node.reload(node.nodenum, true);

           return node;
       }

       this.getNode = function(index) {
           if(index == null) return root.children;
           else {
               var nodes = root.children;

               if(iParser.isIndexDepth(index)) {
                   var keys = iParser.getIndexList(index);
                   return getNodeChildLeaf(keys, nodes[keys.shift()]);
               } else {
                   return (nodes[index]) ? nodes[index] : null;
               }
           }
       }

       this.getNodeAll = function(index) {
           var dataList = [],
               tmpNodes = (index == null) ? root.children : [ this.getNode(index) ];

           for(var i = 0; i < tmpNodes.length; i++) {
               if(tmpNodes[i]) {
                   dataList.push(tmpNodes[i]);

                   if(tmpNodes[i].children.length > 0) {
                       setNodeChildAll(dataList, tmpNodes[i]);
                   }
               }
           }

           return dataList;
       }

       this.getNodeParent = function(index) { // 해당 인덱스의 부모 노드를 가져옴 (단, 해당 인덱스의 노드가 없을 경우)
           var keys = iParser.getIndexList(index);

           if(keys.length == 1) {
               return root;
           } else if(keys.length == 2) {
               return this.getNode(keys[0]);
           } else if(keys.length > 2) {
               keys.pop();
               return this.getNode(keys.join("."));
           }
       }

       this.getRoot = function() {
           return root;
       }
   }

   return NodeManager;
});

jui.define("chart.brush.treemap.container", [ "util.treemap" ], function(util) {

    var Container = function(xoffset, yoffset, width, height) {
        this.xoffset = xoffset; // offset from the the top left hand corner
        this.yoffset = yoffset; // ditto
        this.height = height;
        this.width = width;

        this.shortestEdge = function () {
            return Math.min(this.height, this.width);
        };

        // getCoordinates - for a row of boxes which we've placed
        //                  return an array of their cartesian coordinates
        this.getCoordinates = function (row) {
            var coordinates = [],
                subxoffset = this.xoffset, subyoffset = this.yoffset, //our offset within the container
                areawidth = util.sumArray(row) / this.height,
                areaheight = util.sumArray(row) / this.width;

            if (this.width >= this.height) {
                for (var i = 0; i < row.length; i++) {
                    coordinates.push([ subxoffset, subyoffset, subxoffset + areawidth, subyoffset + row[i] / areawidth ]);
                    subyoffset = subyoffset + row[i] / areawidth;
                }
            } else {
                for (var i = 0; i < row.length; i++) {
                    coordinates.push([ subxoffset, subyoffset, subxoffset + row[i] / areaheight, subyoffset + areaheight ]);
                    subxoffset = subxoffset + row[i] / areaheight;
                }
            }

            return coordinates;
        }

        // cutArea - once we've placed some boxes into an row we then need to identify the remaining area,
        //           this function takes the area of the boxes we've placed and calculates the location and
        //           dimensions of the remaining space and returns a container box defined by the remaining area
        this.cutArea = function (area) {
            if (this.width >= this.height) {
                var areawidth = area / this.height,
                    newwidth = this.width - areawidth;

                return new Container(this.xoffset + areawidth, this.yoffset, newwidth, this.height);
            } else {
                var areaheight = area / this.width,
                    newheight = this.height - areaheight;

                return new Container(this.xoffset, this.yoffset + areaheight, this.width, newheight);
            }
        }
    }

    return Container;
});

jui.define("chart.brush.treemap.calculator", [ "util.base", "util.treemap", "chart.brush.treemap.container" ], function(_, util, Container) {

    // normalize - the Bruls algorithm assumes we're passing in areas that nicely fit into our
    //             container box, this method takes our raw data and normalizes the data values into
    //             area values so that this assumption is valid.
    function normalize(data, area) {
        var normalizeddata = [],
            sum = util.sumArray(data),
            multiplier = area / sum;

        for (var i = 0; i < data.length; i++) {
            normalizeddata[i] = data[i] * multiplier;
        }

        return normalizeddata;
    }

    // treemapMultidimensional - takes multidimensional data (aka [[23,11],[11,32]] - nested array)
    //                           and recursively calls itself using treemapSingledimensional
    //                           to create a patchwork of treemaps and merge them
    function treemapMultidimensional(data, width, height, xoffset, yoffset) {
        xoffset = (typeof xoffset === "undefined") ? 0 : xoffset;
        yoffset = (typeof yoffset === "undefined") ? 0 : yoffset;

        var mergeddata = [],
            mergedtreemap,
            results = [];

        if(_.typeCheck("array", data[0])) { // if we've got more dimensions of depth
            for(var i = 0; i < data.length; i++) {
                mergeddata[i] = sumMultidimensionalArray(data[i]);
            }

            mergedtreemap = treemapSingledimensional(mergeddata, width, height, xoffset, yoffset);

            for(var i = 0; i < data.length; i++) {
                results.push(treemapMultidimensional(data[i], mergedtreemap[i][2] - mergedtreemap[i][0], mergedtreemap[i][3] - mergedtreemap[i][1], mergedtreemap[i][0], mergedtreemap[i][1]));
            }
        } else {
            results = treemapSingledimensional(data,width,height, xoffset, yoffset);
        }
        return results;
    }

    // treemapSingledimensional - simple wrapper around squarify
    function treemapSingledimensional(data, width, height, xoffset, yoffset) {
        xoffset = (typeof xoffset === "undefined") ? 0 : xoffset;
        yoffset = (typeof yoffset === "undefined") ? 0 : yoffset;

        //console.log(normalize(data, width * height))
        var rawtreemap = squarify(normalize(data, width * height), [], new Container(xoffset, yoffset, width, height), []);
        return flattenTreemap(rawtreemap);
    }

    // flattenTreemap - squarify implementation returns an array of arrays of coordinates
    //                  because we have a new array everytime we switch to building a new row
    //                  this converts it into an array of coordinates.
    function flattenTreemap(rawtreemap) {
        var flattreemap = [];

        if(rawtreemap) {
            for (var i = 0; i < rawtreemap.length; i++) {
                for (var j = 0; j < rawtreemap[i].length; j++) {
                    flattreemap.push(rawtreemap[i][j]);
                }

            }
        }

        return flattreemap;
    }

    // squarify  - as per the Bruls paper
    //             plus coordinates stack and containers so we get
    //             usable data out of it
    function squarify(data, currentrow, container, stack) {
        var length;
        var nextdatapoint;
        var newcontainer;

        if (data.length === 0) {
            stack.push(container.getCoordinates(currentrow));
            return;
        }

        length = container.shortestEdge();
        nextdatapoint = data[0];

        if (improvesRatio(currentrow, nextdatapoint, length)) {
            currentrow.push(nextdatapoint);
            squarify(data.slice(1), currentrow, container, stack);
        } else {
            newcontainer = container.cutArea(util.sumArray(currentrow), stack);
            stack.push(container.getCoordinates(currentrow));
            squarify(data, [], newcontainer, stack);
        }
        return stack;
    }

    // improveRatio - implements the worse calculation and comparision as given in Bruls
    //                (note the error in the original paper; fixed here)
    function improvesRatio(currentrow, nextnode, length) {
        var newrow;

        if (currentrow.length === 0) {
            return true;
        }

        newrow = currentrow.slice();
        newrow.push(nextnode);

        var currentratio = calculateRatio(currentrow, length),
            newratio = calculateRatio(newrow, length);

        // the pseudocode in the Bruls paper has the direction of the comparison
        // wrong, this is the correct one.
        return currentratio >= newratio;
    }

    // calculateRatio - calculates the maximum width to height ratio of the
    //                  boxes in this row
    function calculateRatio(row, length) {
        var min = Math.min.apply(Math, row),
            max = Math.max.apply(Math, row),
            sum = util.sumArray(row);

        return Math.max(Math.pow(length, 2) * max / Math.pow(sum, 2), Math.pow(sum, 2) / (Math.pow(length, 2) * min));
    }

    // sumMultidimensionalArray - sums the values in a nested array (aka [[0,1],[[2,3]]])
    function sumMultidimensionalArray(arr) {
        var total = 0;

        if(_.typeCheck("array", arr[0])) {
            for(var i = 0; i < arr.length; i++) {
                total += sumMultidimensionalArray(arr[i]);
            }
        } else {
            total = util.sumArray(arr);
        }

        return total;
    }

    return treemapMultidimensional;
});

jui.define("chart.brush.treemap", [ "util.base", "chart.brush.treemap.calculator", "chart.brush.treemap.nodemanager" ],
    function(_, Calculator, NodeManager) {

    var TEXT_MARGIN_LEFT = 3;

    /**
     * @class chart.brush.treemap
     *
     * @extends chart.brush.core
     */
    var TreemapBrush = function() {
        var nodes = new NodeManager(),
            titleKeys = {};

        function convertNodeToArray(key, nodes, result, now) {
            if(!now) now = [];

            for(var i = 0; i < nodes.length; i++) {
                if(nodes[i].children.length == 0) {
                    now.push(nodes[i][key]);
                } else {
                    convertNodeToArray(key, nodes[i].children, result, []);
                }
            }

            result.push(now);
            return result;
        }

        function mergeArrayToNode(keys, values) {
            for(var i = 0; i < keys.length; i++) {
                if(_.typeCheck("array", keys[i])) {
                    mergeArrayToNode(keys[i], values[i]);
                } else {
                    var node = nodes.getNode(keys[i]);
                    node.x = values[i][0];
                    node.y = values[i][1];
                    node.width = values[i][2] - values[i][0];
                    node.height = values[i][3] - values[i][1];
                }
            }
        }

        function isDrawNode(node) {
            if(node.width == 0 && node.height == 0 && node.x == 0 && node.y == 0) {
                return false;
            }

            return true;
        }

        function getMinimumXY(node, dx, dy) {
            if(node.children.length == 0) {
                return {
                    x: Math.min(dx, node.x),
                    y: Math.min(dy, node.y)
                };
            } else {
                for(var i = 0; i < node.children.length; i++) {
                    return getMinimumXY(node.children[i], dx, dy);
                }
            }
        }

        function createTitleDepth(self, g, node, sx, sy) {
            var fontSize = self.chart.theme("treemapTitleFontSize"),
                w = self.axis.area("width"),
                h = self.axis.area("height"),
                xy = getMinimumXY(node, w, h);

            var text = self.chart.text({
                "font-size": fontSize,
                "font-weight": "bold",
                fill: self.chart.theme("treemapTitleFontColor"),
                x: sx + xy.x + TEXT_MARGIN_LEFT,
                y: sy + xy.y + fontSize,
                "text-anchor": "start"
            }, (_.typeCheck("function", self.brush.format) ? self.format(node) : node.text));

            g.append(text);
            titleKeys[node.index] = true;
        }

        function getRootNodeSeq(node) {
            if(node.parent.depth > 0) {
                return getRootNodeSeq(node.parent);
            }

            return node.nodenum;
        }

        this.drawBefore = function() {
            for(var i = 0; i < this.axis.data.length; i++) {
                var d = this.axis.data[i],
                    k = this.getValue(d, "index");

                nodes.insertNode(k, {
                    text: this.getValue(d, "text", ""),
                    value: this.getValue(d, "value", 0),
                    x: this.getValue(d, "x", 0),
                    y: this.getValue(d, "y", 0),
                    width: this.getValue(d, "width", 0),
                    height: this.getValue(d, "height", 0)
                });
            }

            var nodeList = nodes.getNode(),
                preData = convertNodeToArray("value", nodeList, []),
                preKeys = convertNodeToArray("index", nodeList, []),
                afterData = Calculator(preData, this.axis.area("width"), this.axis.area("height"));

            mergeArrayToNode(preKeys, afterData);
        }

        this.draw = function() {
            var g = this.svg.group(),
                sx = this.axis.area("x"),
                sy = this.axis.area("y"),
                nodeList = nodes.getNodeAll();

            for(var i = 0; i < nodeList.length; i++) {
                if(this.brush.titleDepth == nodeList[i].depth) {
                    createTitleDepth(this, g, nodeList[i], sx, sy);
                }

                if(!isDrawNode(nodeList[i])) continue;

                var x = sx + nodeList[i].x,
                    y = sy + nodeList[i].y,
                    w = nodeList[i].width,
                    h = nodeList[i].height;

                if(this.brush.showText && !titleKeys[nodeList[i].index]) {
                    var cx = x + (w / 2),
                        cy = y + (h / 2),
                        fontSize = this.chart.theme("treemapTextFontSize");

                    if(this.brush.textOrient == "top") {
                        cy = y + fontSize;
                    } else if(this.brush.textOrient == "bottom") {
                        cy = y + h - fontSize/2;
                    }

                    if(this.brush.textAlign == "start") {
                        cx = x + TEXT_MARGIN_LEFT;
                    } else if(this.brush.textAlign == "end") {
                        cx = x + w - TEXT_MARGIN_LEFT;
                    }

                    var text = this.chart.text({
                        "font-size": fontSize,
                        fill: this.chart.theme("treemapTextFontColor"),
                        x: cx,
                        y: cy,
                        "text-anchor": this.brush.textAlign
                    }, (_.typeCheck("function", this.brush.format) ? this.format(nodeList[i]) : nodeList[i].text));

                    g.append(text);
                }

                var elem = this.svg.rect({
                    stroke: this.chart.theme("treemapNodeBorderColor"),
                    "stroke-width": this.chart.theme("treemapNodeBorderWidth"),
                    x: x,
                    y: y,
                    width: w,
                    height: h,
                    fill: this.color(getRootNodeSeq(nodeList[i]))
                });

                if(_.typeCheck("function", this.brush.nodeColor)) {
                    var color = this.brush.nodeColor.call(this.chart, nodeList[i]);
                    elem.attr({ fill: this.color(color) });
                }

                this.addEvent(elem, nodeList[i]);
                g.prepend(elem);
            }

            return g;
        }
    }

    TreemapBrush.setup = function() {
        return {
            /** @cfg {"top"/"center"/"bottom" } [orient="top"]  Determines the side on which the tool tip is displayed (top, center, bottom). */
            textOrient: "top", // or bottom
            /** @cfg {"start"/"middle"/"end" } [align="center"] Aligns the title message (start, middle, end).*/
            textAlign: "middle",
            showText: true,
            titleDepth: 1,
            nodeColor: null,
            clip: false,
            format: null
        };
    }

    return TreemapBrush;
}, "chart.brush.core");