import vector
import stringplus
import association

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

type Vector = {
    size->Number
    isEmpty->Boolean
    clear->Void
    contains(_:T)->Boolean
    addFirst(_:T)->Void
    addLast(_:T)->Void
    firstElement->T
    lastElement->T
    removeFirst->T
    removeLast->T
    removeValue(_:T)->T
    indexOf(_:T)->Number
    at(_:Number)->T
    setValue(_:T)at(_:Number)->T
    add(_:T)at(_:Number)->Void
    removeFromIndex(_:Number)->T
    iterator->Iterator
    asString->String
    forEachDo(_:Block)->Void
    map(_:Block)->Vector
    ensureCapacity(_:Number)->Void
    capacity->Number
    copyInto(_)->Void
    indexOf(_:T)startingFrom(_:Number)->Number
    setSize(_:Number)->Void
    trimToSize->Void}

type Association = {
     ==(_:Association)->Boolean
     hashcode->Number
     getValue->V
     getKey->K
     setValue(_:V)->V
     asString->String}

type StringPair = {
    getFirst->String
    getSecond->String
    hashcode->Number
    ==(_:StringPair)->Boolean
    asString->String}

type WordStream = {
     addLexItems(_:String)->Void
     nextToken->String
     hasMoreTokens->Boolean
     resetTo(_:Number)->Void
     currentIndex->Number}

type FreqList = {
    add(_:String)->Void
    get(_:Number)->String
    asString->String}

//======PAIR=====

method newPair(p1:String, p2:String)->StringPair is public{Pair.new(p1, p2)}

class Pair.new(fst:String, snd:String){

    var first:String is readable := fst

    var second:String is readable := snd

    method getFirst->String is public{first}

    method getSecond->String is public{second}

    // Won't be used, but necessary so that StringPair can be used
    // as key of Association
    method hashcode->Number is public{first.hashcode + second.hashcode}

    method ==(other:StringPair)->Boolean is public{
        (first == other.getFirst) && (second == other.getSecond)}

     method asString->String is public{"<{first}, {second}>"}
}
//======WORDSTREAM======

method newWordStream->WordStream is public{WordStreamClass.new}

class WordStreamClass.new{

     // list of words and punctuation from input text
    var lexItems:Vector is readable := vector.new

    // index of next item to be returned from nextToken method
    var nextItem:Number is readable := 1

    // characteres indicating end of word
    var delimiter:String is readable := " -'\";:,.!?\n\t"

    // @param line string containing words to be placed in list
    // of lexical items
    // @post all lexical items added to list
    method addLexItems(line:String)->Void is public{
        var strIter := line.iter
        var currItem:String := ""
        var nxt:String := ""

        // while line contains more characters, parses next character
        // if character is a delimiter, adds current word and
        // delimiter (if not whitespace) to list
        // and starts a new word
        // otherwise adds character onto current word
        while{strIter.havemore} do {
            nxt := strIter.next
            if(stringplus.contains(delimiter, nxt)) then{
                if(currItem != "") then {lexItems.addLast(currItem)}
                if(!isWhiteSpace(nxt)) then {lexItems.addLast(nxt)}
                currItem := ""}
            else{currItem := currItem ++ nxt}
        }
        if(currItem != "") then {lexItems.addLast(currItem)}
    }

    // @param item character from input
    // @return true iff item corresponds to whitespace (space, newline, or tab)
    method isWhiteSpace(item:String)->Boolean is confidential{
        return (item == " ") || (item == "\n") || (item == "\t")}

    // @return next non-whitespace token from list of lexical items
    method nextToken->String is public{
        var test:String := lexItems.at(nextItem)
        while{(nextItem <= lexItems.size) && isWhiteSpace(test.at(1))} do{
            nextItem := nextItem + 1
            test := lexItems.at(nextItem)}
        if (nextItem <= lexItems.size) then {
            nextItem := nextItem + 1
            return lexItems.at(nextItem - 1)}
        else{
            print "out of tokens"
            return ""}
    }

    // @return true iff there are more tokens to be returned from list
    method hasMoreTokens->Boolean is public{nextItem <= lexItems.size}

    // @post resets nextItem to specified index
    method resetTo(index:Number)->Void is public{nextItem := index}

    // @return the index of the next item to be considered
    method currentIndex->Number is public{nextItem}
}

//===============FREQLIST===============
method newFreqList->FreqList is public{FreqListClass.new}

class FreqListClass.new{
    // list of associations holding words and their frequencies
    var flist:Vector is readable := vector.new

    // total # of instances of words held in list
    var totalOccurrences:Number is readable := 0

    // @param word the word to be tallied in frequency list
    // @post if word was not in frequency list it is added
    // w/ frequency 1; if already in, increase frequency by 1
    method add(word:String)->Void is public{
        var assoc:Association := 
            association.withKey(word)value(0)
        var wordNo:Number := flist.indexOf(assoc)  //location of word in list
        if (wordNo == -1) then {  // word was not in list, insert it
            assoc.setValue(1)
            flist.addLast(assoc)}
        else{ // word was in list, bump frequency
            var element:Association := flist.at(wordNo)
            var newValue:Number := element.getValue + 1
            element.setValue(newValue)
        }
        totalOccurrences := totalOccurrences + 1
    }

    // @param prob probability chosen
    // @return word chosen tandomly from words in frequency list
    // The probability of a word being chosen is proportional to
    // its frequency strored in the list.
    method get(prob:Number)->String is public{
       var count:Number := 0  //sum of frequencies so far

       //number between 1 and size of list (inclusive)
       var target:Number := (prob * totalOccurrences).truncate

       var letterNo:Number := 1
       var element:Association
       while{(count <= target) && (letterNo <= flist.size)} do {
            element := flist.at(letterNo)
            var probOfElt:Number := element.getValue
            count := count + probOfElt
            letterNo := letterNo + 1
        }
        return element.getKey
    }
    method asString->String is public{"Frequency List: {flist}\n"}
}