/****************************************************************/
/**** Ordered Bag Data-type, elements kept in Increasing order **/ 
/****************************************************************/

sealed abstract class Bag[A <: Ord[A]] { //FIX
  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(e, _) if (x  < e) => false 
      case Plus(_, rest)          => rest.contains(x) 
    }
  }

  def add(x: A) : Bag[A] = 
    this match {
      case Plus(e, rest) if (x > e)  => Plus(e, rest add x)
      case Plus(e, _)    if (x == e) => this
      case _                         => 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 Plus(e, rest) if (x <  e) => this // no need to look further
      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("Bag(", ", ", ")")
}

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

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

object BagTest {
 
  // FIX
  // Will not compile as Int not <: Ord[Int] (yuck!)
  //val b0   = Bag(1,2,3,4)
  //val b0a  = Bag(1, 6, 12, 5)
  //val tmp0 = b0 union b0a
  //val tmp1 = b0 contains 2
  //val tmp2 = b0 contains 20

  // Here's a version of Int that is Ordered ...
  case class I(x: Int) extends Ord[I] {
    def cmp(that: I) = x - that.x
  }
  
  // Now we can use the I versions...
  val b0   = Bag(I(1), I(2), I(3),  I(4))
  val b0a  = Bag(I(1), I(6), I(12), I (5))
  val tmp0 = b0 union b0a
  val tmp1 = b0 contains I(2)
  val tmp2 = b0 contains I(20)

  // Here's a version of String that is Ordered ...
  case class S(x: String) extends Ord[S] {
    def cmp(that: S) = x compare that.x //"builtin" compare function for strings
  }
 
  // Now we can use the S versions...
  val b1   = Bag(S("cat"), S("dog"), S("monkey"))
  val tmp3 = b1 contains S("cat")
  val tmp4 = b1 union b1
  val b1a  = b1 add S("hippo")

  case class P[A <: Ord[A], B <: Ord[B]](x: A, y: B) 
    extends Ord[P[A, B]]{
    def cmp(that: P[A, B]) = {
      val res = x cmp that.x
      if (res == 0) res else { y cmp that.y }
    }
  }

  // Will not compile as (String, Int) not <: Ord[(String, Int)] (yuck!)
  val b2   = Bag(P(S("cat"), I(3)), P(S("dog"), I(4)), P(S("monkey"), I(5)))
}
