/****************************************************************/
/**** 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 { 
  def json: JVal
}

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

object Json { 

  implicit def stringJson(s: String) = 
    new Json {
      def json: JVal = JStr(s)
    }

  implicit def intJson(i: Int) = 
    new Json { 
      def json: JVal = JNum(i)
    }

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

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

object JsonTest {
  import Json._
  
  val j0 = 1.json
  val j1 = "taco".json
  val j2 = (1, "taco").json
  val j3 = (1, ("cat", (3, ("lion", (5, "Null"))))).json

}
