package pl.edu.pw.mini.sg.mixed.json

import argonaut.{ CodecJson, DecodeJson, DecodeResult, EncodeJson, Json }
import pl.edu.pw.mini.sg.game.deterministic.single.{ LinearizedAdvanceableGame, LinearizedAdvanceableGameFifo, LinearizedGameProvider }
import pl.edu.pw.mini.sg.game.boundedrationality.payoffchanger.PayoffChangerJson
import pl.edu.pw.mini.sg.game.boundedrationality.probabilitieschanger.ProbabilitiesChangerJson
import pl.edu.pw.mini.sg.json.FlatTraitJsonCodec
import pl.edu.pw.mini.sg.mcts.MctsParameters
import pl.edu.pw.mini.sg.mixed.{ MixedMethodConfig, UctAttackerSamplerConfig, InfeasibleAttackerPropagation, NoPropagation, JustPropagation, RecalcPropagation }
import pl.edu.pw.mini.sg.mixed.adjuster.StatefulAdjuster
import pl.edu.pw.mini.sg.mixed.expansion.NodeExpansionDecision
import pl.edu.pw.mini.sg.mixed.stchooser.SubtreeChooser
import pl.edu.pw.mini.sg.mixed.expansion.ExpansionJson
import pl.edu.pw.mini.sg.mixed.expander.ExpanderJson
import pl.edu.pw.mini.sg.mixed.stchooser.SubtreeChooserJson
import pl.edu.pw.mini.sg.mcts.json.MctsParametersJson
import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationalityJson
import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationality
import pl.edu.pw.mini.sg.mixed.adjuster.AdjusterJson
import pl.edu.pw.mini.sg.mixed.decision.{ImproveNodeDecision, ImproveDecisionJson}
import pl.edu.pw.mini.sg.mixed.prune.PruneDecision
import pl.edu.pw.mini.sg.mixed.response.provider.{AttackerResponseProvider, ResponseProviderJson}

object MixedMethodConfigJson {
  implicit def uctAttackerSamplerCodec(implicit
    mixedMethodE: EncodeJson[MixedMethodConfig],
    mixedMethodD: DecodeJson[MixedMethodConfig],
    mctsConfigE: EncodeJson[MctsParameters],
    mcstConfigD: DecodeJson[MctsParameters],
  ) : CodecJson[UctAttackerSamplerConfig] =
    CodecJson.casecodec6(UctAttackerSamplerConfig.apply, UctAttackerSamplerConfig.unapply)("mixedMethod", "infeasiblePenalty", "infeasiblePropagation", "mcts", "descendSample", "linearizedGame")

  val DFS_LINEARIZED = "dfs"
  val BFS_LINEARIZED = "bfs"
  implicit val linearizedGameProviderEncode: EncodeJson[LinearizedGameProvider] = EncodeJson{
    case LinearizedAdvanceableGame => Json.jString(DFS_LINEARIZED)
    case LinearizedAdvanceableGameFifo => Json.jString(BFS_LINEARIZED)
  }
  implicit val linearizedGameProviderDecode: DecodeJson[LinearizedGameProvider] =
    DecodeJson(hCursor =>
      hCursor.as[String].flatMap{
        case DFS_LINEARIZED => DecodeResult.ok(LinearizedAdvanceableGame)
        case BFS_LINEARIZED => DecodeResult.ok(LinearizedAdvanceableGameFifo)
        case x => DecodeResult.fail(s"Unknown linearized game: $x", hCursor.history)
      })

  val NO_PROPAGATION = "noPropagation"
  val JUST_PROPAGATION = "justPropagation"
  val RECALC_PROPAGATION = "recalcPropagation"
  implicit val infeasiblePropagationEncode : EncodeJson[InfeasibleAttackerPropagation] =
    EncodeJson(ia => ia match {
      case NoPropagation => Json.jString(NO_PROPAGATION)
      case JustPropagation => Json.jString(JUST_PROPAGATION)
      case RecalcPropagation => Json.jString(RECALC_PROPAGATION)
    })

  implicit val infeasiblePropagationDecode : DecodeJson[InfeasibleAttackerPropagation] =
    DecodeJson(hCursor =>
      hCursor.as[String].flatMap(str => str match {
        case NO_PROPAGATION => DecodeResult.ok(NoPropagation)
        case JUST_PROPAGATION => DecodeResult.ok(JustPropagation)
        case RECALC_PROPAGATION => DecodeResult.ok(RecalcPropagation)
        case _ => DecodeResult.fail("Unknown propagation type", hCursor.history)
      })
    )

  implicit def mixedMethodConfigCodec(
    implicit
    expanderTC: FlatTraitJsonCodec[NodeExpansionDecision],
    adjusterTC: FlatTraitJsonCodec[StatefulAdjuster],
    chooserEn: EncodeJson[SubtreeChooser],
    chooserDe: DecodeJson[SubtreeChooser],
    improveEn: EncodeJson[ImproveNodeDecision],
    improveDe: DecodeJson[ImproveNodeDecision],
    attackerOracleEn: EncodeJson[AttackerResponseProvider],
    attackerOracleDe: DecodeJson[AttackerResponseProvider],
    pruneDecisionEn: EncodeJson[PruneDecision],
    pruneDecisionDe: DecodeJson[PruneDecision],
    boundedRationalityEn: EncodeJson[BoundedRationality],
    boundedRationalityDe: DecodeJson[BoundedRationality]
  ) : CodecJson[MixedMethodConfig] =
    CodecJson.casecodec11(MixedMethodConfig.apply, MixedMethodConfig.unapply)(
      "refinementIterations", "feasibilityIterations", "expander",
      "adjuster", "subtreeChooser", "poorImprovementIterationsStop",
      "poorImprovementEps", "improveDecision", "attackerOracle",
      "pruneDecision", "boundedRationalityStrategy"
    )

  val defaultMixedMethodConfigCodec : CodecJson[MixedMethodConfig] = {
    import ExpanderJson._
    import ExpansionJson._
    import SubtreeChooserJson._
    import MctsParametersJson._
    import AdjusterJson._
    import ImproveDecisionJson._
    import ResponseProviderJson._
    import PruneDecisionJson._
    import BoundedRationalityJson._
    import PayoffChangerJson._
    import ProbabilitiesChangerJson._
    mixedMethodConfigCodec
  }

  val defaultUctSamplerCodec : CodecJson[UctAttackerSamplerConfig] = {
    import ExpanderJson._
    import ExpansionJson._
    import SubtreeChooserJson._
    import MctsParametersJson._
    import AdjusterJson._
    import ImproveDecisionJson._
    import ResponseProviderJson._
    import PruneDecisionJson._
    import BoundedRationalityJson._
    import PayoffChangerJson._
    import ProbabilitiesChangerJson._
    uctAttackerSamplerCodec
  }
}
