/****************************************************************/
/**** An Ordered Bag Data-type, with Implicit Views *************/ 
/****************************************************************/

sealed abstract class Bag[A] (implicit c: A => Ord[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(e, _) if (x  > e) => false 
      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 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]()(implicit c: A => Ord[A]) extends Bag[A]
case class Plus[A](elt: A, rest: Bag[A])(implicit c: A => Ord[A]) extends Bag[A]

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

object OrdInstances {
  
  // implicit means use automatically as implicit parameter
  implicit def intOrd(x: Int) = new Ord[Int] {
    def cmp(that: Int) : Int = x - that
  }
 
  // implicit means use automatically as implicit parameter
  implicit def stringOrd(x: String) = new Ord[String] {
    def cmp(that: String) : Int = x compare that
  }
  
  //// implicit means use automatically as implicit parameter
  //implicit def tup2Ord[A, B](x: (A, B))
  //             (implicit cmpA: A => Ord[A], cmpB: B => Ord[B]) 
  //  = new Ord[(A, B)] {
  //      def cmp(that: (A, B)) : Int = { 
  //        val c1 = x._1 cmp that._1
  //        if (c1 != 0) c1 else { x._2 cmp that._2 } 
  //      }
  //    }

  // implicit means use automatically as implicit parameter
  implicit def tup2OrdNew[A <% Ord[A], B <% Ord[B]](x: (A, B))
    = new Ord[(A, B)] {
        def cmp(that: (A, B)) : Int = { 
          val c1 = x._1 cmp that._1
          if (c1 != 0) c1 else { x._2 cmp that._2 } 
        }
      }



}

object BagTest {

  import OrdInstances._
  
  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
  val b1   = Bag("cat", "dog", "monkey")
  val tmp3 = b1 contains "cat"
  val tmp4 = b1 union b1
  val b1a  = b1 add "hippo"
  val b2   = Bag(("cat", 3), ("dog", 4), ("monkey", 5))
}
