# classes/matrix.js

    "use strict";

    import Flatten from '../flatten';

    /**
     * Class representing an affine transformation 3x3 matrix:
     * <pre>
     *      [ a  c  tx
     * A =    b  d  ty
     *        0  0  1  ]
     * </pre
     * @type {Matrix}
     */
    export class Matrix {
        /**
         * Construct new instance of affine transformation matrix <br/>
         * If parameters omitted, construct identity matrix a = 1, d = 1
         * @param {number} a - position(0,0)   sx*cos(alpha)
         * @param {number} b - position (0,1)  sx*sin(alpha)
         * @param {number} c - position (1,0)  -sy*sin(alpha)
         * @param {number} d - position (1,1)  sy*cos(alpha)
         * @param {number} tx - position (2,0) translation by x
         * @param {number} ty - position (2,1) translation by y
         */
        constructor(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) {
            this.a = a;
            this.b = b;
            this.c = c;
            this.d = d;
            this.tx = tx;
            this.ty = ty;
        }

        /**
         * Return new cloned instance of matrix
         * @return {Matrix}
         **/
        clone() {
            return new Matrix(this.a, this.b, this.c, this.d, this.tx, this.ty);
        };

        /**
         * Transform vector [x,y] using transformation matrix. <br/>
         * Vector [x,y] is an abstract array[2] of numbers and not a FlattenJS object <br/>
         * The result is also an abstract vector [x',y'] = A * [x,y]:
         * <code>
         * [x'       [ ax + by + tx
         *  y'   =     cx + dy + ty
         *  1]                    1 ]
         * </code>
         * @param {number[]} vector - array[2] of numbers
         * @returns {number[]} transformation result - array[2] of numbers
         */
        transform(vector) {
            return [
                vector[0] * this.a + vector[1] * this.c + this.tx,
                vector[0] * this.b + vector[1] * this.d + this.ty
            ]
        };

        /**
         * Returns result of multiplication of this matrix by other matrix
         * @param {Matrix} other_matrix - matrix to multiply by
         * @returns {Matrix}
         */
        multiply(other_matrix) {
            return new Matrix(
                this.a * other_matrix.a + this.c * other_matrix.b,
                this.b * other_matrix.a + this.d * other_matrix.b,
                this.a * other_matrix.c + this.c * other_matrix.d,
                this.b * other_matrix.c + this.d * other_matrix.d,
                this.a * other_matrix.tx + this.c * other_matrix.ty + this.tx,
                this.b * other_matrix.tx + this.d * other_matrix.ty + this.ty
            )
        };

        /**
         * Return new matrix as a result of multiplication of the current matrix
         * by the matrix(1,0,0,1,tx,ty)
         * @param {number} tx - translation by x
         * @param {number} ty - translation by y
         * @returns {Matrix}
         */
        translate(...args) {
            let tx, ty;
            if (args.length == 1 && (args[0] instanceof Flatten.Vector)) {
                tx = args[0].x;
                ty = args[0].y;
            } else if (args.length == 2 && typeof (args[0]) == "number" && typeof (args[1]) == "number") {
                tx = args[0];
                ty = args[1];
            } else {
                throw Flatten.Errors.ILLEGAL_PARAMETERS;
            }
            return this.multiply(new Matrix(1, 0, 0, 1, tx, ty))
        };

        /**
         * Return new matrix as a result of multiplication of the current matrix
         * by the matrix that defines rotation by given angle (in radians) around
         * point (0,0) in counter clockwise direction
         * @param {number} angle - angle in radians
         * @returns {Matrix}
         */
        rotate(angle) {
            let cos = Math.cos(angle);
            let sin = Math.sin(angle);
            return this.multiply(new Matrix(cos, sin, -sin, cos, 0, 0));
        };

        /**
         * Return new matrix as a result of multiplication of the current matrix
         * by the matrix (sx,0,0,sy,0,0) that defines scaling
         * @param {number} sx
         * @param {number} sy
         * @returns {Matrix}
         */
        scale(sx, sy) {
            return this.multiply(new Matrix(sx, 0, 0, sy, 0, 0));
        };

        /**
         * Returns true if two matrix are equal parameter by parameter
         * @param {Matrix} matrix - other matrix
         * @returns {boolean} true if equal, false otherwise
         */
        equalTo(matrix) {
            if (!Flatten.Utils.EQ(this.tx, matrix.tx)) return false;
            if (!Flatten.Utils.EQ(this.ty, matrix.ty)) return false;
            if (!Flatten.Utils.EQ(this.a, matrix.a)) return false;
            if (!Flatten.Utils.EQ(this.b, matrix.b)) return false;
            if (!Flatten.Utils.EQ(this.c, matrix.c)) return false;
            if (!Flatten.Utils.EQ(this.d, matrix.d)) return false;
            return true;
        };
    };

    Flatten.Matrix = Matrix;
    /**
     * Function to create matrix equivalent to "new" constructor
     * @param args
     */
    export const matrix = (...args) => new Flatten.Matrix(...args);
    Flatten.matrix = matrix;