/* CookieJar ADT, Mutable Object-Oriented Version
   Using Scala Lists of Pairs (CookieJarArray)
   CSci 556: Multiparadigm Programming, Spring 2012
   CSci 555: Functional Programming, Spring 2016
   H. Conrad Cunningham, Professor
   Computer and Information Science
   University of Mississippi

1234567890123456789012345678901234567890123456789012345678901234567890

2010-Spring: Developed from mutable Ruby version
2012-Spring: Minor updates
2016-03-09:  Some cleanup of Scala code and comments
2022-04-18:  Scala 3 compatibility check, added Unit to procedures

*/

/* Class CookieJarArray implements the CookieJar ADT using Scala's
   Array data structure and an integer counter to implement the jar
   (mathematical bag) of cookies.  The cookies occur in the array with
   indexes less than the counter the same number of times they do in
   the cookie jar.

   IMPLEMENTATION INVARIANT: 
     (0 <= end <= jar.length) &&
     (ForAll c : c IN CookieType ::
         OCCURRENCES(c,Bag) == jar.slice(0,end).filter(_==c).length )

   A production implementation of this class should probably have the
   initial array size and the expansion factor as parameters to the
   constructor, with an alternate constructor using the default
   values.

 */

class CookieJarArray[CookieType:scala.reflect.ClassTag]
       extends CookieJar[CookieType] {

  /* CookieJar's bag model is implemented by an Array "jar" of cookies
     and counter "end" that refers to the next empty position in the
     array.  The bag is represented by the portion of the array with
     indices 0 to end-1.
  */

  private var jar = new Array[CookieType](CookieJarArray.DEF_JAR_SIZE)
  private var end = 0

  def putIn(cookie: CookieType): Unit = { 
    if (end >= jar.length) expandArray
    jar(end) = cookie
    end += 1
  }
 
  def eat(cookie: CookieType): Unit = {
    var in = end-1
    while (in >= 0 && jar(in) != cookie)  // search back from end
      in -= 1                             //  to minimize copy
    if (in >= 0) {
      for (i <- in to end-2)
        jar(i) = jar(i+1)
      end -= 1
      //jar(end) = null <-- left out to test logic related to "end"
    }
    else 
      sys.error("Attempt to eat cookie \"" + cookie +
                 "\", which is not in the jar.")
  }
       
  def isEmpty: Boolean = (end == 0)

  def has(cookie: CookieType): Boolean = {
    for ( i <- 0 to end-1 )
      if (jar(i) == cookie)
        return true
    false
  }

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

  override def toString =
     "CookieJar(" + jar.slice(0,end).mkString(",") + ")"

  /* Internal method called to increase the array size when array jar
     is full and new cookies need to be added.  It doubles the size on
     each call.
  */

  private def expandArray: Unit = {
    // println("Expanding cookie jar array from size = " + end)
    val newJar  = new Array[CookieType](2 * jar.length)
    for (i <- 0 to jar.length-1)
      newJar(i) = jar(i)
    jar = newJar
 }
}


/* CookieJarArray companion object to hold configuration paramters of
   the CookieJarArray class. 
*/

object CookieJarArray {
  val DEF_JAR_SIZE = 1  // make very small to aid in testing
  
}
