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

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

import pl.edu.pw.mini.sg.rng.RandomGenerator
import pl.edu.pw.mini.sg.game.deterministic.{AdvanceablePlayerVisibleState, DeterministicGame, PlayerVisibleState, PureStrategy, TreeExpander}
import pl.edu.pw.mini.sg.game.deterministic.single.GameWithPayoff
import pl.edu.pw.mini.sg.mixed.response.AttackerResponseFinder
import pl.edu.pw.mini.sg.mixed.tree.{CalculateResults, InnerResultNode, MixedStrategyNode}
import pl.edu.pw.mini.sg.util.VectorUtils._
import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationality


final case class StableAttackerFinderState[AM, AS](
  recentAttackers: Vector[(Int,PureStrategy[AM, AS])] = Vector(),
  noNew : Int = 0,
  skipped: Int = 0
) {
  def register(strategy: PureStrategy[AM, AS], limit: Int) : StableAttackerFinderState[AM, AS] = {
      recentAttackers.view.zipWithIndex.find { case ((ct, ps), idx) => ps == strategy }.map(_._2) match {
        case None => StableAttackerFinderState(
          if(recentAttackers.size >= limit) ((1, strategy)) +: recentAttackers.sortBy(_._1).tail
          else ((1, strategy)) +: recentAttackers
        )
        case Some(idx) => StableAttackerFinderState(recentAttackers.updatedWith(idx, {case (count, str) => (count+1, str)}), noNew+1, 0)
      }
  }
}

final object StableAttackerFinder {
  def zeroState[AM, AS] = StableAttackerFinderState[AM, AS]()

  def findStableAttacker[
    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],
    treeExpander: TreeExpander[DM, DS, GameWithPayoff],
    state: StableAttackerFinderState[AM, AS],
    attackerOracle: AttackerResponseFinder
  )(
    implicit rng: RandomGenerator,
    boundedRationality: BoundedRationality
  ): ((PureStrategy[AM, AS], InnerResultNode[DM, DS]), StableAttackerFinderState[AM, AS]) = {
    @tailrec
    def stableOptimalAttacker(state: StableAttackerFinderState[AM, AS]) : ((PureStrategy[AM, AS], InnerResultNode[DM, DS]), StableAttackerFinderState[AM, AS]) = {
      val (optimalAttackerResponse, state1) = attackerOracle.findResponse[DM, AM, DS, AS, G](gameRoot, defenderStrategy, desiredAttackerPayoff, state)
      val optimalAttackerPayoff = new InnerResultNode(defenderStrategy, gameRoot.defenderState)
      val expanded = CalculateResults.calculate(gameRoot, defenderStrategy, optimalAttackerResponse, optimalAttackerPayoff, treeExpander, false)
      if(!expanded) {
        assert(optimalAttackerPayoff.payoff.equalsEps(OptimalAttacker.payoff(gameRoot, defenderStrategy, optimalAttackerResponse), 1e-3))
        ((optimalAttackerResponse, optimalAttackerPayoff), state1)
      }
      else
        stableOptimalAttacker(state1)
    }
    stableOptimalAttacker(state)
  }
}
