type Dimensions = {
    m->Number
    n->Number
    ==(_:DimType)->Boolean
    asString->String}

type MathMatrix = {
    dimensions->Dimensions
    setValue(_:Number)atRow(_:Number)column(_:Number)->Void
    atRow(_:Number)column(_:Number)->Number
    add(_:MathMatrix)->MathMatrix
    sub(_:MathMatrix)->MathMatrix
    scalarMult(_:Number)->MathMatrix
    getRow(_:Number)->Dynamic
    getCol(_:Number)->Dynamic
    matrixMult(_:MathMatrix)->MathMatrix
    transpose->MathMatrix
    asString->String}

class MatrixDimensions.new(r:Number, c:Number){
    def m:Number is public, readable = r
    def n:Number is public, readable = c
    method ==(other:Dimensions)->Boolean is public{
        (m == other.m) && (n == other.n)}
    method asString->String is public{"{m}x{n}"}
}

//constructs matrix with r rows, c columns, and all entries set to 0
method withRows(r:Number)columns(c:Number)->MathMatrix{Matrix.new(r, c)}

class Matrix.new(numRows:Number, numCols:Number){

    //array containing rows of matrix
    var index is readable := PrimitiveArray.new(numRows)

    def dimensions:Dimensions is public, readable := 
        MatrixDimensions.new(numRows, numCols)

    //create rows, all values start off set to 0
    for(0..(numRows - 1)) do {i->
        var thisRow := PrimitiveArray.new(numCols)
        index[i] := thisRow
        for(0..(numCols - 1)) do {j->
            thisRow[j] := 0}
    }

    // sets entry at row i, column j to val
    method setValue(val:Number)atRow(i:Number)column(j:Number)->Void is public{
        if((i < 1)||(j < 1)||(i > dimensions.m)||(j > dimensions.n)) then{
            print "Invalid index {i}, {j}"}
        else{
            var theRow := index[i - 1]
            theRow[j - 1] := val}
    }

    // returns entry at row i, column j, or 0 if dimensions are invalid
    method atRow(i:Number)column(j:Number)->Number is public{
        if((i < 1)||(j < 1)||(i > dimensions.m)||(j > dimensions.n)) then{
            print "Invalid index; returning 0"
            0}
        else{
           var theRow := index[i - 1]
           theRow[j - 1]}
    }

    // returns sum of this matrix to other, or
    // other if the dimensions are not the same
    method add(other:MathMatrix)->MathMatrix is public{
        if(!(dimensions == other.dimensions)) then {
            print "Invalid dimensions; can't add. Returning argument."
            return other}
        else{
            var result := Matrix.new(dimensions.m, dimensions.n)
            for(1..dimensions.m) do{i->
                for(1..dimensions.n) do {j->
                    result.setValue(atRow(i)column(j) + other.atRow(i)column(j))
                        atRow(i)column(j)}
            }
            return result}
    }

    // returns difference of this matrix and other, or
    // other if the dimensions are not the same
    method sub(other:MathMatrix)->MathMatrix is public{
        if(!(dimensions == other.dimensions)) then {
            print "Invalid dimensions; can't subtract. Returning argument."
            return other}
        else{
            var result := Matrix.new(dimensions.m, dimensions.n)
            for(1..dimensions.m) do{i->
                for(1..dimensions.n) do {j->
                    result.setValue(atRow(i)column(j) - other.atRow(i)column(j))
                        atRow(i)column(j)}
            }
            return result}
    }

    //returns result of multiplying this matrix by the scalar lambda
    method scalarMult(lambda:Number)->MathMatrix is public{
        var result := Matrix.new(dimensions.m, dimensions.n)
        for(1..dimensions.m) do{i->
            for(1..dimensions.n) do {j->
                result.setValue(lambda * atRow(i)column(j))atRow(i)column(j)}
        }
        return result
    }

    // returns the ith row of the matrix as an array, or an empty array
    // if i is not a valid row entry
    method getRow(i:Number)->Dynamic is public{
        if((i < 1) || (i > dimensions.m)) then{
            print "Invalid row index. Returning [empty]."
            return PrimitiveArray.new(1)}
        else{return index[i-1]}
    }

    // returns jth column of the matrix as an array, or an empty array
    // if j is not a valid column entry
    method getCol(j:Number)->Dynamic is public{
        if((j < 1) || (j > dimensions.n)) then{
            print "Invalid col index. Returning [empty]."
            return PrimitiveArray.new(1)}
        else{
            var result := PrimitiveArray.new(dimensions.m)
            for(0..(dimensions.m - 1)) do{row->
                var thisRow := index[row]
                result[row] := thisRow[j-1]}
            return result
        }
    }

    // returns the product of this matrix and other, or other if the
    // dimensions are incorrect for matrix multiplication (# columns
    // of this should equal # rows of other)
    method matrixMult(other:MathMatrix)->MathMatrix is public{
        if(dimensions.n != other.dimensions.m) then{
            print "Invalid dimensions; can't multiply. Returning argument."
            return other}
        else{
            var result:MathMatrix := 
                Matrix.new(dimensions.m, other.dimensions.n)
            for(1..dimensions.m) do {i->
                var rowVector := getRow(i)
                for(1..other.dimensions.n) do{j->
                    var colVector := other.getCol(j)
                    var sum := 0
                    for(0..(rowVector.size - 1)) do {k->
                        sum := sum + rowVector[k] * colVector[k]}
                    result.setValue(sum)atRow(i)column(j)}
            }
            return result}
    }

    // returns the transpose of this matrix
    method transpose->MathMatrix is public{
        var result:MathMatrix := Matrix.new(dimensions.n, dimensions.m)
        for(1..dimensions.n) do{i->
            for(1..dimensions.m) do{j->
                result.setValue(atRow(j)column(i))atRow(i)column(j)}
        }
        return result}

    // returns string representation of this matrix
    method asString->String is public{
        var s:String := "{dimensions} matrix:\n"
        for(0..(index.size - 1)) do {i->
            var thisRow := index[i]
            for(0..(thisRow.size - 1)) do {j->
                s := s ++ thisRow[j] ++ " "}
            s := s ++ "\n"}
        return s}
}