package basics.math.algebra;

public class Matrix {

    public double fields[][];

    public Matrix() {
        this.fields = new double[4][4];
    }

    public static Matrix translation(Vector offset) {
        Matrix result = new Matrix();
        result.toIdentity();

        result.fields[3][0] = offset.x;
        result.fields[3][1] = offset.y;
        result.fields[3][2] = offset.z;

        return result;
    }

    public static Matrix scale(Vector scale) {
        Matrix result = new Matrix();
        result.toIdentity();

        result.fields[0][0] = scale.x;
        result.fields[1][1] = scale.y;
        result.fields[2][2] = scale.z;

        return result;
    }

    public static Matrix rotationX(double theta) {
        Matrix result = new Matrix();

        double cos = Math.cos(theta);
        double sin = Math.sin(theta);

        result.fields = new double[][]{
                {1, 0, 0, 0},
                {0, cos, -sin, 0},
                {0, sin, cos, 0},
                {0, 0, 0, 1}
        };

        return result;
    }

    public static Matrix rotationY(double theta) {
        Matrix result = new Matrix();

        double cos = Math.cos(theta);
        double sin = Math.sin(theta);

        result.fields = new double[][]{
                {cos, 0, sin, 0},
                {0, 1, 0, 0},
                {-sin, 0, cos, 0},
                {0, 0, 0, 1}
        };

        return result;
    }

    public static Matrix rotationZ(double theta) {
        Matrix result = new Matrix();

        double cos = Math.cos(theta);
        double sin = Math.sin(theta);

        result.fields = new double[][]{
                {cos, -sin, 0, 0},
                {sin, cos, 0, 0},
                {0, 0, 1, 0},
                {0, 0, 0, 1}
        };

        return result;
    }

    public static Matrix rotation(Vector angles) {
        Matrix rotX = Matrix.rotationX(angles.x);
        Matrix rotY = Matrix.rotationX(angles.y);
        Matrix rotZ = Matrix.rotationX(angles.z);

        return rotX.mul(rotY).mul(rotZ);
    }

    public void toIdentity() {
        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                if (x == y) {
                    this.fields[x][y] = 1;
                } else {
                    this.fields[x][y] = 0;
                }
            }
        }
    }

    public Matrix scale(double factor) {
        Matrix result = new Matrix();

        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                result.fields[x][y] *= factor;
            }
        }

        return result;
    }

    public Matrix mul(Matrix other) {
        Matrix result = new Matrix();

        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                for (int v = 0; v < 4; v++) {
                    result.fields[x][y] += this.fields[v][x] * other.fields[y][v];
                }
            }
        }

        return result;
    }

    public Vector mul(Vector vector) {
        Vector result = Vector.origin();

        result.x = this.fields[0][0] * vector.x + this.fields[1][0] * vector.y + this.fields[2][0] * vector.z + this.fields[3][0];
        result.y = this.fields[0][1] * vector.x + this.fields[1][1] * vector.y + this.fields[2][1] * vector.z + this.fields[3][1];
        result.z = this.fields[0][2] * vector.x + this.fields[1][2] * vector.y + this.fields[2][2] * vector.z + this.fields[3][2];

        return result;
    }

    public Matrix add(Matrix other) {
        Matrix result = new Matrix();

        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                result.fields[x][y] = this.fields[x][y] + other.fields[x][y];
            }
        }

        return result;
    }

    public Matrix sub(Matrix other) {
        Matrix result = new Matrix();

        for (int x = 0; x < 4; x++) {
            for (int y = 0; y < 4; y++) {
                result.fields[x][y] = this.fields[x][y] - other.fields[x][y];
            }
        }

        return result;
    }

    @Override
    public String toString() {
        String value = "";

        for (int x = 0; x < 4; x++) {
            value += String.format("{ %f, %f, %f, %f }\n", this.fields[0][x], this.fields[1][x], this.fields[2][x], this.fields[3][x]);
        }

        return value;
    }
}
