/* CookieJarTupleList ADT, Immutable Method-Chaining Funtions
   Using immutable Scala Lists of pairs
   CSci 555: Functional Programming, Spring 2016
   H. Conrad Cunningham, Professor
   Computer and Information Science
   University of Mississippi

1234567890123456789012345678901234567890123456789012345678901234567890

2013-03-09: Developed immutable Scala version from 2010-12 mutable
            Scala version
2018-10-01: Cleaned up format
2022-04-18: Scala 3 compatibility check, minor comment changes

*/

/*
Class ICookieJarTupleList implements the CookieJar ADT using a Scala
List of pairs to represent the jar (mathematical bag) of cookies.  The
first component of a pair is the CookieType and the second is the
nonzero count of cookies with that type in the jar.  Each CookieType
appears at most once in the list of tuples.

   IMPLEMENTATION INVARIANT: 
     (ForAll c : c IN CookieType ::
                 jar.filter(_ == (c,_)).length <= 1) &&
     (ForAll c, i : c IN CookieType && i IN Int ::
                    jar.filter(_ == (c,i)).length == 1 <=> i>0 &&
		    OCCURENCES(c,Bag) = i)

See the comments in ICookieJar.scala and ICookieJarList.scala for more
information on this family of ADTs.

TODO: Consider whether the primary constructor should be "private".

*/

class ICookieJarTupleList[CookieType]
  (private val jar: List[(CookieType,Int)])
  extends ICookieJar[CookieType] {
 
  def putIn(cookie: CookieType) =
    jar.span(_._1 != cookie) match {
      case (l,(_,i)::r) => 
        new ICookieJarTupleList(l ++ ((cookie,i+1)::r))
      case _ => 
        new ICookieJarTupleList((cookie,1) :: jar)
    }
 
  def eat(cookie: CookieType): ICookieJar[CookieType] =
    jar.span(_._1 != cookie) match {
      case (l,(_,i)::r) =>
        new ICookieJarTupleList(l ++ 
                (if (i>1) (cookie,i-1)::r else r))
      case _ => 
        sys.error("Attempt to eat cookie \""
                  + cookie + "\", which is not in the jar.")
    }
      
  def isEmpty: Boolean = jar.isEmpty

  def has(cookie: CookieType): Boolean = 
    !jar.filter(_._1 == cookie).isEmpty

  /* Redefine toString to return form "ICookieJar(e1,e2,e3,...,en)"
     with all elements of bag in arbitrary order.
  */

  override def toString = "ICookieJar(" + jarList.mkString(",") + ")"


  /* Private method jarList converts the tuple list jar to a list 
     with appropriate duplicates.
  */

  private def jarList: List[CookieType] = 
    for ((c,i) <- jar ; j <- 1 to i) yield c

}
