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

import pl.edu.pw.mini.sg.game.deterministic.plugin.{ GameParams }
import scala.annotation.tailrec
import scala.math.Ordering

import pl.edu.pw.mini.sg.game.Payoff
import pl.edu.pw.mini.sg.game.boundedrationality.DistortedPayoff
import pl.edu.pw.mini.sg.game.deterministic.{AdvanceablePlayerVisibleState, DeterministicGame, PlayerVisibleState}
import pl.edu.pw.mini.sg.game.deterministic.single.LinearizedGameProvider
import pl.edu.pw.mini.sg.mcts.NestedMcs
import pl.edu.pw.mini.sg.mixed.tree.MixedStrategyNode
import pl.edu.pw.mini.sg.rng.RandomGenerator
import pl.edu.pw.mini.sg.util.Logging

final class BestResult[T, Q](
  implicit val ord: Ordering[Q]
) {
  var result: Option[(T, Q)] = None
  def registerResult(x: T, v: Q) = {
    result match {
      case Some((_, v2)) if ord.gt(v2,  v) => ()
      case _ => result = Some((x, v))
    }
  }
}

object NestedMcsAttackerSampler extends Logging {
  implicit private val po = Ordering.fromLessThan((p1 : DistortedPayoff, p2: DistortedPayoff) => p1.real.defender < p2.real.defender)

  def findBehavioralStrategy[
    DM, AM,
    DS <: PlayerVisibleState[DM],
    AS <: AdvanceablePlayerVisibleState[AM, AS],
    G <: DeterministicGame[DM, AM, DS, AS, G]
  ](
    game: G, params: GameParams,
    config : NestedMcsAttackerSamplerConfig   
  )(
    implicit rng: RandomGenerator
  ) : (MixedStrategyNode[DM, DS, Unit], DistortedPayoff) = {
    val (evalFun, bestRes) = attackerStrategyEvaluation[DM, AM, DS, AS, G](config, game, params)

    val linearizedAttackerGame = config.linearizedGameProvider.create[AM,AS](game.attackerState)
    @tailrec 
    def doSampling(game: config.linearizedGameProvider.Game[AM, AS]) : Unit = {
      val (_, _, mm) = NestedMcs.findBestPath[AM, AS, config.linearizedGameProvider.Game[AM,AS]](config.nestedMcsConfig)(game, evalFun, 1, Vector())
      mm match {
        case m +: _ => doSampling(game.makeMove(m))
        case _ => ()
      }
    }

    doSampling(linearizedAttackerGame)

    bestRes.result.map{case (a,b) => (a, b)}.get
  }

  def attackerStrategyEvaluation[
    DM, AM,
    DS <: PlayerVisibleState[DM],
    AS <: AdvanceablePlayerVisibleState[AM, AS],
    G <: DeterministicGame[DM, AM, DS, AS, G]
  ](
    config: NestedMcsAttackerSamplerConfig,
    game: G,
    params: GameParams
  )(
    implicit rng: RandomGenerator
  ) : (config.linearizedGameProvider.Game[AM, AS] => Double, BestResult[MixedStrategyNode[DM, DS, Unit], DistortedPayoff]) = {
    val infeasiblePayoff = (params.maxDefenderPayoff-params.minDefenderPayoff)*config.infeasiblePenalty+params.minDefenderPayoff
    val br = new BestResult[MixedStrategyNode[DM, DS, Unit], DistortedPayoff]()
    (
      x => {
        val desiredAttacker = x.pureStrategy

        UctAttackerSampler.maybeFeasibleToFeasibleStrategy[DM, AM, DS, AS, G](config.mixedConfig)(game, desiredAttacker)(UctAttackerSampler.defenderForGivenAttacker[DM, AM, DS, AS, G](
          config.mixedConfig)(game, desiredAttacker)(new MixedStrategyNode[DM, DS, config.mixedConfig.strategyAdjuster.AdjusterState](game.defenderState)(config.mixedConfig.strategyAdjuster.ost))) match {
          case FeasibleResult(str, payoff) => br.registerResult(str.deepClone, payoff); payoff.real.defender
          case InfeasibleResult(str, payoff) => br.registerResult(str.deepClone, payoff); infeasiblePayoff
        }
      }, br
    )
  }
}
