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

import pl.edu.pw.mini.sg.game.deterministic.{ AdvanceablePlayerVisibleState, PureStrategy }
import scala.annotation.tailrec

import scala.collection.mutable.{Map => MMap}
import scalaz.NonEmptyList

final case class SimplePureStrategy[M, S](
  override val move : M,
  override val state: S,
  successors: Map[S, PureStrategy[M,S]]
) extends PureStrategy[M, S] {
  def nextState(resultingState: S) : PureStrategy[M, S] = successors.get(resultingState).getOrElse(new DummyPureStrategy(resultingState))

  override def shortString = s"${move} ${successors.headOption.map(_._2.shortString).getOrElse("")}"
}

final class DummyPureStrategy[M, S](
  override val state: S
) extends PureStrategy[M, S] {
  override def move = ???
  override def shortString: String = ""
  override def nextState(resultingState: S) = ???
}

private[single] final class MutablePureStrategy[M, S](
  val state: S
) {
  var move: M = _
  var moveSet: Boolean = false
  val successors : MMap[S, MutablePureStrategy[M, S]] = MMap()

  def succ(state: S) = successors.getOrElseUpdate(state, new MutablePureStrategy[M, S](state))

  def toImmutable : SimplePureStrategy[M, S] = if(moveSet) new SimplePureStrategy(move, state, successors.mapValues(_.toImmutable).toMap) else throw new IllegalStateException(s"move not set for state $state")
}


final case class LinearizedAdvanceableGame[M, S <: AdvanceablePlayerVisibleState[M, S]](
  currentHistory: NonEmptyList[S],
  pendingBranches: List[NonEmptyList[S]],
  decisions: List[(NonEmptyList[S], M)]
) extends LinearizedGame[M, S, LinearizedAdvanceableGame[M, S]] {
  override val playerState : S = currentHistory.head
  override def possibleMoves: List[M] = currentHistory.head.possibleMoves

  override def makeMove(move: M) : LinearizedAdvanceableGame[M, S] = {
    val successorsNF = currentHistory.head.possibleContinuations(move)
    val successors = successorsNF.filter(_.possibleMoves.length > 0)
    val newDecision: List[(NonEmptyList[S], M)] = (currentHistory, move) :: decisions

    successors match {
      case Nil =>
        pendingBranches match {
          case x::xs => LinearizedAdvanceableGame(x, xs, newDecision)
          case Nil => LinearizedAdvanceableGame(successorsNF.head <:: currentHistory, Nil, newDecision)
        }
      case s::ss => {
        LinearizedAdvanceableGame(s<::currentHistory, ss.map(_ <:: currentHistory) ++ pendingBranches, newDecision)
      }
    }
  }

  def pureStrategy : PureStrategy[M, S] = {
    val root = new MutablePureStrategy[M,S](currentHistory.last)
    require(decisions.exists(_._1.size == 1))
    decisions.foreach{case (histRev, move) => {
      val x = histRev.reverse.tail.foldLeft(root)(_.succ(_))
      x.move = move
      x.moveSet = true
    }}
    require(root.moveSet)
    root.toImmutable
  }
}


object LinearizedAdvanceableGame extends LinearizedGameProvider {
  override type Game[M, S <: AdvanceablePlayerVisibleState[M, S]] = LinearizedAdvanceableGame[M, S]

  override def create[M,S <: AdvanceablePlayerVisibleState[M,S]](state: S) : LinearizedAdvanceableGame[M, S] = new LinearizedAdvanceableGame(NonEmptyList(state), Nil, Nil)

  override def pureStrategyToLinearizedGameMoveSequence[M, S <: AdvanceablePlayerVisibleState[M, S]](
    root: S,
    pureStrategy : PureStrategy[M, S]
  ) : List[M] = {
    @tailrec
    def genMoves(currentState: S, currentStrategy: PureStrategy[M,S], pendingBranches: List[(S, PureStrategy[M, S])], result: List[M]): List[M] = {
      if(currentState.possibleMoves.nonEmpty) {
        val theMove = currentStrategy.move
        val nextStates = currentState.possibleContinuations(theMove)
        nextStates match {
          case st::sts => {
            val nextStrategy = currentStrategy.nextState(st)
            genMoves(st,nextStrategy, sts.map(s => (s, currentStrategy.nextState(s)))++pendingBranches, currentStrategy.move :: result)
          }
          case List() => {
            val ll = theMove :: result
            pendingBranches match {
              case List() => ll
              case (s,str)::tail => genMoves(s, str, tail, ll)
            }
          }
        }
      } else {
        pendingBranches match {
              case List() => result
              case (s,str)::tail => genMoves(s, str, tail, result)
        }
      }
    }


    genMoves(root, pureStrategy, List(), List()).reverse
  }
}
