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

import pl.edu.pw.mini.sg.rng.RandomGenerator
import pl.edu.pw.mini.sg.game.deterministic.PlayerVisibleState
import pl.edu.pw.mini.sg.mixed.IterationInfo
import pl.edu.pw.mini.sg.mixed.adjuster.{ AssesmentBasedAdjuster, StatefulAdjuster }
import pl.edu.pw.mini.sg.mixed.tree.{ InnerResultNode, MixedStrategyNode }


final class BisectAdjuster(val fallbackAdjuster: AssesmentBasedAdjuster) extends StatefulAdjuster {
  override type AdjusterState = BisectAdjusterState[fallbackAdjuster.AdjusterState]

  implicit val ost = new BisectAdjusterStateType(fallbackAdjuster.ost)

  def inTreeAdjust[DM, DS <: PlayerVisibleState[DM]](
    mixedStrategy: MixedStrategyNode[DM,DS, AdjusterState],
    desiredAttackerPayoff: InnerResultNode[DM,DS],
    optimalAttackerPayoff: InnerResultNode[DM,DS]
  )(
    implicit rng: RandomGenerator, iterationInfo: IterationInfo) : Boolean = {
    val diff = desiredAttackerPayoff.payoff.distortedAttacker.attacker - optimalAttackerPayoff.payoff.distortedAttacker.attacker
    def diffm(move: DM) = desiredAttackerPayoff.payoff(move).distortedAttacker.attacker - optimalAttackerPayoff.payoff(move).distortedAttacker.attacker
    val assesments = mixedStrategy.probabilities.view.map(p => diffm(p._1) - diff).toVector
    
    mixedStrategy.optimizerState.lastGoodPosition match {
      case Left(pos) => {
        mixedStrategy.probabilities.view.zipWithIndex.zip(pos).foreach{case (((dm, pr), idx), oldpr) =>
          mixedStrategy.updateProb(idx, (pr+oldpr)/2)
        }
        true
      }
      case Right(fallbackState) => {
        val newFS = fallbackAdjuster.updateState(fallbackState, assesments)
        mixedStrategy.optimizerState = new BisectAdjusterState(Right(newFS), 0)
        fallbackAdjuster.updateStrategy(mixedStrategy, newFS, assesments)
      }
    }
  }
  
  def outOfTreeAdjust[DM, DS <: PlayerVisibleState[DM]](
    mixedStrategy: MixedStrategyNode[DM,DS,AdjusterState],
    optimalAttackerPayoff: InnerResultNode[DM,DS]
  )(
    implicit rng: RandomGenerator, iterationInfo: IterationInfo) : Boolean = {
    val assesments = mixedStrategy.probabilities.view.map(p =>
      optimalAttackerPayoff.payoff.distortedAttacker.attacker - optimalAttackerPayoff.payoff(p._1).distortedAttacker.attacker).toVector
    val state = fallbackAdjuster.updateState(
      mixedStrategy.optimizerState.lastGoodPosition.right.get,
      assesments
    )

    // 0 jest OK, bo nigdy go nie użyję. Może powinienem inaczej zorganizować ten typ? 
    mixedStrategy.optimizerState = new BisectAdjusterState(Right(state), 0)

    fallbackAdjuster.updateStrategy(mixedStrategy, state, assesments)
  }
  def positiveAdjust[DM, DS <: PlayerVisibleState[DM]](
    mixedStrategy: MixedStrategyNode[DM,DS,AdjusterState],
    gamePayoff: InnerResultNode[DM,DS]
  )(
    implicit rng: RandomGenerator, iterationInfo: IterationInfo) : Boolean = {
    mixedStrategy.optimizerState = mixedStrategy.optimizerState.registerFeasible(mixedStrategy.probabilities.view.map(_._2).toVector)
    val assesments = mixedStrategy.probabilities.view.map(p =>
     gamePayoff.payoff(p._1).distortedAttacker.defender - gamePayoff.payoff.distortedAttacker.defender).toVector

    mixedStrategy.probabilities.view.zipWithIndex.zip(assesments).foreach{
      case (((dm, prob), idx), as) => {
        mixedStrategy.updateProb(idx, Math.max(0, prob+as))
      }}

    mixedStrategy.normalizeOrEqual()
    true
  }
}

/*
1) Jestem w obszarze feasible:
  zapisuję bieżącą pozycję i aktualizuję wartości w kierunku poprawy

2) Nie jestem w obszarze feasible i:
   a) mam tę sama długość wektora
     -- aktualizacja last good known ze zmniejszonym współczynnikiem. Jakim, skoro go nie mam? 
        a może mam? w końcu mam bieżącą pozycję. 
   b) mam inną długość wektora --
      dodaję 0 i dalej punkt a)
   b) nie mam w ogóle wektora 
     -- fallback
3) Jestem w obszarze out of tree
  -- chyba tylko fallback. Jakieś inne opcje? 

Problem techniczny: jak realizaować fallback, kiedy nie zgadza się adjuster state? 
 1) Mógłbym podawać jakąś soczewkę / podobny mechanizm zamiast węzła wprost
 2) Mógłbym wyłączyć jeszce jakiś podmoduł z adjustera

*/
