package pl.edu.pw.mini.sg.strategychecker

import argonaut.{ Json, Parse }
import better.files.File
import java.util.UUID
import org.rogach.scallop.ScallopConf
import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationality
import pl.edu.pw.mini.sg.game.deterministic.{ BehavioralStrategy, PlayerVisibleState, PureStrategy }
import pl.edu.pw.mini.sg.game.deterministic.plugin.{ GamePlugin, GamePluginDecoding }
import pl.edu.pw.mini.sg.game.graph.GraphGamePlugin
import pl.edu.pw.mini.sg.game.graph.sud.{ DeterministicSingleUnitMoveGraphGame, SingleUnitDefenderState }
import pl.edu.pw.mini.sg.game.results.TrainingResult
import pl.edu.pw.mini.sg.game.util.attacker.OptimalAttacker
import scala.util.Try

object Checker {
  private final class Options(args: Seq[String]) extends ScallopConf(args) {
    val trainingResultFile = opt[String]("training-result", 'r', required = true).map(x => File(x))
    val gamesDirectory = opt[String]("games-dir", 'g', required = true).map(x => File(x))
    val dumpMixedStrategy = opt[Boolean]("dump-mixed-strategy", 'm')
    val dumpOptimalAttacker = opt[Boolean]("dump-optimal-attacker", 'a')
    val dumpOptimalAttackerFlat = opt[Boolean]("dump-optimal-attacker-flat", 'f')
    val dumpPayoffs = opt[Boolean]("dump-payoffs", 'p')

    verify()
  }

  private val plugins : List[GamePluginDecoding] = List(GraphGamePlugin)

  def getPlugin(game: String) : Either[String, GamePluginDecoding] = {
    plugins.find(_.fileExtension == game).map(Right.apply).getOrElse(Left(s"Game plugin for ${game} not found"))
  }

  def buildGameLibrary(gameDir: File, extension: String, plugin: GamePluginDecoding) : Map[UUID, plugin.GameConfig] = {
    gameDir.glob(s"*.${extension}", true).map(path => plugin.readConfig(path.toJava).recoverWith{case th => Try[plugin.GameConfig](throw new RuntimeException(path.toString(), th))}.get).map(x => (x.id, x)).toMap
  }

  def runWithPlugin(plugin: GamePluginDecoding, gamesDirectory: File, trainingResultJson: Json, options: Options) : Unit = {
    val games : Map[UUID, plugin.GameConfig] = buildGameLibrary(gamesDirectory, plugin.fileExtension, plugin)
    val trainingResult = TrainingResult.trainingResultDecode(plugin)(games).decodeJson(trainingResultJson).toEither
      .fold(err => throw new RuntimeException(s"${err}"), identity)
    val game = plugin.singleCoordFactory.create(games(trainingResult.game.id))
    val (optimalAttacker, payoff) = OptimalAttacker.calculate[
      plugin.singleCoordFactory.DefenderMove,
      plugin.singleCoordFactory.AttackerMove,
      plugin.singleCoordFactory.DefenderState,
      plugin.singleCoordFactory.AttackerState,
      plugin.singleCoordFactory.Game
    ](game, trainingResult.defenderStrategy)(BoundedRationality.fullyRational)


    if(options.dumpOptimalAttacker()) {
      println("Optimal attacker:")
      println(optimalAttacker)
    }
    if(options.dumpPayoffs()) {
      println("Payoff:")
      println(payoff)
    }
    if(options.dumpMixedStrategy()) {
      println("Flat mixed strategy")
      makeFlatStrategy(trainingResult.defenderStrategy).foreach(println)
    }
    if(options.dumpOptimalAttackerFlat()) {
      println("Flat optimal attacker")
      ???
    }
  }

  private def makeFlatStrategy[DM, DS <: PlayerVisibleState[DM]](strategy: BehavioralStrategy[DM, DS]) : List[(Double, List[DM])] = if(strategy.state.possibleMoves.isEmpty)
    List((1.0, Nil))
  else
    strategy.probabilities.flatMap{ case (m, prob) => {
      val sfi = strategy.successorsInTree.filter(_._1==m)
      sfi.filter(if (sfi.find(_._2.possibleMoves.nonEmpty).nonEmpty) _._2.possibleMoves.nonEmpty else (x=> true))
      .flatMap{case (dm, ds) =>
        makeFlatStrategy(strategy.nextState(dm, ds)).map{case (pr, str) => (pr*prob, dm::str)}
      }}
    }.toList

  def main(args: Array[String]) : Unit = {
    val options = new Options(args)

    (for {
      trainingResultJson <- Parse.parse(options.trainingResultFile().contentAsString)
      gameType <- trainingResultJson.field("game").flatMap(_.field("type")) match {
        case Some(t) => t.as[String].toEither.left.map(_.toString())
        case None => Left("No game.type field in training result!!")
      }
      plugin <- getPlugin(gameType)
    } yield runWithPlugin(plugin, options.gamesDirectory(), trainingResultJson, options)).fold(
      error => println(s"Error occured: ${error}"),
      identity
    )
  }
}
