package pl.edu.pw.mini.sg.efgame

import argonaut.{ CodecJson, DecodeJson, DecodeResult, EncodeJson, EncodeJsonKey, Json }
import java.lang.NumberFormatException
import scala.util.Try


object EfGameConfigJson {
  val ATTACKER_IS = "attackerIs"
  val DEFENDER_IS = "defenderIs"
  val leafNodeCodec : CodecJson[LeafGameNode] = CodecJson.casecodec2(LeafGameNode.apply, LeafGameNode.unapply)("defenderPayoff", "attackerPayoff")

  lazy val innerNodeEncode : EncodeJson[InnerGameNode] = EncodeJson{n : InnerGameNode => {
    implicit val eki : EncodeJsonKey[Int] = EncodeJsonKey.from(_.toString)
    implicit val x = gameNodeEncode

    (DEFENDER_IS, Json.jNumber(n.defenderIS)) ->: (ATTACKER_IS, Json.jNumber(n.attackerIS)) ->:
    EncodeJson.MapEncodeJson[Int, Map[Int, GameNode]].encode(n.successors)
  }}

  val gameNodeEncode : EncodeJson[GameNode] = EncodeJson(obj => obj match {
    case in: InnerGameNode => innerNodeEncode.encode(in)
    case leaf : LeafGameNode => leafNodeCodec.encode(leaf)
  })

  implicit def decodeIntMap[V](implicit vd: DecodeJson[V]) = DecodeJson[Map[Int,V]] { hc => {
    hc.fields match {
      case Some(fl) => fl.foldLeft(DecodeResult.ok(Map[Int, V]())){(mm, cur) =>
        for {
          map <- mm
          vv <- (hc --\ cur).as[V]
          kk <- Try{DecodeResult.ok(cur.toInt)}.recover{case pe: NumberFormatException => DecodeResult.fail(s"Move $cur should be integer", hc.history)}.get 
        } yield (map+((kk,vv)))
      }
      case None => DecodeResult.fail("Empty move list", hc.history)
    }
  }}

  lazy val innerNodeDecode: DecodeJson[InnerGameNode] = DecodeJson { cursor => {
    implicit val dd = gameNodeDecode
    for {
      attIs <- (cursor --\ ATTACKER_IS).as[Int]
      defIs <- (cursor --\ DEFENDER_IS).as[Int]
      map <- cursor.downField(ATTACKER_IS).deleteGoParent.downField(DEFENDER_IS).deleteGoParent.as[Map[Int, Map[Int,GameNode]]]
  } yield InnerGameNode(defIs, attIs, map) }}

  lazy val gameNodeDecode : DecodeJson[GameNode] = leafNodeCodec ||| innerNodeDecode

  implicit val efGameConfigCodec : CodecJson[EfGameConfig] = {
    implicit val gne = innerNodeEncode
    implicit val gnd = innerNodeDecode
    implicit val eki : EncodeJsonKey[Int] = EncodeJsonKey.from(_.toString)

    CodecJson.casecodec4(EfGameConfig.apply, EfGameConfig.unapply)("gameTree","attackerIs","defenderIs", "uuid")
  }
}
