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

import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationality
import pl.edu.pw.mini.sg.rng.RandomGenerator
import pl.edu.pw.mini.sg.game.deterministic.single.LinearizedAdvanceableGame
import pl.edu.pw.mini.sg.game.deterministic.{AdvanceablePlayerVisibleState, DeterministicGame, PlayerVisibleState, PureStrategy}
import pl.edu.pw.mini.sg.mcts.{NestedMcs, NestedMcsConfig}
import pl.edu.pw.mini.sg.mixed.tree.{InnerResultNode, MixedStrategyNode}

import scala.annotation.tailrec
import scalaz.NonEmptyList
import pl.edu.pw.mini.sg.game.util.attacker.OptimalAttacker



final case class NestedMcsAttackerResponse(config: NestedMcsConfig, defenderEps: Double,
historySkipConfig: HistorySkipConfig) extends AttackerResponseWithHistory {
  def calculateAttacker[
    DM, AM, DS <: PlayerVisibleState[DM],
    AS <: AdvanceablePlayerVisibleState[AM,AS],
    G <: DeterministicGame[DM,AM,DS,AS,G]
  ](
    gameRoot: G,
    defenderStrategy: MixedStrategyNode[DM, DS, _],
    desiredAttackerPayoff: InnerResultNode[DM,DS],
  )(
    implicit rng: RandomGenerator,
    boundedRationality: BoundedRationality
  ) : PureStrategy[AM,AS] = {
    def evalLeaf(leaf: LinearizedAdvanceableGame[AM, AS]) = {
      val payoffs = OptimalAttacker.payoff(gameRoot, defenderStrategy, leaf.pureStrategy)
      // TODO :: czy zniekształcenie powinno wpływac na rozsztrzyganie remisów?
      payoffs.distortedAttacker.attacker+defenderEps*payoffs.distortedAttacker.defender
    }

    @tailrec def samplePath(currentRoot: LinearizedAdvanceableGame[AM, AS], bestLeaf: Option[(LinearizedAdvanceableGame[AM, AS], Double, Vector[AM])]) : LinearizedAdvanceableGame[AM, AS] = {
      val (payoff, leaf, moves) = NestedMcs.findBestPath[AM, AS, LinearizedAdvanceableGame[AM, AS]](config)(currentRoot, evalLeaf, 1, Vector())

      val (newBestLeaf, newBestPayoff, newBestMoves) = bestLeaf match {
        case Some((bl, bp, bm)) if bp> payoff => (bl, bp, bm)
        case _ => (leaf, payoff, moves)
      }

      if(currentRoot.possibleMoves.isEmpty)
        newBestLeaf
      else
        samplePath(currentRoot.makeMove(newBestMoves.head), Some((newBestLeaf, newBestPayoff, newBestMoves.tail)))
    }

    val game = LinearizedAdvanceableGame[AM, AS](NonEmptyList(gameRoot.attackerState), Nil, Nil)
    samplePath(game, None).pureStrategy
  }
  val isExact: Boolean = false
}
