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

import argonaut.{ CodecJson, DecodeJson, DecodeResult, EncodeJson }
import pl.edu.pw.mini.sg.json.{ FlatTraitJsonCodec, TraitSubtypeCodec }
import pl.edu.pw.mini.sg.mixed.adjuster.bisect.BisectAdjuster
import pl.edu.pw.mini.sg.mixed.adjuster.bisectrotate.BisectRotateAdjuster
import pl.edu.pw.mini.sg.mixed.adjuster.decreasing.DecreasingRateAdjuster
import pl.edu.pw.mini.sg.mixed.adjuster.globaldamping.GlobalDampingAdjuster
import pl.edu.pw.mini.sg.mixed.adjuster.inhibitor.{ InhibitingStrategyAdjuster, LinearInhibitingStrategyAdjuster }
import pl.edu.pw.mini.sg.mixed.adjuster.momentum.{MomentumAdjuster, MomentumDirectionLimitAdjuster, MomentumUpdateSignLimitAdjuster, SquaredNormalizerMomentumAdjuster}
import pl.edu.pw.mini.sg.mixed.adjuster.pushtoedge.{ IterativePushToEdgeAdjuster, PushToEdgeAdjuster }
import pl.edu.pw.mini.sg.mixed.adjuster.signchange.{SignChangeCounterGlobalAdjuster, SignChangeCounterLocalAdjuster}


object AdjusterJson {
  implicit val inhibitingAdjusterCodec : CodecJson[InhibitingStrategyAdjuster] =
    CodecJson.casecodec1(InhibitingStrategyAdjuster.apply, InhibitingStrategyAdjuster.unapply)(
      "rate"
    )

  implicit val linearInhibitingAdjusterCodec : CodecJson[LinearInhibitingStrategyAdjuster] =
    CodecJson.casecodec1(LinearInhibitingStrategyAdjuster.apply, LinearInhibitingStrategyAdjuster.unapply)(
      "rate"
    )

  implicit val momentumAdjusterEncode : EncodeJson[MomentumAdjuster.type] =
    EncodeJson.UnitEncodeJson.contramap { x => Unit }

  implicit val momentumAdjusterDecode : DecodeJson[MomentumAdjuster.type] =
    DecodeJson(x => DecodeResult.ok(MomentumAdjuster))

  implicit val globalDampingEncode : EncodeJson[GlobalDampingAdjuster.type] = EncodeJson.UnitEncodeJson.contramap { x => Unit }
  implicit val globalDampingDecode : DecodeJson[GlobalDampingAdjuster.type] = DecodeJson(x => DecodeResult.ok(GlobalDampingAdjuster))

  implicit val globalSignChangeEncode : EncodeJson[SignChangeCounterGlobalAdjuster.type] = EncodeJson.UnitEncodeJson.contramap { x => Unit }
  implicit val globalSignChangeDecode : DecodeJson[SignChangeCounterGlobalAdjuster.type] = DecodeJson(x => DecodeResult.ok(SignChangeCounterGlobalAdjuster))

  implicit val localSignChangeEncode : EncodeJson[SignChangeCounterLocalAdjuster.type] = EncodeJson.UnitEncodeJson.contramap { x => Unit }
  implicit val localSignChangeDecode : DecodeJson[SignChangeCounterLocalAdjuster.type] = DecodeJson(x => DecodeResult.ok(SignChangeCounterLocalAdjuster))

  implicit lazy val bisectAdjusterEncode : EncodeJson[BisectAdjuster] = EncodeJson
    .jencode1L((ba : BisectAdjuster) => ba.fallbackAdjuster)("fallback")(adjusterCodec.contramap(x => x))
  implicit lazy val bisectAdjusterDecode : DecodeJson[BisectAdjuster] = DecodeJson.jdecode1L(
    new BisectAdjuster(_))("fallback")(adjusterCodec.map(x => x.asInstanceOf[AssesmentBasedAdjuster]))

  implicit lazy val bisectRotateAdjusterCodec : CodecJson[BisectRotateAdjuster] =
    CodecJson.codec2[StatefulAdjuster, Double, BisectRotateAdjuster](
      (adj, dist) => new BisectRotateAdjuster(adj.asInstanceOf[AssesmentBasedAdjuster], dist), (bra : BisectRotateAdjuster) => (bra.fallbackAdjuster, bra.edgeDist)
    )(
      "fallback", "edgeDist")

  implicit val decreasingRateAdjusterCodec : CodecJson[DecreasingRateAdjuster] =
    CodecJson.codec1[Double,DecreasingRateAdjuster](new DecreasingRateAdjuster((_)), _.decreaseRate)("rate")

  implicit lazy val pushToEdgeAdjusterEncode : EncodeJson[PushToEdgeAdjuster] = EncodeJson
    .jencode1L((ba : PushToEdgeAdjuster) => ba.innerAdjuster)("inner")(adjusterCodec.contramap(x => x))
  implicit lazy val pushToEdgeAdjusterDecode : DecodeJson[PushToEdgeAdjuster] = DecodeJson.jdecode1L(
    new PushToEdgeAdjuster(_))("inner")(adjusterCodec.map(x => x.asInstanceOf[AssesmentBasedAdjuster]))

    implicit lazy val pushToEdgeIterativeAdjusterEncode : EncodeJson[IterativePushToEdgeAdjuster] = EncodeJson
    .jencode1L((ba : IterativePushToEdgeAdjuster) => ba.innerAdjuster)("inner")(adjusterCodec.contramap(x => x))
  implicit lazy val pushToEdgeIterativeAdjusterDecode : DecodeJson[IterativePushToEdgeAdjuster] = DecodeJson.jdecode1L(
    new IterativePushToEdgeAdjuster(_))("inner")(adjusterCodec.map(x => x.asInstanceOf[AssesmentBasedAdjuster]))

  implicit val momentumDirectionLimitAdjusterEncode: EncodeJson[MomentumDirectionLimitAdjuster.type] =
    EncodeJson.UnitEncodeJson.contramap(_ => ())

  implicit val momentumDirectionLimitAdjusterDecode : DecodeJson[MomentumDirectionLimitAdjuster.type] =
    DecodeJson(x => DecodeResult.ok(MomentumDirectionLimitAdjuster))

  def singletonCodec(singleton: AnyRef) : CodecJson[singleton.type] = {
    val encode : EncodeJson[singleton.type] = EncodeJson.UnitEncodeJson.contramap(_ => ())
    val decode : DecodeJson[singleton.type] = DecodeJson(x => DecodeResult.ok(singleton))
    CodecJson.derived(encode, decode)
  }

  implicit val momentumUpdateSignLimitAdjusterEncode: EncodeJson[MomentumUpdateSignLimitAdjuster.type] =
    EncodeJson.UnitEncodeJson.contramap(_ => ())

  implicit val momentumUpdateSignLimitAdjusterDecode : DecodeJson[MomentumUpdateSignLimitAdjuster.type] =
    DecodeJson(x => DecodeResult.ok(MomentumUpdateSignLimitAdjuster))

  implicit val squaredNormalizerMomentumAdjusterCodec : CodecJson[SquaredNormalizerMomentumAdjuster] =
    CodecJson.casecodec1(SquaredNormalizerMomentumAdjuster.apply, SquaredNormalizerMomentumAdjuster.unapply)("exponent")

  implicit val adjusterCodec: FlatTraitJsonCodec[StatefulAdjuster] =
    new FlatTraitJsonCodec[StatefulAdjuster](List(
      new TraitSubtypeCodec(inhibitingAdjusterCodec, inhibitingAdjusterCodec, "inhibiting"),
      new TraitSubtypeCodec(linearInhibitingAdjusterCodec, linearInhibitingAdjusterCodec, "linearInhibiting"),
      new TraitSubtypeCodec(momentumAdjusterEncode, momentumAdjusterDecode, "momentum"),
      new TraitSubtypeCodec(globalDampingEncode, globalDampingDecode, "globalDamping"),
      new TraitSubtypeCodec(globalSignChangeEncode, globalSignChangeDecode, "globalSignChange"),
      new TraitSubtypeCodec(localSignChangeEncode, localSignChangeDecode, "coordSignChange"),
      new TraitSubtypeCodec(bisectAdjusterEncode, bisectAdjusterDecode, "bisect"),
      new TraitSubtypeCodec(bisectRotateAdjusterCodec, bisectRotateAdjusterCodec, "bisectRotate"),
      new TraitSubtypeCodec(pushToEdgeAdjusterEncode, pushToEdgeAdjusterDecode, "pushToEdge"),
      new TraitSubtypeCodec(pushToEdgeIterativeAdjusterEncode, pushToEdgeIterativeAdjusterDecode, "iterativePushToEdge"),
      new TraitSubtypeCodec(decreasingRateAdjusterCodec, decreasingRateAdjusterCodec, "decreasingRate"),
      new TraitSubtypeCodec(momentumDirectionLimitAdjusterEncode, momentumDirectionLimitAdjusterDecode, "momentumDirectionLimit"),
      new TraitSubtypeCodec(momentumUpdateSignLimitAdjusterEncode, momentumUpdateSignLimitAdjusterDecode, "momentumUpdateSignLimit"),
      new TraitSubtypeCodec(squaredNormalizerMomentumAdjusterCodec, squaredNormalizerMomentumAdjusterCodec, "squaredNormalizerMomentum")
    ))
}
