import structures_lib
import association

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

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

type Collection = {
    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)->Collection}

type Set = {
    clear->Void
    isEmpty->Void
    add(_:T)->Void
    removeValue(_:T)->T
    contains(_:T)->Boolean
    containsAll(_:Set)->Boolean
    clone->Set
    addAll(_:Set)->Void
    retainAll(_:Set)->Void
    removeAll(_:Set)->Void
    iterator->Iterator
    size->Number
    asString->String}

type Map = {
    size->Number
    isEmpty->Boolean
    containsKey(_:K)->Boolean
    containsValue(_:V)->Boolean
    valueAt(_:K)->V
    put(_:V)at(_:K)->V
    removeValueAt(_:K)->V
    putAll(_:Map)->Void
    clear->Void
    keySet->Set
    values->Collection
    entrySet->Set
    asString->String}
    //equals(_:Map)->Boolean //not until better equality
    //hashcode->Number  //at the moment only String has hashcode method

def missing:Dynamic is public, readable = structures_lib.missing

// Constructs an empty map
method new->Map is public{MapList.new}

class MapList.new{
    // List for storing entries in the map
    var data:Collection is readable := structures_lib.newList
    
    // @post @return number of entries in the map
    method size->Number is public{data.size}

    // @post @return true iff map does not contain any entries
    method isEmpty->Boolean is public{data.isEmpty}

    // @pre k is not "missing"
    // @post @return true iff k is key mapped to a value
    method containsKey(k:Dynamic)->Boolean is public{
        data.contains(association.withKey(k))}

    // @pre v is not "missing"
    // @post @return true iff v is target of at least one map entry
    method containsValue(v:Dynamic)->Boolean is public{
        var i:Iterator := association.valueIterator(data.iterator)
        while{i.hasNext} do {
            var value:Dynamic := i.next
            if(v == value) then{return true}
        }
        false}

    // @pre k is a key, possibly in map
    // @post @return value mapped to from k, or missing
    // if no such entry
    method valueAt(k:Dynamic)->Dynamic is public{
        var i:Number := data.indexOf(association.withKey(k))
        if(i > 0) then{data.at(i).getValue}
        else{missing}
    }

    // @pre k and v are not "missing"
    // @post inserts mapping from k to v in map
    // @return value formerly associated with k, or
    // missing if no such entry
    method put(v:Dynamic)at(k:Dynamic)->Dynamic is public{
        var temp:Association := association.withKey(k)value(v)
        var result:Association := data.removeValue(temp)
        data.addLast(temp)
        match(result)
            case{gt:Association-> gt.getValue}
            case{gt-> missing}
    }
    
    // @pre k is not "missing"
    // @post removes any mapping from k to a value
    // @return the value formerly associated with k, or
    // missing if no such entry
    method removeValueAt(k:Dynamic)->Dynamic is public{
        var v:Dynamic := data.removeValue(association.withKey(k))
        match(v)
            case{gt:Association-> v.getValue}
            case{gt-> missing}
    }
    
    // @post all mappings of other installed in this map,
    // overriding any conflicting maps
    method putAll(other:Map)->Void is public{
        var i:Iterator := other.entrySet.iterator
        while{i.hasNext} do{
            var e:Association := i.next
            put(e.getValue)at(e.getKey)}
    }

    // @post removes all map entries associated with this map
    method clear->Void is public{data.clear}

    // @post @return set of all keys associated with this map
    method keySet->Set is public{
        var result:Set := structures_lib.newSet
        var i:Iterator := data.iterator
        while{i.hasNext} do{
            var a:Association := i.next
            result.add(a.getKey)}
        result
    }

    // @post @return collection containing range of map (all values mapped to)
    method values->Collection is public{
        var result:Collection := structures_lib.newList
        var i:Iterator := association.valueIterator(data.iterator)
        while{i.hasNext} do{result.addLast(i.next)}
        result}
  
    // @post @return set of key-value pairs contained in map
    method entrySet->Set is public{
        var result:Set := structures_lib.newSet
        var i:Iterator := data.iterator
        while{i.hasNext} do {
            var a:Association := i.next
            result.add(a)}
        result}

    // @post @return string representation of map
    method asString->String is public{
        var s:String := "
            s := s ++ "\n{data.at(i).getKey}={data.at(i).getValue}"}
        s := s ++ ">"
        return s}
}