package pl.edu.pw.mini.game.flipit.deterministic.allpoints

import pl.edu.pw.mini.sg.rng.RandomGenerator
import pl.edu.pw.mini.game.flipit.deterministic.{Flip, NoOp}
import pl.edu.pw.mini.sg.game.deterministic.AdvanceablePlayerVisibleState
import pl.edu.pw.mini.game.flipit.deterministic.FlipItMove

sealed trait AllPointsFlipItAttackerState extends
    AdvanceablePlayerVisibleState[FlipItMove, AllPointsFlipItAttackerState] {
  def next(move: FlipItMove, success: Boolean, payoff: Double) : AllPointsFlipItAttackerState
}

object AllPointsFlipItAttackerStateTerminal extends AllPointsFlipItAttackerState {
  override def possibleMoves = Nil
  override def possibleContinuations(move: FlipItMove): List[AllPointsFlipItAttackerState] =
    throw new IllegalStateException("terminal state")
  override def sampleContinuations(move: FlipItMove)(implicit rng: RandomGenerator): AllPointsFlipItAttackerState =
    throw new IllegalStateException("terminal state")
  override def next(move: FlipItMove, success: Boolean, payoff: Double) : AllPointsFlipItAttackerState =
    throw new IllegalStateException("terminal state")
}


final class AllPointsFlipItAttackerStateInner(
  val previousMoveSucceded: Boolean,
  val lastRoundReward: Double,
  val possiblyOwnedVertices: Set[Int],
  val round: Int,
  cache: Cache
) extends AllPointsFlipItAttackerState {
  override def possibleMoves = NoOp::(
    (cache.config.entryNodes++possiblyOwnedVertices++(possiblyOwnedVertices.flatMap(cache.neighbours))).toSet.map(cache.flipMoves).toList
  )

  override def toString: String = s"APFAS ${previousMoveSucceded}, ${lastRoundReward}, ${possiblyOwnedVertices}, ${round}"

  // Omitting cache in comparison for better performance
  override def equals(other: Any) : Boolean = other match {
    case s : AllPointsFlipItAttackerStateInner =>
      s.previousMoveSucceded == previousMoveSucceded &&
      s.lastRoundReward == lastRoundReward &&
      possiblyOwnedVertices == s.possiblyOwnedVertices &&
      s.round == round
    case _ => false
  }

  override lazy val hashCode : Int = (previousMoveSucceded, lastRoundReward, possiblyOwnedVertices, round).##

  private def possibleContinuationsFailure(move: FlipItMove) : List[AllPointsFlipItAttackerState] = {
    val moveCost = move.flippedVertex.map(cache.config.attackerCosts).getOrElse(0.0)
    val newPossiblyOwned = possiblyOwnedVertices -- move.flippedVertex
    val possibleRewards = (0.0::(newPossiblyOwned.subsets().map(_.view.map(cache.config.attackerRewards).sum).toList)).distinct
    possibleRewards.map(r => new AllPointsFlipItAttackerStateInner(false, r-moveCost, newPossiblyOwned, round+1, cache))
  }

  private def possibleContinuationsSucces(move: Flip) : List[AllPointsFlipItAttackerState] = {
    val moveCost = move.flippedVertex.map(cache.config.attackerCosts).getOrElse(0.0)
    val newPossiblyOwned = possiblyOwnedVertices ++ move.flippedVertex
    val possibleRewards = ((newPossiblyOwned.subsets().filter(_.contains(move.vertex)).map(_.view.map(cache.config.attackerRewards).sum).toList)).distinct
    possibleRewards.map(r => new AllPointsFlipItAttackerStateInner(true, r-moveCost, newPossiblyOwned, round+1, cache))
  }

  override def possibleContinuations(move: FlipItMove): List[AllPointsFlipItAttackerState] = {
    if(round == cache.config.rounds)
      List(AllPointsFlipItAttackerStateTerminal)
    else
      move match {
        case NoOp => possibleContinuationsFailure(move)
        case mm: Flip => possibleContinuationsFailure(mm) ++ possibleContinuationsSucces(mm)
      }
  }
  override def sampleContinuations(move: FlipItMove)(implicit rng: RandomGenerator): AllPointsFlipItAttackerState = {
    if(round == cache.config.rounds) AllPointsFlipItAttackerStateTerminal
    else {
      val moveCost = move.flippedVertex.map(cache.config.attackerCosts).getOrElse(0.0)
      val r1 = rng.nextInt(1 << possiblyOwnedVertices.size)
      val randomSubset : Set[Int] = if(r1==0) Set()
            else possiblyOwnedVertices.subsets().drop(r1-1).next()
      move match {
        case NoOp => {
          val reward = randomSubset.view.map(cache.config.attackerRewards).sum

          new AllPointsFlipItAttackerStateInner(false, reward, possiblyOwnedVertices, round+1, cache)
        }
        case x: Flip => {
          val success = rng.nextBoolean()
          val reward = (if(success) randomSubset+x.vertex else randomSubset-x.vertex)
            .view.map(cache.config.attackerRewards).sum
          val pp = if (success) possiblyOwnedVertices+x.vertex else possiblyOwnedVertices-x.vertex
          new AllPointsFlipItAttackerStateInner(success, reward-moveCost, pp, round+1, cache)
        }
      }
    }
  }

  
  def next(move: FlipItMove, success: Boolean, payoff: Double) : AllPointsFlipItAttackerState = {
    if(round == cache.config.rounds)
      AllPointsFlipItAttackerStateTerminal
    else 
      new AllPointsFlipItAttackerStateInner(success, payoff, move match {
        case NoOp => possiblyOwnedVertices
        case x: Flip if success => possiblyOwnedVertices+x.vertex
        case x: Flip if !success => possiblyOwnedVertices-x.vertex
      }, 
        round+1, cache)
  }
}
