/****************************************************************/
/**** A Generic Data-type ***************************************/ 
/****************************************************************/

/* In Ocaml: 
   type 'a bag = Empty 
               | Plus of ('a * 'a bag)
*/ 
sealed abstract class Bag[A] {
  def size : Int = 
    this match {
      case Empty()       => 0
      case Plus(_, rest) => 1 + rest.size
    }

  def contains(x: A) : Boolean = {
    this match {
      case Empty()                => false
      case Plus(e, _) if (x == e) => true  
      case Plus(_, rest)          => rest.contains(x) 
    }
  }

  def add(x: A) : Bag[A] = {
    if (this.contains(x)) this else { Plus(x, this) }
  }

  // Should remind you of a particular ML function?
  def union(that: Bag[A]) : Bag[A] = {
    that match {
      case Empty()       => this
      case Plus(x, rest) => this.add(x).union(rest) 
    }
  }

  def gimme: Option[A] = 
    this match {
      case Plus(x, rest) => Some(x)
      case _             => None
    }
  
  def remove(x: A) : Bag[A] = {
    this match {
      case Empty()                   => this
      case Plus(e, rest) if (x == e) => rest // requires no duplicates!
      case Plus(e, rest)             => Plus(e, rest.remove(x))
    }
  }

  def toList : List[A] = 
    this match {
      case Empty()       => List()
      case Plus(x, rest) => x :: (rest.toList)
    }

  override def toString : String = this.toList.mkString(",")
}

case class Empty[A]() extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A]) extends Bag[A]

/* A Companion Object that has various key methods */
object Bag {
  def apply[A](xs: A*) : Bag[A] = { 
    val s0 : Bag[A] = Empty()
    xs.toList.foldLeft(s0)(_ add _)  
  }
}

object BagTest {
  val b0  = Bag(1,2,3,4)
  val b0a = Bag(1, 6, 12, 5)
  val _   = b0 union b0a
  val _   = b0 contains 2
  val _   = b0 contains 20
  val b1  = Bag("cat", "dog", "monkey")
  val _   = b1 contains "cat"
  val  _  = b1 union b1
  val b1a = b1 add "hippo"
  val b2 = Bag(("cat", 3), ("dog", 4), ("monkey", 5))
}
