namespace Matrices {

    class Matrix {
        /*
            nativeData[0] -> Array<float> data
            nativeData[1] -> width
            nativeData[2] -> height
            nativeData[3] -> Array<Value> data as values
            nativeData[4] -> bool: data as values are dirty?
            nativeData[5] -> string: error
            nativeData[6] -> Array<float> writeBuffer: new data gets written here on multipliccations and then pointers are swapped with data.
        */

        field width;
        field height;

        constructor(height, width) {
            this.width = width;
            this.height = height;
            $$$('allocNativeData', this, 7);
            if ($lib_matrices_initMatrix(this, width, height)) this.argError();
        }

        static function newIdentityMatrix(size) {
            matrix = new Matrices.Matrix(size, size);
            index = 0;
            for (i = 0; i < size; ++i) {
                matrix.setValue(i, i, 1.0);
            }
            return matrix;
        }

        function getValue(row, col) {
            return $lib_matrices_getValue(this, col, row) ?? this.argError();
        }

        function setValue(row, col, value) {
            return $lib_matrices_setValue(this, col, row, value) ?? this.argError();
        }

        function multiplyByScalar(value, output = null) {
            return $lib_matrices_multiplyScalar(this, value, output, output is Matrices.Matrix) ?? this.argError();
        }

        function addMatrix(other, output = null) {
            return $lib_matrices_addMatrix(this, other, other is Matrices.Matrix, output, output is Matrices.Matrix) ?? this.argError();
        }

        function multiplyByMatrix(other, output = null) {
            return $lib_matrices_multiplyMatrix(this, other, other is Matrices.Matrix, output, output is Matrices.Matrix) ?? this.argError();
        }

        function toVector(output) {
            while (output.length < this.width * this.height) {
                output.add(0.0);
            }
            output[0] = $lib_matrices_getValue(this, 0, 0);
            return $lib_matrices_toVector(this, output) ?? this.argError();
        }

        function copyFrom(other) {
            return $lib_matrices_copyFrom(this, other, other is Matrices.Matrix) ?? this.argError();
        }

        function argError() { throw new Core.InvalidArgumentException($lib_matrices_getError(this)); }
    }
}
