package net.ladstatt.tictactoe

import scala.util.Random

/**
  * A naive way to implement a brute force strategy for tic tac toe
  */
object BruteForceTicTacToeStrategy extends TicTacToeStrategy {

  val newGame: TicTacToe = TicTacToe()

  /**
    * for a given list of moves, this function reduces the moves and the
    * starting state (the tic tac toe) to a resulting tic tac toe. If the
    * game is already over, the remaining moves are ignored.
    *
    * @param t
    * @param moves
    * @return
    */
  def play(t: TicTacToe, moves: Seq[TMove]): TicTacToe = {
    moves.foldLeft(t) {
      case (game, move) =>
        if (!game.isOver) {
          game.turn(move)
        } else game
    }
  }

  // calculate all tic tac toe games possible
  lazy val allGames: Map[Seq[TMove], TicTacToe] = {
    TicTacToe.allMoves.toSeq.permutations.map {
      case moves =>
        val t = play(newGame, moves)
        (t.movesSoFar, t)
    }.toMap
  }

  // separate draw games from games where a winner exists
  lazy val (gamesWithWinner: Map[Seq[TMove], TicTacToe], gamesWithDraw: Map[Seq[TMove], TicTacToe]) =
    allGames.partition {
      case (_, game) => game.winner.isDefined
    }

  // partition all games where a winner exists
  lazy val (gamesA, gamesB) = gamesWithWinner.partition {
    case (_, game) => game.winner.get._1 == PlayerA
  }

  /**
    * returns the next turn for a given tic tac toe game, or none if the tic tac toe
    * game is already over, meaning somebody won or there is a draw
    *
    * @param game
    * @return
    */
  override def calcNextTurn(game: TicTacToe): Option[TMove] = {
    if (game.isOver) None
    else {
      val plr = game.nextPlayer
      // only interested in the win for the player
      val winningGames: Map[Seq[TMove], TicTacToe] =
        if (plr == PlayerA) gamesA else gamesB

      // we try to win, so lets search if there exists a path to a winning game
      val potentialWinningMoves: Seq[Seq[TMove]] =
        winningGames.filter {
          case (moves, _) => moves.startsWith(game.movesSoFar)
        }.keys.toSeq

      if (potentialWinningMoves.nonEmpty) {
        // println(plr + ": " + potentialWinningMoves.size + " ways to to win the game.")
        Some(determineMove(game, potentialWinningMoves))
      } else {
        // check if we can reach a draw
        val potentialDraws =
          gamesWithDraw.filter {
            case (moves, _) => moves.startsWith(game.movesSoFar)
          }.keySet.toSeq
        if (potentialDraws.nonEmpty) {
          //println(plr + ": " + potentialDraws.size + " ways to a draw left.")
          Some(determineMove(game, potentialDraws))
        } else {
          // no winning path nor draw could be found, we'll loose
          // so just take any random move
          //println(plr + ": I take a random move since it is already clear I loose.")
          Some(game.remainingMoves.toSeq(Random.nextInt(game.remainingMoves.size)))
        }

      }
    }
  }

  /**
    * uses some heuristics to choose the best move.
    *
    * @param game
    * @param potentialMoves
    * @return
    */
  def determineMove(game: TicTacToe, potentialMoves: Seq[Seq[TMove]]): TMove = {
    // check if we could win with the next move
    val winningMove = game.lookAhead(PlayerB)
    if (winningMove.isDefined) {
      winningMove.get
    } else {
      // check if there is already an obvious threat from the opponent to win the game
      // if there is, we'll take the move
      val winningMoveForOpponent = game.lookAhead(PlayerA)
      if (winningMoveForOpponent.isDefined) {
        winningMoveForOpponent.get
      } else {
        // prefer the middle center f
        if (potentialMoves.exists {
          case moves => moves.drop(game.movesSoFar.length).head == MiddleCenter
        }) {
          MiddleCenter
        } else {
          // we take the shortest path to win
          val possibilities = potentialMoves.sortWith((a, b) => a.size < b.size)
          val aPathToWin = possibilities.head

          aPathToWin.drop(game.movesSoFar.length).head
        }
      }
    }
  }
}