import io
import math_matrix

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

var ostream := io.output
var istream := io.input

def default:MathMatrix = math_matrix.withRows 1 columns 1

var matrixA:MathMatrix := default
var matrixB:MathMatrix := default
var matrixC:MathMatrix := default

//returns line entered into standard input
method getLine->String{
    var line:String := ""
    var nxt := istream.next
    while{nxt != "\n"} do {
        line := line ++ nxt
        nxt := istream.next}
    return line}

//returns true if string only contains digits
method checkString(entry:String)->Boolean{
    var entIter := entry.iter
    while{entIter.havemore} do {
        var nxt := entIter.next
        if((nxt.ord < 48) || (nxt.ord > 57)) then{return false}
    }
    true}

//returns matrix selected by user
method getMatrix(chosen:String)->MathMatrix{
    if(chosen == "A") then{matrixA}
    else{if(chosen == "B") then{matrixB}
        else{if(chosen == "C") then{matrixC}
            else{
                ostream.write("Invalid entry; must be A B or C: ")        
                getMatrix(getLine)}}}
}

//asks user to input desired numbers of rows and columns, creates
//empty matrix with those dimensions, and has user input values for
//each entry
//if user inputs invalid entry for dimensions or values, returns
//default 1x1 matrix
method makeMatrix->MathMatrix{
    ostream.write("Enter number of rows: \n")
    var entered:String := getLine
    var rows:Number := 0
    if(checkString(entered)) then{rows := entered.asNumber}
    else{
        ostream.write("Invalid entry\n")
        return default}
    ostream.write("Enter number of columns: \n")
    entered := getLine
    var cols:Number := 0
    if(checkString(entered)) then{cols := entered.asNumber}
    else{
        ostream.write("Invalid entry\n")
        return default}
    var myMatrix:MathMatrix := if((cols > 0) && (rows > 0)) then{
                        math_matrix.withRows(rows)columns(cols)}
                    else{
                        ostream.write("Invalid dimensions\n")
                        default}
    for(1..rows) do {i->
        ostream.write("Row {i} :\n")
        for(1..cols) do {j->
            ostream.write("{i}, {j} = ")
            entered := getLine
            if(checkString(entered)) then{
                myMatrix.setValue(entered.asNumber)atRow(i)column(j)}
            else{myMatrix.setValue 0 atRow(i)column(j)}
        }
    }           
    return myMatrix}

//allows user to set dimensions and entries of selected matrix
method edit->Void{
    ostream.write("Available matrices: A B C\n")
    var choose := getLine
    if(choose == "A") then{matrixA := makeMatrix}
    else{if(choose == "B") then{matrixB := makeMatrix}
         else{if(choose == "C") then{matrixC := makeMatrix}
              else{ostream.write("Invalid entry\n")}}}}

//allows user to view selected matrix
method get->Void{
    ostream.write("Available matrices: A B C\n")
    var choose := getLine
    ostream.write("{getMatrix(choose)}\n")}

//allows user to perform basic math on matrices and displays result
//does not currently allow operations on more than two matrices
method math->Void{
    ostream.write("Available methods: + - * t\n")
    var c := getLine
    if(c == "*") then{
        ostream.write("Enter s for scalar, m for matrix\n")
        c := getLine
        if(c == "s") then{  // scalar multiplication
            ostream.write("First argument, expecting number: ")
            c := getLine
            var lambda:Number := 0
            if(checkString(c)) then{lambda := c.asNumber}
            else{
                ostream.write("Invalid entry\n")
                return None}
            ostream.write("Second argument (A B C): ")
            c := getLine
            store(getMatrix(c).scalarMult(lambda))}
        else{if(c == "m") then{  // matrix multiplication
            ostream.write("First argument (A B C): ")
            c := getLine
            var first:MathMatrix := getMatrix(c)
            ostream.write("Second argument (A B C): ")
            c := getLine
            var second:MathMatrix := getMatrix(c)
            if(first.dimensions.n != second.dimensions.m) then {
                ostream.write("Invalid dimensions\n")}
            else{store(first.matrixMult(second))}}}}
    else{if((c == "+") || (c == "-")) then{ //addition and subtraction
            var op:String := c
            ostream.write("First argument (A B C): ")
            c := getLine
            var first:MathMatrix := getMatrix(c)
            ostream.write("Second argument (A B C): ")
            c := getLine
            var second:MathMatrix := getMatrix(c)
            if(first.dimensions == second.dimensions) then{
                if(op == "+") then {store(first.add(second))}
                else{store(first.sub(second))}}
            else{ostream.write("Invalid dimensions\n")}}
        else{if(c == "t") then {  //transpose
                ostream.write("Argument (A B C): ")
                store(getMatrix(getLine).transpose)}
            else{ostream.write("Invalid method\n")}}}}

//allows user to store result of computation in slot A, B, or C
method store(result)->Void{
    ostream.write("{result}\n")
    ostream.write("Store result? y/n\n")
    var c:String := getLine
    if(c == "y") then {
        ostream.write("Available matrices: A B C\n")
        c := getLine
        if(c == "A") then {matrixA := result}
        else{if(c == "B") then{matrixB := result}
            else{if(c == "C") then{matrixC := result}
                else{ostream.write("Invalid entry\n")}}}
        }
}

ostream.write("Options: get math edit quit\n")

//reads user input and executes until user inputs "quit"
var command:String := getLine
while{command != "quit"} do {
    if(command == "edit") then{edit}
    else{if(command == "get") then{get}
         else{if(command == "math") then{math}
             else{ostream.write("Invalid entry\n")}}}
    ostream.write("Options: get math edit quit\n")
    command := getLine}