package pl.edu.pw.mini.sg.game.deterministic.single

import org.scalacheck.Gen
import pl.edu.pw.mini.sg.game.deterministic.{AdvanceablePlayerVisibleState, DeterministicGame, PlayerVisibleState}
import pl.edu.pw.mini.sg.rng.RandomGenerator

final case class NaiveAdvanceableState(
  successors: Map[(Int, Int), NaiveRandomGame],
  possibleMoves: List[Int],
  attackerState: Int
) extends AdvanceablePlayerVisibleState[Int, NaiveAdvanceableState] {
  override def possibleContinuations(move: Int): List[NaiveAdvanceableState] = {
    require(possibleMoves.contains(move), s"Move not allowed in state $move, $possibleMoves")
    successors.filter{case ((dm, am), game) => am == move}.
      map{
        case ((dm, _), NaiveRandomGameInner(aS, dS, dM, aM, succ)) =>
          NaiveAdvanceableState(succ, aM, dm)
        case ((dm, _), NaiveRandomGameLeaf(aS, dS, aP, dP)) =>
          NaiveAdvanceableState(Map(), Nil, dm)
      }.toList.sortBy(_.attackerState)
  }
  override def sampleContinuations(move: Int)(implicit rng: RandomGenerator): NaiveAdvanceableState = ???
  override def toString = s"NAS($possibleMoves, $attackerState)" 
}

final case class NaiveDefenderState(
  possibleMoves: List[Int],
  previousAM: Int
) extends PlayerVisibleState[Int]

sealed trait NaiveRandomGame extends DeterministicGame[
  Int, Int, NaiveDefenderState, NaiveAdvanceableState, NaiveRandomGame
]

final case class NaiveRandomGameInner(
  previousAM: Int,
  previousDM: Int,
  defenderMoves: List[Int],
  attackerMoves: List[Int],
  successors: Map[(Int, Int), NaiveRandomGame]
) extends NaiveRandomGame {
  val defenderPayoff = 0
  val attackerPayoff = 0
  lazy val attackerState : NaiveAdvanceableState = NaiveAdvanceableState(successors, attackerMoves, previousDM)
  lazy val  defenderState: NaiveDefenderState = NaiveDefenderState(defenderMoves, previousAM)
  def makeMove(dm: Int, am: Int) : NaiveRandomGame = successors((dm, am))

  override def toString = "NaiveRandomGame(...)"
}

final case class NaiveRandomGameLeaf(
  previousDM: Int,
  previousAM: Int,
  attackerPayoff: Double,
  defenderPayoff: Double
) extends NaiveRandomGame {
  def makeMove(dm: Int, am: Int) : NaiveRandomGame = throw new IllegalStateException("terminal state")

  def attackerState = NaiveAdvanceableState(Map(), Nil, previousDM)
  def defenderState = NaiveDefenderState(Nil, previousAM)
}

object NaiveRandomGame {
  def gameGenerator(previousDM: Int, previousAM: Int, level: Int): Gen[NaiveRandomGame] = 
    Gen.oneOf(true, true, level >= 5).flatMap{
      case true => gameGeneratorLeaf(previousDM, previousAM, level)
      case false => gameGeneratorInner(previousDM, previousAM, level)
    }


  def gameGeneratorLeaf(previousDM: Int, previousAM: Int, level: Int): Gen[NaiveRandomGameLeaf] = for {
    dp <- Gen.choose(0.0, 2.0)
    ap <- Gen.choose(0.0, 2.0) 
  } yield NaiveRandomGameLeaf(previousDM, previousAM, ap, dp)

  def gameGeneratorInner(previousDM: Int, previousAM: Int, level: Int): Gen[NaiveRandomGameInner] = for {
    dm <- Gen.choose(2, 5)
    am <- Gen.choose(2, 5)
    labels = (
      for {
        d <- Range.inclusive(1, dm); a <- Range.inclusive(1, am)
      } yield (d, a)
    ).toList
    ss <- sequence(labels.map{case (d, a) => gameGenerator(d, a, level+1)})
  } yield NaiveRandomGameInner(previousAM, previousDM, List.tabulate(dm)(_+1),
    List.tabulate(am)(_+1), labels.zip(ss).toMap)

  def sequence[T](l: List[Gen[T]]) : Gen[List[T]] = l match {
    case Nil => Gen.const(Nil)
    case x::xx => x.flatMap(e => sequence(xx).map(ee => e::ee))
  }

  val gameGeneratorStart: Gen[NaiveRandomGameInner] = gameGeneratorInner(0, 0, 0)

  def generateMoveSequence(lgp: LinearizedGameProvider)(game: NaiveRandomGame) : Gen[List[(Int, NaiveAdvanceableState)]] = {
    def generateSequence(lg : lgp.Game[Int, NaiveAdvanceableState]) : Gen[List[(Int, NaiveAdvanceableState)]] = {
      if(lg.possibleMoves.isEmpty)
        Gen.const(Nil)
      else
        for {
          move <- Gen.choose(0, lg.possibleMoves.length-1).map(lg.possibleMoves)
          succ = lg.makeMove(move)
          rest <- generateSequence(succ)
        } yield (move, succ.playerState) :: rest
    }
    val lingame = lgp.create[Int, NaiveAdvanceableState](game.attackerState)
    generateSequence(lingame)
  }

  def generateGameWithMoveSequence(lgp: LinearizedGameProvider): Gen[GMS] =
    gameGeneratorStart.flatMap(game => {
      generateMoveSequence(lgp)(game).map(moves => {
        require(moves.nonEmpty)
        GMS(game, moves)
      })
    })
}

final case class GMS(
  game: NaiveRandomGameInner,
  moves: List[(Int, NaiveAdvanceableState)]
)
