package pl.edu.pw.mini.sg.se

import java.io.{File => JFile}

import argonaut.Argonaut.jEmptyObject
import argonaut.Json.JsonAssoc
import argonaut.{EncodeJson, Json, Parse}
import argonaut.JsonIdentity.ToJsonIdentity
import better.files.FileOps
import ch.qos.logback.classic.{Level, LoggerContext}
import ch.qos.logback.classic.gaffer.GafferConfigurator
import org.apache.commons.math3.random.RandomGenerator
import org.rogach.scallop.ScallopConf
import org.slf4j.Logger
import pl.edu.pw.mini.sg.game.deterministic.plugin.GameParams
import pl.edu.pw.mini.sg.game.results.{CpuInfo, GameDescriptor, TrainingResult}
import pl.edu.pw.mini.sg.results.MethodInfo
import squants.time.Nanoseconds

final class SEArgs(arguments: Seq[String]) extends ScallopConf(arguments) {
  val game = opt[JFile](name = "game", short = 'g', argName = "file", descr = "Game file", required = true)
  val output = opt[JFile](name = "output", short = 'o', argName = "file", descr = "Training result output file", required = true)
  val methodConfig = opt[JFile](name = "method-config", short = 'm', argName = "file", descr = "UctAttackerSampler config", required=true)
  val debug = opt[Boolean](name="debug", short = 'd')

  verify()
}

object SEFinder {
  val gamePlugins = pl.edu.pw.mini.sg.game.deterministic.Plugins.defaultPlugins

  def main(args: Array[String]) : Unit = {
    val context: LoggerContext = org.slf4j.LoggerFactory.getILoggerFactory().asInstanceOf[LoggerContext];
    val configurator = new GafferConfigurator(context);
    context.reset();
    configurator.run(SEFinder.getClass.getResource("logback.groovy"));

    val options = new SEArgs(args)

    if(options.debug()) {
      println("Logger debug")
      context.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.DEBUG)
    }

    val rng = new RNGImpl(new org.apache.commons.math3.random.MersenneTwister())

    val gamePlugin =
      options.game().toScala.extension.toRight("Game file must have an extension")
        .flatMap( ext => gamePlugins.find("."+_.fileExtension == ext).toRight(s"No game plugin for extension $ext")).left.map(new RuntimeException(_)).toTry.get


    val gameConfig = gamePlugin.readConfig(options.game()).get

    val game = gamePlugin.singleCoordFactory.create(gameConfig)

    val methodConfig = Parse.parse(options.methodConfig().toScala.contentAsString) match {
      case Right(c) => c
      case Left(err) => throw new RuntimeException(s"$err")
    }

    val strategyMethod = StrategyMethodParser.tryParse(DefaultMethods.list)(methodConfig)
      .fold(x => throw new RuntimeException(s"Parse failures: $x"), identity)

    val params = GameParams(gamePlugin.singleCoordFactory.maxDefenderPayoff(gameConfig),
      gamePlugin.singleCoordFactory.minDefenderPayoff(gameConfig))
    val startTime = System.nanoTime()
    val (strategy, payoff, preprocessTime) =
      strategyMethod.findBehavioralStrategy[
        gamePlugin.singleCoordFactory.DefenderMove,
        gamePlugin.singleCoordFactory.AttackerMove,
        gamePlugin.singleCoordFactory.DefenderState,
        gamePlugin.singleCoordFactory.AttackerState,
        gamePlugin.singleCoordFactory.Game
      ](game, params, rng)
    val endTime = System.nanoTime()

    val executionTime = Nanoseconds(endTime - startTime) - preprocessTime

    import gamePlugin.singleCoordFactory.json._

    val output : String = ToJsonIdentity(TrainingResult(
      payoff,
      strategy,
      strategyMethod.getMethodInfo(options.methodConfig().getName, petrus.se.BuildInfo.gitCommit, petrus.se.BuildInfo.version),
      executionTime,
      preprocessTime,
      CpuInfo(),
      GameDescriptor(gamePlugin.fileExtension,gameConfig.id))).asJson.toString

    options.output().toScala.write(output) 
  }
}
