/**
 * Ext.draw.Matix is a utility class used to calculate 
 * [affine transformation](http://en.wikipedia.org/wiki/Affine_transformation) matrix.  
 * The matrix class is used to apply transformations to existing 
 * {@link Ext.draw.sprite.Sprite sprites} using a number of convenience transform 
 * methods.
 * 
 * Transformations configured directly on a sprite are processed in the following order: 
 * scaling, rotation, and translation.  The matrix class offers additional flexibility.  
 * Once a sprite is created, you can use the matrix class's transform methods as many 
 * times as needed and in any order you choose. 
 *
 * To demonstrate, we'll start with a simple {@link Ext.draw.sprite.Rect rect} sprite 
 * with the intent of rotating it 180 degrees with the bottom right corner being the 
 * center of rotation.  To begin, let's look at the initial, untransformed sprite:
 * 
 *     @example
 *     var drawContainer = new Ext.draw.Container({
 *         renderTo: Ext.getBody(),
 *         width: 380,
 *         height: 380,
 *         sprites: [{
 *             type: 'rect',
 *             width: 100,
 *             height: 100,
 *             fillStyle: 'red'
 *         }]
 *     });
 * 
 * Next, we'll use the {@link #rotate} and {@link #translate} methods from our matrix 
 * class to position the rect sprite.
 * 
 *     @example
 *     var drawContainer = new Ext.draw.Container({
 *         renderTo: Ext.getBody(),
 *         width: 380,
 *         height: 380,
 *         sprites: [{
 *             type: 'rect',
 *             width: 100,
 *             height: 100,
 *             fillStyle: 'red'
 *         }]
 *     });
 *     
 *     var main = drawContainer.getSurface();
 *     var rect = main.getItems()[0];
 *     
 *     var m = new Ext.draw.Matrix().translate(100, 100).
 *     rotate(Math.PI).
 *     translate(-100, - 100);
 *     
 *     rect.setTransform(m);
 *     main.renderFrame();
 * 
 * In the previous example we perform the following steps in order to achieve our 
 * desired rotated output:
 * 
 *  - translate the rect to the right and down by 100
 *  - rotate by 180 degrees
 *  - translate the rect to the right and down by 100
 * 
 * **Note:** A couple of things to note at this stage; 1) the rotation center point is 
 * the upper left corner of the sprite by default and 2) with transformations, the 
 * sprite itself isn't transformed, but rather the entire coordinate plane of the sprite 
 * is transformed.  The coordinate plane itself is translated by 100 and then rotated 
 * 180 degrees.  And that is why in the third step we translate the sprite using 
 * negative values.  Translating by -100 in the third step results in the sprite 
 * visually moving to the right and down within the draw container.
 * 
 * Fortunately there is a shortcut we can apply using two optional params of the rotate 
 * method allowing us to specify the center point of rotation:
 * 
 *     @example
 *     var drawContainer = new Ext.draw.Container({
 *         renderTo: Ext.getBody(),
 *         width: 380,
 *         height: 380,
 *         sprites: [{
 *             type: 'rect',
 *             width: 100,
 *             height: 100,
 *             fillStyle: 'red'
 *         }]
 *     });
 *     
 *     var main = drawContainer.getSurface();
 *     var rect = main.getItems()[0];
 *     
 *     var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
 *     
 *     rect.setTransform(m);
 *     main.renderFrame();
 * 
 * 
 * This class is compatible with 
 * [SVGMatrix](http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGMatrix) except:
 *
 *   1. Ext.draw.Matrix is not read only
 *   2. Using Number as its values rather than floats
 * 
 * Using this class helps to reduce the severe numeric 
 * [problem with HTML Canvas and SVG transformation](http://stackoverflow.com/questions/8784405/large-numbers-in-html-canvas-translate-result-in-strange-behavior)
 * 
 * Additionally, there's no way to get the current transformation matrix 
 * [in Canvas](http://stackoverflow.com/questions/7395813/html5-canvas-get-transform-matrix).
 */
Ext.define('Ext.draw.Matrix', {
 
    isMatrix: true,
 
    statics: {
        /**
         * @static
         * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
         * and (x1p, y1p)
         * @param {Number} x0 
         * @param {Number} y0 
         * @param {Number} x1 
         * @param {Number} y1 
         * @param {Number} x0p 
         * @param {Number} y0p 
         * @param {Number} x1p 
         * @param {Number} y1p 
         */
        createAffineMatrixFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
            var dx = x1 - x0,
                dy = y1 - y0,
                dxp = x1p - x0p,
                dyp = y1p - y0p,
                r = 1 / (dx * dx + dy * dy),
                a = dx * dxp + dy * dyp,
                b = dxp * dy - dx * dyp,
                c = -* x0 - b * y0,
                f = b * x0 - a * y0;
 
            return new this(* r, -* r, b * r, a * r, c * r + x0p, f * r + y0p);
        },
 
        /**
         * @static
         * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
         * and (x1p, y1p)
         * @param {Number} x0 
         * @param {Number} y0 
         * @param {Number} x1 
         * @param {Number} y1 
         * @param {Number} x0p 
         * @param {Number} y0p 
         * @param {Number} x1p 
         * @param {Number} y1p 
         */
        createPanZoomFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
            if (arguments.length === 2) {
                return this.createPanZoomFromTwoPair.apply(this, x0.concat(y0));
            }
            
            // eslint-disable-next-line vars-on-top
            var dx = x1 - x0,
                dy = y1 - y0,
                cx = (x0 + x1) * 0.5,
                cy = (y0 + y1) * 0.5,
                dxp = x1p - x0p,
                dyp = y1p - y0p,
                cxp = (x0p + x1p) * 0.5,
                cyp = (y0p + y1p) * 0.5,
                r = dx * dx + dy * dy,
                rp = dxp * dxp + dyp * dyp,
                scale = Math.sqrt(rp / r);
 
            return new this(scale, 0, 0, scale, cxp - scale * cx, cyp - scale * cy);
        },
 
        /**
         * @method
         * @static
         * Create a flyweight to wrap the given array.
         * The flyweight will directly refer the object and the elements can be changed
         * by other methods.
         *
         * Do not hold the instance of flyweight matrix.
         *
         * @param {Array} elements 
         * @return {Ext.draw.Matrix} 
         */
        fly: (function() {
            var flyMatrix = null,
                simplefly = function(elements) {
                    flyMatrix.elements = elements;
 
                    return flyMatrix;
                };
 
            return function(elements) {
                if (!flyMatrix) {
                    flyMatrix = new Ext.draw.Matrix();
                }
 
                flyMatrix.elements = elements;
                Ext.draw.Matrix.fly = simplefly;
 
                return flyMatrix;
            };
        })(),
 
        /**
         * @static
         * Create a matrix from `mat`. If `mat` is already a matrix, returns it.
         * @param {Mixed} mat 
         * @return {Ext.draw.Matrix} 
         */
        create: function(mat) {
            if (mat instanceof this) {
                return mat;
            }
 
            return new this(mat);
        }
    },
 
    /**
     * Create an affine transform matrix.
     *
     * @param {Number} xx Coefficient from x to x
     * @param {Number} xy Coefficient from x to y
     * @param {Number} yx Coefficient from y to x
     * @param {Number} yy Coefficient from y to y
     * @param {Number} dx Offset of x
     * @param {Number} dy Offset of y
     */
    constructor: function(xx, xy, yx, yy, dx, dy) {
        if (xx && xx.length === 6) {
            this.elements = xx.slice();
        }
        else if (xx !== undefined) {
            this.elements = [xx, xy, yx, yy, dx, dy];
        }
        else {
            this.elements = [1, 0, 0, 1, 0, 0];
        }
    },
 
    /**
     * Prepend a matrix onto the current.
     *
     * __Note:__ The given transform will come after the current one.
     *
     * @param {Number} xx Coefficient from x to x.
     * @param {Number} xy Coefficient from x to y.
     * @param {Number} yx Coefficient from y to x.
     * @param {Number} yy Coefficient from y to y.
     * @param {Number} dx Offset of x.
     * @param {Number} dy Offset of y.
     * @return {Ext.draw.Matrix} this
     */
    prepend: function(xx, xy, yx, yy, dx, dy) {
        var elements = this.elements,
            xx0 = elements[0],
            xy0 = elements[1],
            yx0 = elements[2],
            yy0 = elements[3],
            dx0 = elements[4],
            dy0 = elements[5];
 
        elements[0] = xx * xx0 + yx * xy0;
        elements[1] = xy * xx0 + yy * xy0;
        elements[2] = xx * yx0 + yx * yy0;
        elements[3] = xy * yx0 + yy * yy0;
        elements[4] = xx * dx0 + yx * dy0 + dx;
        elements[5] = xy * dx0 + yy * dy0 + dy;
 
        return this;
    },
 
    /**
     * Prepend a matrix onto the current.
     *
     * __Note:__ The given transform will come after the current one.
     * @param {Ext.draw.Matrix} matrix 
     * @return {Ext.draw.Matrix} this
     */
    prependMatrix: function(matrix) {
        return this.prepend.apply(this, matrix.elements);
    },
 
    /**
     * Postpend a matrix onto the current.
     *
     * __Note:__ The given transform will come before the current one.
     *
     * @param {Number} xx Coefficient from x to x.
     * @param {Number} xy Coefficient from x to y.
     * @param {Number} yx Coefficient from y to x.
     * @param {Number} yy Coefficient from y to y.
     * @param {Number} dx Offset of x.
     * @param {Number} dy Offset of y.
     * @return {Ext.draw.Matrix} this
     */
    append: function(xx, xy, yx, yy, dx, dy) {
        var elements = this.elements,
            xx0 = elements[0],
            xy0 = elements[1],
            yx0 = elements[2],
            yy0 = elements[3],
            dx0 = elements[4],
            dy0 = elements[5];
 
        elements[0] = xx * xx0 + xy * yx0;
        elements[1] = xx * xy0 + xy * yy0;
        elements[2] = yx * xx0 + yy * yx0;
        elements[3] = yx * xy0 + yy * yy0;
        elements[4] = dx * xx0 + dy * yx0 + dx0;
        elements[5] = dx * xy0 + dy * yy0 + dy0;
 
        return this;
    },
 
    /**
     * Postpend a matrix onto the current.
     *
     * __Note:__ The given transform will come before the current one.
     *
     * @param {Ext.draw.Matrix} matrix 
     * @return {Ext.draw.Matrix} this
     */
    appendMatrix: function(matrix) {
        return this.append.apply(this, matrix.elements);
    },
 
    /**
     * Set the elements of a Matrix
     * @param {Number} xx 
     * @param {Number} xy 
     * @param {Number} yx 
     * @param {Number} yy 
     * @param {Number} dx 
     * @param {Number} dy 
     * @return {Ext.draw.Matrix} this
     */
    set: function(xx, xy, yx, yy, dx, dy) {
        var elements = this.elements;
 
        elements[0] = xx;
        elements[1] = xy;
        elements[2] = yx;
        elements[3] = yy;
        elements[4] = dx;
        elements[5] = dy;
 
        return this;
    },
 
    /**
     * Return a new matrix represents the opposite transformation of the current one.
     *
     * @param {Ext.draw.Matrix} [target] A target matrix. If present, it will receive
     * the result of inversion to avoid creating a new object.
     *
     * @return {Ext.draw.Matrix} 
     */
    inverse: function(target) {
        var elements = this.elements,
            a = elements[0],
            b = elements[1],
            c = elements[2],
            d = elements[3],
            e = elements[4],
            f = elements[5],
            rDim = 1 / (* d - b * c);
 
        a *= rDim;
        b *= rDim;
        c *= rDim;
        d *= rDim;
 
        if (target) {
            target.set(d, -b, -c, a, c * f - d * e, b * e - a * f);
 
            return target;
        }
        else {
            return new Ext.draw.Matrix(d, -b, -c, a, c * f - d * e, b * e - a * f);
        }
    },
 
    /**
     * Translate the matrix.
     *
     * @param {Number} x 
     * @param {Number} y 
     * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
     * @return {Ext.draw.Matrix} this
     */
    translate: function(x, y, prepend) {
        if (prepend) {
            return this.prepend(1, 0, 0, 1, x, y);
        }
        else {
            return this.append(1, 0, 0, 1, x, y);
        }
    },
 
    /**
     * Scale the matrix.
     *
     * @param {Number} sx 
     * @param {Number} sy 
     * @param {Number} scx 
     * @param {Number} scy 
     * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
     * @return {Ext.draw.Matrix} this
     */
    scale: function(sx, sy, scx, scy, prepend) {
        var me = this;
 
        // null or undefined
        if (sy == null) {
            sy = sx;
        }
 
        if (scx === undefined) {
            scx = 0;
        }
 
        if (scy === undefined) {
            scy = 0;
        }
 
        if (prepend) {
            return me.prepend(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
        }
        else {
            return me.append(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
        }
    },
 
    /**
     * Rotate the matrix.
     *
     * @param {Number} angle Radians to rotate
     * @param {Number|null} rcx Center of rotation.
     * @param {Number|null} rcy Center of rotation.
     * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
     * @return {Ext.draw.Matrix} this
     */
    rotate: function(angle, rcx, rcy, prepend) {
        var me = this,
            cos = Math.cos(angle),
            sin = Math.sin(angle);
 
        rcx = rcx || 0;
        rcy = rcy || 0;
 
        if (prepend) {
            return me.prepend(
                cos, sin,
                -sin, cos,
                rcx - cos * rcx + rcy * sin,
                rcy - cos * rcy - rcx * sin
            );
        }
        else {
            return me.append(
                cos, sin,
                -sin, cos,
                rcx - cos * rcx + rcy * sin,
                rcy - cos * rcy - rcx * sin
            );
        }
    },
 
    /**
     * Rotate the matrix by the angle of a vector.
     *
     * @param {Number} x 
     * @param {Number} y 
     * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
     * @return {Ext.draw.Matrix} this
     */
    rotateFromVector: function(x, y, prepend) {
        var me = this,
            d = Math.sqrt(* x + y * y),
            cos = x / d,
            sin = y / d;
 
        if (prepend) {
            return me.prepend(cos, sin, -sin, cos, 0, 0);
        }
        else {
            return me.append(cos, sin, -sin, cos, 0, 0);
        }
    },
 
    /**
     * Clone this matrix.
     * @return {Ext.draw.Matrix} 
     */
    clone: function() {
        return new Ext.draw.Matrix(this.elements);
    },
 
    /**
     * Horizontally flip the matrix
     * @return {Ext.draw.Matrix} this
     */
    flipX: function() {
        return this.append(-1, 0, 0, 1, 0, 0);
    },
 
    /**
     * Vertically flip the matrix
     * @return {Ext.draw.Matrix} this
     */
    flipY: function() {
        return this.append(1, 0, 0, -1, 0, 0);
    },
 
    /**
     * Skew the matrix
     * @param {Number} angle 
     * @return {Ext.draw.Matrix} this
     */
    skewX: function(angle) {
        return this.append(1, 0, Math.tan(angle), 1, 0, 0);
    },
 
    /**
     * Skew the matrix
     * @param {Number} angle 
     * @return {Ext.draw.Matrix} this
     */
    skewY: function(angle) {
        return this.append(1, Math.tan(angle), 0, 1, 0, 0);
    },
 
    /**
     * Shear the matrix along the x-axis.
     * @param factor The horizontal shear factor.
     * @return {Ext.draw.Matrix} this
     */
    shearX: function(factor) {
        return this.append(1, 0, factor, 1, 0, 0);
    },
 
    /**
     * Shear the matrix along the y-axis.
     * @param factor The vertical shear factor.
     * @return {Ext.draw.Matrix} this
     */
    shearY: function(factor) {
        return this.append(1, factor, 0, 1, 0, 0);
    },
 
    /**
     * Reset the matrix to identical.
     * @return {Ext.draw.Matrix} this
     */
    reset: function() {
        return this.set(1, 0, 0, 1, 0, 0);
    },
 
    /* eslint-disable max-len */
    /**
     * @private
     * Split Matrix to `{{devicePixelRatio,c,0},{b,devicePixelRatio,0},{0,0,1}}.{{xx,0,dx},{0,yy,dy},{0,0,1}}`
     * @return {Object} Object with b,c,d=devicePixelRatio,xx,yy,dx,dy
     */
    precisionCompensate: function(devicePixelRatio, comp) {
        /* eslint-enable max-len */
        var elements = this.elements,
            x2x = elements[0],
            x2y = elements[1],
            y2x = elements[2],
            y2y = elements[3],
            newDx = elements[4],
            newDy = elements[5],
            r = x2y * y2x - x2x * y2y;
 
        comp.b = devicePixelRatio * x2y / x2x;
        comp.c = devicePixelRatio * y2x / y2y;
        comp.d = devicePixelRatio;
        comp.xx = x2x / devicePixelRatio;
        comp.yy = y2y / devicePixelRatio;
        comp.dx = (newDy * x2x * y2x - newDx * x2x * y2y) / r / devicePixelRatio;
        comp.dy = (newDx * x2y * y2y - newDy * x2x * y2y) / r / devicePixelRatio;
    },
 
    /**
     * @private
     * Split Matrix to `{{1,c,0},{b,d,0},{0,0,1}}.{{xx,0,dx},{0,xx,dy},{0,0,1}}`
     * @return {Object} Object with b,c,d,xx,yy=xx,dx,dy
     */
    precisionCompensateRect: function(devicePixelRatio, comp) {
        var elements = this.elements,
            x2x = elements[0],
            x2y = elements[1],
            y2x = elements[2],
            y2y = elements[3],
            newDx = elements[4],
            newDy = elements[5],
            yxOnXx = y2x / x2x;
 
        comp.b = devicePixelRatio * x2y / x2x;
        comp.c = devicePixelRatio * yxOnXx;
        comp.d = devicePixelRatio * y2y / x2x;
        comp.xx = x2x / devicePixelRatio;
        comp.yy = x2x / devicePixelRatio;
        comp.dx = (newDy * y2x - newDx * y2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
        comp.dy = -(newDy * x2x - newDx * x2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
    },
 
    /**
     * Transform point returning the x component of the result.
     * @param {Number} x 
     * @param {Number} y 
     * @return {Number} x component of the result.
     */
    x: function(x, y) {
        var elements = this.elements;
 
        return x * elements[0] + y * elements[2] + elements[4];
    },
 
    /**
     * Transform point returning the y component of the result.
     * @param {Number} x 
     * @param {Number} y 
     * @return {Number} y component of the result.
     */
    y: function(x, y) {
        var elements = this.elements;
 
        return x * elements[1] + y * elements[3] + elements[5];
    },
 
    /**
     * @private
     * @param {Number} i 
     * @param {Number} j 
     * @return {String} 
     */
    get: function(i, j) {
        return +this.elements[+ j * 2].toFixed(4);
    },
 
    /**
     * Transform a point to a new array.
     * @param {Array} point 
     * @return {Array} 
     */
    transformPoint: function(point) {
        var elements = this.elements,
            x, y;
 
        if (point.isPoint) {
            x = point.x;
            y = point.y;
        }
        else {
            x = point[0];
            y = point[1];
        }
 
        return [
            x * elements[0] + y * elements[2] + elements[4],
            x * elements[1] + y * elements[3] + elements[5]
        ];
    },
 
    /**
     * @param {Object} bbox Given as `{x: Number, y: Number, width: Number, height: Number}`.
     * @param {Number} [radius] 
     * @param {Object} [target] Optional target object to recieve the result.
     * Recommended to use it for better gc.
     *
     * @return {Object} Object with x, y, width and height.
     */
    transformBBox: function(bbox, radius, target) {
        var elements = this.elements,
            l = bbox.x,
            t = bbox.y,
            w0 = bbox.width * 0.5,
            h0 = bbox.height * 0.5,
            xx = elements[0],
            xy = elements[1],
            yx = elements[2],
            yy = elements[3],
            cx = l + w0,
            cy = t + h0,
            w, h, scales;
 
        if (radius) {
            w0 -= radius;
            h0 -= radius;
            scales = [
                Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]),
                Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3])
            ];
            w = Math.abs(w0 * xx) + Math.abs(h0 * yx) + Math.abs(scales[0] * radius);
            h = Math.abs(w0 * xy) + Math.abs(h0 * yy) + Math.abs(scales[1] * radius);
        }
        else {
            w = Math.abs(w0 * xx) + Math.abs(h0 * yx);
            h = Math.abs(w0 * xy) + Math.abs(h0 * yy);
        }
 
        if (!target) {
            target = {};
        }
 
        target.x = cx * xx + cy * yx + elements[4] - w;
        target.y = cx * xy + cy * yy + elements[5] - h;
        target.width = w + w;
        target.height = h + h;
 
        return target;
    },
 
    /**
     * Transform a list for points.
     *
     * __Note:__ will change the original list but not points inside it.
     * @param {Array} list 
     * @return {Array} list
     */
    transformList: function(list) {
        var elements = this.elements,
            xx = elements[0],
            yx = elements[2],
            dx = elements[4],
            xy = elements[1],
            yy = elements[3],
            dy = elements[5],
            ln = list.length,
            p, i;
 
        for (= 0; i < ln; i++) {
            p = list[i];
            list[i] = [
                p[0] * xx + p[1] * yx + dx,
                p[0] * xy + p[1] * yy + dy
            ];
        }
 
        return list;
    },
 
    /**
     * Determines whether this matrix is an identity matrix (no transform).
     * @return {Boolean} 
     */
    isIdentity: function() {
        var elements = this.elements;
 
        return elements[0] === 1 &&
               elements[1] === 0 &&
               elements[2] === 0 &&
               elements[3] === 1 &&
               elements[4] === 0 &&
               elements[5] === 0;
    },
 
    /**
     * Determines if this matrix has the same values as another matrix.
     * @param {Ext.draw.Matrix} matrix A maxtrix or array of its elements.
     * @return {Boolean} 
     */
    isEqual: function(matrix) {
        var elements = matrix && matrix.isMatrix ? matrix.elements : matrix,
            myElements = this.elements;
 
        return myElements[0] === elements[0] &&
               myElements[1] === elements[1] &&
               myElements[2] === elements[2] &&
               myElements[3] === elements[3] &&
               myElements[4] === elements[4] &&
               myElements[5] === elements[5];
    },
 
    /**
     * @deprecated 6.0.1 This method is deprecated.
     * Determines if this matrix has the same values as another matrix.
     * @param {Ext.draw.Matrix} matrix 
     * @return {Boolean} 
     */
    equals: function(matrix) {
        return this.isEqual(matrix);
    },
 
    /**
     * Create an array of elements by horizontal order (xx,yx,dx,yx,yy,dy).
     * @return {Array} 
     */
    toArray: function() {
        var elements = this.elements;
 
        return [elements[0], elements[2], elements[4], elements[1], elements[3], elements[5]];
    },
 
    /**
     * Create an array of elements by vertical order (xx,xy,yx,yy,dx,dy).
     * @return {Array|String} 
     */
    toVerticalArray: function() {
        return this.elements.slice();
    },
 
    /**
     * Get an array of elements.
     * The numbers are rounded to keep only 4 decimals.
     * @return {Array} 
     */
    toString: function() {
        var me = this;
 
        return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), me.get(2, 0),
                me.get(2, 1)].join(',');
    },
 
    /**
     * Apply the matrix to a drawing context.
     * @param {Object} ctx 
     * @return {Ext.draw.Matrix} this
     */
    toContext: function(ctx) {
        ctx.transform.apply(ctx, this.elements);
 
        return this;
    },
 
    /**
     * Return a string that can be used as transform attribute in SVG.
     * @return {String} 
     */
    toSvg: function() {
        var elements = this.elements;
 
        // The reason why we cannot use `.join` is the `1e5` form is not accepted in svg.
        return "matrix(" +
            elements[0].toFixed(9) + ',' +
            elements[1].toFixed(9) + ',' +
            elements[2].toFixed(9) + ',' +
            elements[3].toFixed(9) + ',' +
            elements[4].toFixed(9) + ',' +
            elements[5].toFixed(9) +
            ")";
    },
 
    /**
     * Get the x scale of the matrix.
     * @return {Number} 
     */
    getScaleX: function() {
        var elements = this.elements;
 
        return Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]);
    },
 
    /**
     * Get the y scale of the matrix.
     * @return {Number} 
     */
    getScaleY: function() {
        var elements = this.elements;
 
        return Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3]);
    },
 
    /**
     * Get x-to-x component of the matrix
     * @return {Number} 
     */
    getXX: function() {
        return this.elements[0];
    },
 
    /**
     * Get x-to-y component of the matrix.
     * @return {Number} 
     */
    getXY: function() {
        return this.elements[1];
    },
 
    /**
     * Get y-to-x component of the matrix.
     * @return {Number} 
     */
    getYX: function() {
        return this.elements[2];
    },
 
    /**
     * Get y-to-y component of the matrix.
     * @return {Number} 
     */
    getYY: function() {
        return this.elements[3];
    },
 
    /**
     * Get offset x component of the matrix.
     * @return {Number} 
     */
    getDX: function() {
        return this.elements[4];
    },
 
    /**
     * Get offset y component of the matrix.
     * @return {Number} 
     */
    getDY: function() {
        return this.elements[5];
    },
 
    /**
     * Splits this transformation matrix into Scale, Rotate, Translate components,
     * assuming it was produced by applying transformations in that order.
     * @return {Object} 
     */
    split: function() {
        var el = this.elements,
            xx = el[0],
            xy = el[1],
            yy = el[3],
            out = {
                translateX: el[4],
                translateY: el[5]
            };
 
        out.rotate = out.rotation = Math.atan2(xy, xx);
        out.scaleX = xx / Math.cos(out.rotate);
        out.scaleY = yy / xx * out.scaleX;
 
        return out;
    }
}, function() {
    function registerName(properties, name, i) {
        properties[name] = {
            get: function() {
                return this.elements[i];
            },
            set: function(val) {
                this.elements[i] = val;
            }
        };
    }
 
    // Compatibility with SVGMatrix.
    if (Object.defineProperties) {
        var properties = {}; // eslint-disable-line vars-on-top
 
        /**
         * @property {Number} a Get x-to-x component of the matrix. Avoid using it for performance
         * consideration.
         * Use {@link #getXX} instead.
         */
        registerName(properties, 'a', 0);
        registerName(properties, 'b', 1);
        registerName(properties, 'c', 2);
        registerName(properties, 'd', 3);
        registerName(properties, 'e', 4);
        registerName(properties, 'f', 5);
        Object.defineProperties(this.prototype, properties);
    }
 
    /**
     * Performs matrix multiplication. This matrix is post-multiplied by another matrix.
     *
     * __Note:__ The given transform will come before the current one.
     *
     * @method
     * @param {Ext.draw.Matrix} matrix 
     * @return {Ext.draw.Matrix} this
     */
    this.prototype.multiply = this.prototype.appendMatrix;
});