/****************************************************************/
/**** PA6 Prelude: Case Classes *********************************/
/****************************************************************/

/* In Ocaml:
   type jval = JStr of string
             | JNum of int
             | JObj of (string, jval) Map.t
             | JArr of jval list
*/

sealed abstract class JVal {
  // Can put methods in the class itself
  // [Sealed]: compiler knows these are the ONLY subclasses of JVal
  //           hence [this] MUST match one of these 4 patterns
  // Thus, ML ADT ~ OO Subtyping with pattern matching!
  override def toString : String =
    this match {
      case JStr(s) => "\"%s\"".format(s)
      case JNum(n) => n.toString
      case JObj(m) => m.map(p => p._1 + " : " + p._2.toString)
                       .mkString("{", ", ", "}")
      case JArr(a) => a.map(_.toString)
                       .mkString("{", ", ", "}")
    }
}

case class JStr(s: String)            extends JVal
case class JNum(n: BigDecimal)        extends JVal
case class JObj(o: Map[String, JVal]) extends JVal
case class JArr(a: List[JVal])        extends JVal

object JValTest {

  val jv1 = JArr(List(JNum(1), JNum(2), JNum(3), JNum(4)))

  val jv2 = JArr(List(JObj(Map( "word" -> JStr("one")
                              , "num" -> JNum(1)))
                    , JObj(Map( "word" -> JStr("two")
                              , "num" -> JNum(2)))
                    , JObj(Map( "word" -> JStr("three")
                              , "num" -> JNum(3)))))

  // Can instead put functions over [JVal] in other places
  def toString(jv: JVal) : String =
    jv match {
      case JStr(s) => "\"%s\"".format(s)
      case JNum(n) => n.toString
      case JObj(m) => m.map(p => p._1 + " : " + toString(p._2))
                       .mkString("{", ", ", "}")
      case JArr(a) => a.map(toString(_))
                       .mkString("{", ", ", "}")
    }
}


/******************************************************************/
/*************** The Json TypeClass *******************************/
/******************************************************************/

//http://marakana.com/s/scala_typeclasses,1117/index.html

trait Json[A] {
  def json(thing: A): JVal
}

/******************************************************************/
/*************** Creating Json Instances **************************/
/******************************************************************/

object Json {

  def toJson[A](thing: A)(implicit json: Json[A]) = json.json(thing)

  implicit object StringJson extends Json[String] {
    def json(thing: String) = JStr(thing)
  }

  implicit object IntJson extends Json[Int] {
    def json(thing: Int) = JNum(thing)
  }

  implicit def tup2Json[A1 : Json, A2 : Json] =
    new Json[(A1, A2)] {
      def json(thing: (A1, A2)): JVal = JObj(Map(
         "fst" -> Json.toJson(thing._1)
       , "snd" -> Json.toJson(thing._2)
      ))
    }
}

/******************************************************************/
/*************** Automatic Json Conversion! ***********************/
/******************************************************************/

object JsonTest {

  val j0 = Json.toJson(1)
  val j1 = Json.toJson("taco")
  val j2 = Json.toJson((1, "taco"))
  val j3 = Json.toJson((1, ("cat", (3, ("lion", (5, "Null"))))))

}
