package com.srbenoit.geom;

/**
 * A transformation matrix capable representing an affine transformation and acting on vectors. We
 * store only 6 matrix elements rather than 9 since three of the elements are constant.
 */
public final class Transform2 {

    /** the first element of the first row */
    private transient double m00;

    /** the second element of the first row */
    private transient double m01;

    /** the third element of the first row */
    private transient double m02;

    /** the first element of the second row */
    private transient double m10;

    /** the second element of the second row */
    private transient double m11;

    /** the third element of the second row */
    private transient double m12;

    /**
     * Constructs and initializes a <code>Transform2</code> to the identity transformation.
     */
    public Transform2() {

        this.m00 = 1.0;
        this.m01 = 0.0;
        this.m02 = 0.0;

        this.m10 = 0.0;
        this.m11 = 1.0;
        this.m12 = 0.0;
    }

    /**
     * Returns a string that contains the values of this <code>Transform2</code>.
     *
     * @return  the <code>String</code> representation
     */
    @Override public String toString() {

        return this.m00 + ", " + this.m01 + ", " + this.m02 + "\n" + this.m10 + ", " + this.m11
            + ", " + this.m12 + "\n";
    }

    /**
     * Sets the value of an element in the matrix.
     *
     * @param  row    the row
     * @param  col    the column
     * @param  value  the new value to place at that location in the matrix
     */
    public void set(final int row, final int col, final double value) {

        switch (row) {

        case 0:
            switch (col) {

            case 0:
                this.m00 = value;
                break;

            case 1:
                this.m01 = value;
                break;

            case 2:
                this.m02 = value;
                break;

            default:
                break;
            }

            break;

        case 1:
            switch (col) {

            case 0:
                this.m10 = value;
                break;

            case 1:
                this.m11 = value;
                break;

            case 2:
                this.m12 = value;
                break;

            default:
                break;
            }

            break;

        default:

            break;
        }
    }

    /**
     * Gets the value of an element in the matrix.
     *
     * @param   row  the row
     * @param   col  the column
     * @return  the value at that location in the matrix
     */
    public double get(final int row, final int col) {

        double value;

        switch (row) {

        case 0:
            switch (col) {

            case 0:
                value = this.m00;
                break;

            case 1:
                value = this.m01;
                break;

            case 2:
                value = this.m02;
                break;

            default:
                value = 0;
                break;
            }

            break;

        case 1:
            switch (col) {

            case 0:
                value = this.m10;
                break;

            case 1:
                value = this.m11;
                break;

            case 2:
                value = this.m12;
                break;

            default:
                value = 0;
                break;
            }

            break;

        default:
            value = 0;
            break;
        }

        return value;
    }

    /**
     * Sets the elements of this matrix from another matrix.
     *
     * @param  matrix  the source matrix
     */
    public void set(final Transform2 matrix) {

        this.m00 = matrix.get(0, 0);
        this.m01 = matrix.get(0, 1);
        this.m02 = matrix.get(0, 2);
        this.m10 = matrix.get(1, 0);
        this.m11 = matrix.get(1, 1);
        this.m12 = matrix.get(1, 2);
    }

    /**
     * Right-multiplies this matrix by <code>matrix</code>.
     *
     * @param  matrix  the matrix by which to multiply this matrix
     */
    public void mul(final Transform2 matrix) {

        double c00;
        double c01;
        double c02;
        double c10;
        double c11;
        double c12;

        c00 = (this.m00 * matrix.get(0, 0)) + (this.m01 * matrix.get(1, 0));
        c01 = (this.m00 * matrix.get(0, 1)) + (this.m01 * matrix.get(1, 1));
        c02 = (this.m00 * matrix.get(0, 2)) + (this.m01 * matrix.get(1, 2)) + this.m02;

        c10 = (this.m10 * matrix.get(0, 0)) + (this.m11 * matrix.get(1, 0));
        c11 = (this.m10 * matrix.get(0, 1)) + (this.m11 * matrix.get(1, 1));
        c12 = (this.m10 * matrix.get(0, 2)) + (this.m11 * matrix.get(1, 2)) + this.m12;

        this.m00 = c00;
        this.m01 = c01;
        this.m02 = c02;
        this.m10 = c10;
        this.m11 = c11;
        this.m12 = c12;
    }

    /**
     * Inverts the <code>MatrixF2</code> in place. Rather than use an LU decomposition, it is
     * faster to just directly compute the inverse of a 3x3 matrix where the bottom row is [0 0 1]
     * directly.
     *
     * @throws  SingularMatrixException  if the matrix is singular (not invertible)
     */
    public void invert() throws SingularMatrixException {

        double det;
        double recip;
        double c00;
        double c01;
        double c02;
        double c10;
        double c11;
        double c12;

        // Compute the determinant and see if it is zero (non-invertible)
        det = (this.m00 * this.m11) - (this.m01 * this.m10);

        if (Math.abs(det) < 0.0001) {
            throw new SingularMatrixException();
        }

        recip = 1.0 / det;

        c00 = this.m11 * recip;
        c01 = -this.m01 * recip;
        c10 = -this.m10 * recip;
        c11 = this.m00 * recip;

        c02 = (-c01 * this.m12) - (c00 * this.m02);
        c12 = (-c10 * this.m02) - (c11 * this.m12);

        this.m00 = c00;
        this.m01 = c01;
        this.m02 = c02;
        this.m10 = c10;
        this.m11 = c11;
        this.m12 = c12;
    }

    /**
     * Transforms the point <code>pointIn</code> using this matrix and places the result into
     * <code>pointOut</code>.
     *
     * @param  pointIn   the point to be transformed
     * @param  pointOut  the point into which the transformed values are placed
     */
    public void transformPoint(final Point2Int pointIn, final Point2Int pointOut) {

        pointOut.setPos((this.m00 * pointIn.getPosX()) + (this.m01 * pointIn.getPosY()) + this.m02,
            (this.m10 * pointIn.getPosX()) + (this.m11 * pointIn.getPosY()) + this.m12);
    }

    /**
     * Transforms the vector <code>vecIn</code> using this matrix and places the result into <code>
     * vecOut</code>.
     *
     * @param  vecIn   the vector to be transformed
     * @param  vecOut  the vector into which the transformed values are placed
     */
    public void transformVec(final Vector2Int vecIn, final Vector2Int vecOut) {

        vecOut.setVec((this.m00 * vecIn.getVecX()) + (this.m01 * vecIn.getVecY()),
            (this.m10 * vecIn.getVecX()) + (this.m11 * vecIn.getVecY()));
    }
}
