import io
import structures_lib

type Stack = {
    push(_:T)->Void
    pop->T
    peek->T
    size->Number
    clear->Void
    contains(_:T)->Boolean
    isEmpty->Boolean
    iterator->Iterator
    asString->String}

type Iterator = {
     reset->Void
     hasNext->Boolean
     next->T
     get->T}

type StateType = {
    addDigit(_:Number)->Void
    doOp(_:String)->Void
    enter->Void
    clear->Void
    pop->Void
    exchange->Void}

method new->StateType{State.new}

// Class representing internal state of calculator. It is responsible
// for keeping track of numbers entered and performing operations.

class State.new{

    // lacking graphics, this will be our "display" for results
    var display is readable:= io.output

    // stack of values on which to perform operations; will only contain Numbers
    var numStack:Stack is readable := structures_lib.newStack

    // list of digits added but not yet entered
    var digitsEntered:Stack is readable := structures_lib.newStack

    // number entered so far
    var currentNum:Number is readable := 0
    
    method addRec(i:Iterator)->Number is confidential{
        if(!i.hasNext) then {0}
        else{i.next + 10 * addRec(i)}
    }

    // User has "clicked" on a digit button 
    // i.e. has entered a digit into input
    // @post updates current number and displays it in text field
    method addDigit(value:Number)->Void is public{
        //reset current number, add value to list of digits added
        currentNum := 0
        digitsEntered.push(value)
        var iter:Iterator := digitsEntered.iterator
        currentNum := addRec(iter)
        display.write("Display: {currentNum}\n")
    }
    
    // User has "clicked" on operator button
    // i.e. has entered an operator into input
    // @pre stack is not empty, enough values to perform 
    // selected operation, and not dividing by 0
    // @post performs selected operation
    method doOp(op:String)->Void is public{
        enter //adds current # to stack
        var firstNum:Number := 0 //first num entered
        var secondNum:Number := 0 //second num entered
        var sign:String := op //operation sign entered
        var result:Number := 0 //result of operation

        //prints error message if fewer than two values
        // in stack
        if(numStack.size < 2) then{
            clear
            display.write("Error: not enough values for this operation\n")
        }
        else{
            //gets & removes top two values from stack
            secondNum := numStack.pop
            firstNum := numStack.pop
            //displays error message if user attempts to divide by 0
            if((sign == "/") && (secondNum == 0)) then {
                 clear
                 display.write("Error: attempted to divide by zero\n")
            }
            else{
                if(sign == "+") then {result := firstNum + secondNum}
                if(sign == "-") then {result := firstNum - secondNum}
                if(sign == "*") then {result := firstNum * secondNum}
                if(sign == "/") then {result := (firstNum / secondNum).truncate}
            }
            //add result to stack & display
            numStack.push(result)
            display.write("Display: {result}\n")
        }
    }

    // user has "clicked" enter button
    // i.e. user has entered "enter"
    // @pre list of digits entered not empty
    // @post add current number to stack
    method enter->Void is public{
        if(!digitsEntered.isEmpty) then {
            numStack.push(currentNum)
            currentNum := 0
            digitsEntered.clear}
    }

    // user has entered "clear"
    // @post clears stack
    method clear->Void is public{
        numStack.clear
        display.write("Display: 0\n")}

    // user has entered "pop"
    // @pre stack is not empty
    // @post removes top value from stack
    method pop->Void is public{
        enter
        if(numStack.isEmpty) then {display.write("Display: 0\n")}
        else{
            numStack.pop
            if(numStack.isEmpty) then {display.write("Display: 0\n")}
            else{display.write("Display: {numStack.peek}\n") }
        }
    }   
    
    // user has entered "exch"
    // @pre stack has at least two values
    // @post switches the top two values of stack
    method exchange->Void is public{
        enter
        if(numStack.size >= 2) then{
            var top:Number := numStack.pop
            var bottom:Number := numStack.pop
            numStack.push(top)
            numStack.push(bottom)
            display.write("Display: {numStack.peek}\n")
        }
        else{
            clear
            display.write("Display: Error\n")
        }
    }
}