//copy codes from d3.js, add 4 functions: tickAttr, tickTextAttr, minorTickAttr and domainAttr;
//axis() changes, need a raphael paper object param, return raphael set object.
//examples in ../examples/axis/ to know the usage.
//a basic part for other data visualization format
/*global d3*/
/*!
 * Axis兼容定义
 */
;(function (name, definition) {
    if (typeof define === 'function') { // Module
        define(definition);
    } else { // Assign to common namespaces or simply the global object (window)
        this[name] = definition(function (id) { return this[id];});
    }
})('Axis', function (require) {
    /**
     * function from d3, get scaleRange of an ordinal scale
     * @param {Array} domain ordinal scale's range
     */
    function d3_scaleExtent(domain) {
        var start = domain[0], stop = domain[domain.length - 1];
        return start < stop ? [start, stop] : [stop, start];
    }

    /**
     * function from d3, get scaleRange of a scale
     */
    function d3_scaleRange(scale) {
        return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
    }

    /**
     * function from d3, get subticks
     * @param scale, scale
     * @param ticks, major ticks of scale
     * @param m, number of subdivide
     */
    function d3_svg_axisSubdivide(scale, ticks, m) {
        var subticks = [];
        if (m && ticks.length > 1) {
            var extent = d3_scaleExtent(scale.domain()),
                i = -1,
                n = ticks.length,
                d = (ticks[1] - ticks[0]) / ++m,
                j,
                v;
            while (++i < n) {
                for (j = m; --j > 0;) {
                    if ((v = +ticks[i] - j * d) >= extent[0]) {
                        subticks.push(v);
                    }
                }
            }
            for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) {
                subticks.push(v);
            }
        }
        return subticks;
    }

    var Axis = function () {
        var scale = d3.scale.linear(),
            orient = "bottom",
            tickMajorSize = 6,
            tickMinorSize = 6,
            tickEndSize = 6,
            tickPadding = 3,
            tickArguments_ = [10],
            tickFormat_,
            tickSubdivide = 0,

            tickAttr_ = {},
            tickTextAttr_ = {},
            minorTickAttr_ = {},
            domainAttr_ = {};
      
        /**
         * @param paper: raphael's paper object.
         * @return axisSet: raphael's set object.
         */
        function axis(paper) {
            // Ticks for quantitative scale, or domain values for ordinal scale.
            var ticks = scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain(),
                tickFormat = tickFormat_ === undefined ?
                    (scale.tickFormat ?
                        scale.tickFormat.apply(scale, tickArguments_)
                        : String)
                    : tickFormat_;

            var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide);
            var range = d3_scaleRange(scale);
        
            var axisSet = paper.set();

            switch (orient) {
            case "bottom":
                subticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + tickMinorSize + "V0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + tickMajorSize + "V0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(tickX,  Math.max(tickMajorSize, 0) + tickPadding + 2,
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "middle"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize)
                    .attr(domainAttr_));
                break;

            case "top":
                subticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + -tickMinorSize + "V0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickX = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickX + "," + -tickMajorSize + "V0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(tickX,  -(Math.max(tickMajorSize, 0) + tickPadding + 2),
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "middle"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize)
                    .attr(domainAttr_));
                break;

            case "left":
                subticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + -tickMinorSize + "," + tickY + "H0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + -tickMajorSize + "," + tickY + "H0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(-(Math.max(tickMajorSize, 0) + tickPadding),  tickY,
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "end"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize)
                    .attr(domainAttr_));
                break;

            case "right":
                subticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickMinorSize + "," + tickY + "H0")
                        .attr(minorTickAttr_));
                });
                ticks.forEach(function (d, i, arr) {
                    var tickY = scale.ticks ? scale(d) : scale(d) + scale.rangeBand() / 2;
                    axisSet.push(paper
                        .path("M" + tickMajorSize + "," + tickY + "H0")
                        .attr(tickAttr_));
                    axisSet.push(paper
                        .text(Math.max(tickMajorSize, 0) + tickPadding,  tickY,
                            typeof tickFormat === "function" ? tickFormat(d) : tickFormat)
                        .attr({"text-anchor": "start"})
                        .attr(tickTextAttr_));
                });
                axisSet.push(paper
                    .path("M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize)
                    .attr(domainAttr_));
                break;
            }

            return axisSet;
        }
      
        /**
         * get or set axis' scale.
         */
        axis.scale = function (x) {
            if (!arguments.length) {
                return scale;
            }
            scale = x;
            return axis;
        };
      
        /**
         * get or set axis' orinet: "bottom", "top", "left", "right", default orient is bottom.
         */
        axis.orient = function (x) {
            if (!arguments.length) {
                return orient;
            }
            orient = x;
            return axis;
        };
      
        /**
         * get or set axis' ticks number.
         */
        axis.ticks = function () {
            if (!arguments.length) {
                return tickArguments_;
            }
            tickArguments_ = arguments;
            return axis;
        };
      
        /**
         * get or set axis' ticks format function, it's a function change format style.
         * from one string format to another string format.
         */
        axis.tickFormat = function (x) {
            if (!arguments.length) {
                return tickFormat_;
            }
            tickFormat_ = x;
            return axis;
        };
      
        /**
         * get or set axis' tick size(length of tick line, unit: px).
         * @param arguments.length === 0, get axis' major tick size.
         * @param arguments.length === 1, set axis' all tick sizes as x.
         * @param arguments.length === 2, get axis' major tick size as x, minor and end size as y.
         * @param arguments.length === 3, get axis' major tick size as x, minor size as y, end size as z.
         */
        axis.tickSize = function (x, y, z) {
            if (!arguments.length) {
                return tickMajorSize;
            }
            var n = arguments.length - 1;
            tickMajorSize = +x;
            tickMinorSize = n > 1 ? +y : tickMajorSize;
            tickEndSize = n > 0 ? +arguments[n] : tickMajorSize;
            return axis;
        };
      
        /**
         * get or set axis' tick padding(the distance between tick text and axis).
         * @param x is a number, unit is px;
         */
        axis.tickPadding = function (x) {
            if (!arguments.length) {
                return tickPadding;
            }
            tickPadding = +x;
            return axis;
        };

        /**
         * get or set axis' sub tick divide number(divide number between two major ticks).
         */
        axis.tickSubdivide = function (x) {
            if (!arguments.length) {
                return tickSubdivide;
            }
            tickSubdivide = +x;
            return axis;
        };

        /**
         * get or set axis' tick attribute(Raphael format).
         */
        axis.tickAttr = function (x) {
            if (!arguments.length) {
                return tickAttr_;
            }
            tickAttr_ = x;
            return axis;
        };
      
        /**
         * get or set axis' tick text attribute(Raphael format).
         */
        axis.tickTextAttr = function (x) {
            if (!arguments.length) {
                return tickTextAttr_;
            }
            tickTextAttr_ = x;
            return axis;
        };

        /**
         * get or set axis' minor tick attribute(Raphael format).
         */
        axis.minorTickAttr = function (x) {
            if (!arguments.length) {
                return minorTickAttr_;
            }
            minorTickAttr_ = x;
            return axis;
        };
      
        /**
         * get or set axis' domain(axis line) attribute(Raphael format).
         */
        axis.domainAttr = function (x) {
            if (!arguments.length) {
                return domainAttr_;
            }
            domainAttr_ = x;
            return axis;
        };
      
        return axis;
    };

    return Axis;
});
