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

import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationality

import scala.annotation.tailrec
import pl.edu.pw.mini.sg.rng.RandomGenerator
import pl.edu.pw.mini.sg.game.deterministic.{AdvanceablePlayerVisibleState, DeterministicGame, PlayerVisibleState, PureStrategy}
import pl.edu.pw.mini.sg.game.deterministic.single.LinearizedAdvanceableGame
import pl.edu.pw.mini.sg.game.util.attacker.OptimalAttacker
import pl.edu.pw.mini.sg.mcts.{MctsAlgorithm, MctsParameters}
import pl.edu.pw.mini.sg.mcts.tree.MctsNode
import pl.edu.pw.mini.sg.mixed.tree.{InnerResultNode, MixedStrategyNode}

import scalaz.NonEmptyList

final case class MctsAttackerResponse(config: MctsParameters, 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] = {
    val game = LinearizedAdvanceableGame[AM, AS](NonEmptyList(gameRoot.attackerState), Nil, Nil)
    val mctsRoot = config.treeFactory.create(game.playerState, game.possibleMoves)

    def evalLeaf(leaf: LinearizedAdvanceableGame[AM, AS]) = {
      val payoffs = OptimalAttacker.payoff(gameRoot, defenderStrategy, leaf.pureStrategy)
      //TODO: czy zniekształcenie powinno też uwzględniać rozstrzyganie remisów?
      (payoffs.distortedAttacker.attacker+defenderEps*payoffs.distortedAttacker.defender, None)
    }

    @tailrec def pathMcts(game: LinearizedAdvanceableGame[AM, AS], currentRoot: MctsNode[AM, AS]) : LinearizedAdvanceableGame[AM, AS] = {
      if(game.possibleMoves.isEmpty)
        game
      else {
        MctsAlgorithm.training(currentRoot, game, evalLeaf, config, rng)
        val bestMove = currentRoot.estimates.maxBy(est => est.estimate).move
        val nextState = game.makeMove(bestMove)
        pathMcts(nextState, currentRoot.successor(bestMove, nextState.playerState).get)
      }
    }

    pathMcts(game, mctsRoot.root).pureStrategy
  }
  val isExact: Boolean = false
}
