jui.define("chart.map", [ "util.base", "util.dom", "util.math", "util.svg" ], function(_, $, math, SVG) {
    /**
     * @class chart.grid.core
     * @extends chart.draw
     * @abstract
     */
    var Map = function() {
        var self = this;
        var pathData = {},
            pathGroup = null,
            pathIndex = {},
            pathScale = 1,
            pathX = 0,
            pathY = 0;

        function loadArray(data) {
            var children = [];

            for(var i = 0, len = data.length; i < len; i++) {
                if(_.typeCheck("object", data[i])) {
                    var style = {};

                    if(_.typeCheck("string", data[i].style)) {
                        style = getStyleObj(data[i].style);
                        delete data[i].style;
                    }

                    var elem = SVG.createObject({
                        type: (data[i].d != null) ? "path" : "polygon",
                        attr: data[i]
                    });

                    // Set styles
                    elem.attr(_.extend(style, {
                        fill: self.chart.theme("mapPathBackgroundColor"),
                        "fill-opacity": self.chart.theme("mapPathBackgroundOpacity"),
                        stroke: self.chart.theme("mapPathBorderColor"),
                        "stroke-width": self.chart.theme("mapPathBorderWidth"),
                        "stroke-opacity": self.chart.theme("mapPathBorderOpacity")
                    }));

                    children.push({
                        path: elem,
                        data: data[i]
                    });
                }
            }

            function getStyleObj(str) {
                var style = {},
                    list = str.split(";");

                for(var i = 0; i < list.length; i++) {
                    if(list[i].indexOf(":") != -1) {
                        var obj = list[i].split(":");

                        style[_.trim(obj[0])] = _.trim(obj[1]);
                    }
                }

                return style;
            }

            return children;
        }

        function getPathList(root) {
            if(!_.typeCheck("string", root.id)) return;

            var pathData = [],
                children = root.childNodes;

            for(var i = 0, len = children.length; i < len; i++) {
                var elem = children[i],
                    name = elem.nodeName.toLowerCase();

                if(elem.nodeType != 1) continue;

                if(name == "g") {
                    pathData = pathData.concat(getPathList(elem));
                } else if(name == "path" || name == "polygon") {
                    var obj = { group: root.id };

                    for(var key in elem.attributes) {
                        var attr = elem.attributes[key];

                        if(attr.specified && isLoadAttribute(attr.name)) {
                            obj[attr.name] = replaceXYValue(attr);
                        }
                    }

                    if(_.typeCheck("string", obj.id)) {
                        _.extend(obj, getDataById(obj.id));
                    }

                    pathData.push(obj);
                }
            }

            return pathData;
        }

        function loadPath(uri) {
            // 해당 URI의 데이터가 존재할 경우
            if(_.typeCheck("array", pathData[uri])) {
                return loadArray(pathData[uri]);
            }

            // 해당 URI의 데이터가 없을 경우
            pathData[uri] = [];

            _.ajax({
                url: uri,
                async: false,
                success: function(xhr) {
                    var xml = xhr.responseXML,
                        svg = xml.getElementsByTagName("svg"),
                        style = xml.getElementsByTagName("style");

                    if(svg.length != 1) return;
                    var children = svg[0].childNodes;

                    for(var i = 0, len = children.length; i < len; i++) {
                        var elem = children[i],
                            name = elem.nodeName.toLowerCase();

                        if(elem.nodeType != 1) continue;

                        if(name == "g") {
                            pathData[uri] = pathData[uri].concat(getPathList(elem));
                        } else if(name == "path" || name == "polygon") {
                            var obj = {};

                            for(var key in elem.attributes) {
                                var attr = elem.attributes[key];

                                if(attr.specified && isLoadAttribute(attr.name)) {
                                    obj[attr.name] = replaceXYValue(attr);
                                }
                            }

                            if(_.typeCheck("string", obj.id)) {
                                _.extend(obj, getDataById(obj.id));
                            }

                            pathData[uri].push(obj);
                        }
                    }

                    // 스타일 태그가 정의되어 있을 경우
                    for(var i = 0; i < style.length; i++) {
                        self.svg.root.element.appendChild(style[i]);
                    }
                },
                fail: function(xhr) {
                    throw new Error("JUI_CRITICAL_ERR: Failed to load resource");
                }
            });

            return loadArray(pathData[uri]);
        }

        function isLoadAttribute(name) {
            return (
                name == "group" || name == "id" || name == "title" || name == "x" || name == "y" ||
                name == "d" || name == "points" || name == "class" || name == "style"
            );
        }

        function replaceXYValue(attr) {
            if(attr.name == "x" || attr.name == "y") {
                return parseFloat(attr.value);
            }

            return attr.value;
        }

        function getDataById(id) {
            var list = self.axis.data;

            for(var i = 0; i < list.length; i++) {
                var dataId = self.axis.getValue(list[i], "id", null);

                if(dataId == id) {
                    return list[i];
                }
            }

            return null;
        }

        function makePathGroup() {
            var group = self.chart.svg.group(),
                list = loadPath(self.map.path);

            for(var i = 0, len = list.length; i < len; i++) {
                var path = list[i].path,
                    data = list[i].data;

                //addEvent(path, list[i]);
                group.append(path);

                if(_.typeCheck("string", data.id)) {
                    pathIndex[data.id] = list[i];
                }
            }

            return group;
        }

        function getScaleXY() { // 차후에 공통 함수로 변경해야 함
            var w = self.map.width,
                h = self.map.height,
                px = ((w * pathScale) - w) / 2,
                py = ((h * pathScale) - h) / 2;

            return {
                x: px + pathX,
                y: py + pathY
            }
        }

        function addEvent(elem, obj) {
            var chart = self.chart;

            elem.on("click", function(e) {
                setMouseEvent(e);
                chart.emit("map.click", [ obj, e ]);
            });

            elem.on("dblclick", function(e) {
                setMouseEvent(e);
                chart.emit("map.dblclick", [ obj, e ]);
            });

            elem.on("contextmenu", function(e) {
                setMouseEvent(e);
                chart.emit("map.rclick", [ obj, e ]);
                e.preventDefault();
            });

            elem.on("mouseover", function(e) {
                setMouseEvent(e);
                chart.emit("map.mouseover", [ obj, e ]);
            });

            elem.on("mouseout", function(e) {
                setMouseEvent(e);
                chart.emit("map.mouseout", [ obj, e ]);
            });

            elem.on("mousemove", function(e) {
                setMouseEvent(e);
                chart.emit("map.mousemove", [ obj, e ]);
            });

            elem.on("mousedown", function(e) {
                setMouseEvent(e);
                chart.emit("map.mousedown", [ obj, e ]);
            });

            elem.on("mouseup", function(e) {
                setMouseEvent(e);
                chart.emit("map.mouseup", [ obj, e ]);
            });

            function setMouseEvent(e) {
                var pos = $.offset(chart.root),
                    offsetX = e.pageX - pos.left,
                    offsetY = e.pageY - pos.top;

                e.bgX = offsetX;
                e.bgY = offsetY;
                e.chartX = offsetX - chart.padding("left");
                e.chartY = offsetY - chart.padding("top");
            }
        }

        this.scale = function(id) {
            if(!_.typeCheck("string", id)) return;

            var x = null,
                y = null,
                path = null,
                data = null,
                pxy = getScaleXY();

            if(_.typeCheck("object", pathIndex[id])) {
                path = pathIndex[id].path;
                data = pathIndex[id].data;

                if(data.x != null) {
                    var dx = self.axis.getValue(data, "dx", 0),
                        cx = parseFloat(data.x) + dx;
                    x = (cx * pathScale) - pxy.x;
                }

                if(data.y != null) {
                    var dy = self.axis.getValue(data, "dy", 0),
                        cy = parseFloat(data.y) + dy;
                    y = (cy * pathScale) - pxy.y;
                }
            }

            return {
                x: x,
                y: y,
                path: path,
                data: data
            }
        }

        this.scale.each = function(callback) {
            var self = this;

            for(var id in pathIndex) {
                callback.apply(self, [ id, pathIndex[id] ]);
            }
        }

        this.scale.size = function() {
            return {
                width: self.map.width,
                height: self.map.height
            }
        }

        this.scale.scale = function(scale) {
            if(!scale || scale < 0) return pathScale;

            pathScale = scale;
            pathGroup.scale(pathScale);
            this.view(pathX, pathY);

            return pathScale;
        }

        this.scale.view = function(x, y) {
            var xy = { x: pathX, y: pathY };

            if(!_.typeCheck("number", x) || !_.typeCheck("number", y))
                return xy;

            pathX = x;
            pathY = y;

            var pxy = getScaleXY();
            pathGroup.translate(-pxy.x, -pxy.y);

            return {
                x: pathX,
                y: pathY
            }
        }

        this.draw = function() {
            var root = this.chart.svg.group();

            pathScale = this.map.scale;
            pathX = this.map.viewX;
            pathY = this.map.viewY;
            pathGroup = makePathGroup();

            // pathGroup 루트에 추가
            root.append(pathGroup);

            if(this.map.scale != 1) {
                this.scale.scale(pathScale);
            }

            if(this.map.viewX != 0 || this.map.viewY != 0) {
                this.scale.view(pathX, pathY);
            }

            if(this.map.hide) {
                root.attr({ visibility: "hidden" });
            }

            return {
                root: root,
                scale: this.scale
            };
        }

        this.drawAfter = function(obj) {
            obj.root.attr({ "clip-path": "url(#" + this.axis.get("clipRectId") + ")" });

            // 모든 path가 그려진 이후에 이벤트 설정
            setTimeout(function() {
                self.scale.each(function(id, obj) {
                    addEvent(obj.path, obj);
                });
            }, 1);
        }
    }

    Map.setup = function() {
        /** @property {chart.builder} chart */
        /** @property {chart.axis} axis */
        /** @property {Object} map */

        return {
            scale: 1,
            viewX: 0,
            viewY: 0,

            /** @cfg {Boolean} [hide=false] Determines whether to display an applicable grid.  */
            hide: false,
            /** @cfg {String} [map=''] Set a map file's name */
            path: "",
            /** @cfg {Number} [width=-1] Set map's width */
            width: -1,
            /** @cfg {Number} [height=-1] Set map's height */
            height: -1
        };
    }

    /**
     * @event map_click
     * Event that occurs when clicking on the map area. (real name ``` map.click ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */
    /**
     * @event map_dblclick
     * Event that occurs when double clicking on the map area. (real name ``` map.dblclick ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */
    /**
     * @event map_rclick
     * Event that occurs when right clicking on the map area. (real name ``` map.rclick ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */
    /**
     * @event map_mouseover
     * Event that occurs when placing the mouse over the map area. (real name ``` map.mouseover ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */
    /**
     * @event map_mouseout
     * Event that occurs when moving the mouse out of the map area. (real name ``` map.mouseout ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */
    /**
     * @event map_mousemove
     * Event that occurs when moving the mouse over the map area. (real name ``` map.mousemove ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */
    /**
     * @event map_mousedown
     * Event that occurs when left clicking on the map area. (real name ``` map.mousedown ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */
    /**
     * @event map_mouseup
     * Event that occurs after left clicking on the map area. (real name ``` map.mouseup ```)
     * @param {jQueryEvent} e The event object.
     * @param {Number} index Axis index.
     */

    return Map;
}, "chart.draw");