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

import pl.edu.pw.mini.sg.rng.RandomGenerator
import org.log4s._
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.bisectrotate.BisectRotateAdjusterBase
import pl.edu.pw.mini.sg.mixed.adjuster.{ AssesmentBasedAdjuster, StatefulAdjuster }
import pl.edu.pw.mini.sg.mixed.tree.{ InnerResultNode, MixedStrategyNode }
import scala.annotation.tailrec


final class PushToEdgeAdjuster(
  val innerAdjuster: AssesmentBasedAdjuster
) extends StatefulAdjuster {
  // czy w ogóle będę miał kilka stanów? Czy nie będzie tak, że ZAWSZE w in tree będę dobijał do granicy?
  // to może się okazać problematyczne w grach wielokrokowych. Ale tym będę się martwił potem.

  import BisectRotateAdjusterBase.orthogonalUpdate

  private val logger = getLogger
  override type AdjusterState = innerAdjuster.AdjusterState
  implicit val ost = innerAdjuster.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 = {
    def diffm(move: DM) = desiredAttackerPayoff.payoff(move).distortedAttacker.attacker - optimalAttackerPayoff.payoff(move).distortedAttacker.attacker
    val exactBorder = mixedStrategy.probabilities.view.map(p => diffm(p._1)).toVector

    logger.debug(s"Initial vec ${mixedStrategy.probabilities.view.map(_._2).toVector}")
    logger.debug(s"Initial payoff DAP:${desiredAttackerPayoff.payoff} OAP:${optimalAttackerPayoff.payoff}")

    @tailrec
    def pushToBorder(originalIndices: Vector[Int],
      exactBorder: Vector[Double], currentPos: Vector[Double]
    ) : Vector[Double] = {
      val pushed = orthogonalUpdate(exactBorder, currentPos)
      val indicesToRemove = pushed.view.zipWithIndex.filter(_._1 < 0).map(_._2).toSet
      def removeIndices[T](vec: Vector[T]) : Vector[T] =
        vec.view.zipWithIndex.filter(x => ! indicesToRemove.contains(x._2)).map(_._1).toVector
      if(indicesToRemove.isEmpty) {
        logger.debug(s"Found with ${mixedStrategy.probabilities.size-pushed.size} removed")
        originalIndices.zipWithIndex.foldLeft(Vector.fill(mixedStrategy.probabilities.size)(0.0)){case (vec, (origidx, idx)) =>
          vec.updated(origidx, pushed(idx))
        }
      } else if(indicesToRemove.size == originalIndices.size) {
          logger.debug("Zero strategy")
          Vector.fill(mixedStrategy.probabilities.size)(0)
        }
        else
          pushToBorder(removeIndices(originalIndices), removeIndices(exactBorder), removeIndices(currentPos))
    }

    val pushed = pushToBorder(
      Vector.tabulate(mixedStrategy.probabilities.size)(identity), exactBorder,
      mixedStrategy.probabilities.view.map(_._2).toVector
    )
    pushed.view.zipWithIndex.foreach{case (prob, idx) =>
      mixedStrategy.updateProb(idx, prob)
    }
    logger.debug(s"not normalized sum: ${mixedStrategy.probabilities.map(_._2).sum}")
    mixedStrategy.normalizeOrEqual()
    logger.debug(s"Final vec ${mixedStrategy.probabilities.view.map(_._2).toVector}")
    logger.debug(s"Final payoff DAP:${{desiredAttackerPayoff.updateValues();desiredAttackerPayoff.payoff.distortedAttacker}} OAP:${{optimalAttackerPayoff.updateValues();optimalAttackerPayoff.payoff.distortedAttacker}}")
    true
  }
  
  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
    mixedStrategy.optimizerState = innerAdjuster.updateState(mixedStrategy.optimizerState, assesments)
    innerAdjuster.updateStrategy(mixedStrategy, mixedStrategy.optimizerState, assesments)
  }
  def positiveAdjust[DM, DS <: PlayerVisibleState[DM]](
    mixedStrategy: MixedStrategyNode[DM,DS,AdjusterState],
    gamePayoff: InnerResultNode[DM,DS]
  )(
    implicit rng: RandomGenerator, iterationInfo: IterationInfo) : Boolean = {
    logger.debug(s"Feasible payoff ${gamePayoff.payoff}")
    val assesments = mixedStrategy.probabilities.view.map(p =>
     gamePayoff.payoff(p._1).real.defender - gamePayoff.payoff.real.defender).toVector
    mixedStrategy.optimizerState = innerAdjuster.updateState(mixedStrategy.optimizerState, assesments)
    innerAdjuster.updateStrategy(mixedStrategy, mixedStrategy.optimizerState, assesments)
  }
}
