package pl.edu.pw.mini.sg.dumpmatrix

import better.files.FileOps
import java.io.{File => JFile}
import org.rogach.scallop.{ ScallopConf }
import pl.edu.pw.mini.jk.sg.game.GameVariant
import pl.edu.pw.mini.jk.sg.game.common.{ Cloneable, SecurityGame }
import pl.edu.pw.mini.jk.sg.game.common.advanceable.AdvanceableAttackerState
import pl.edu.pw.mini.jk.sg.game.{ DefenderAttackerGame, NormalizableFactory }
import pl.edu.pw.mini.jk.sg.game.common.DefenderMove
import pl.edu.pw.mini.jk.sg.game.common.advanceable.AdvanceableDefenderState
import pl.edu.pw.mini.jk.sg.game.{ GameVariantRunner, NormalizableFactoryAction, SimpleGamePlugin }
import pl.edu.pw.mini.jk.sg.game.configuration.DecodeAdvanceableGraphGameConfiguraionJson
import pl.edu.pw.mini.jk.sg.game.graph.GraphGameVariantRunner
import pl.edu.pw.mini.jk.sg.opt.util.GameUtil
import pl.edu.pw.mini.sg.matrix.MatrixGamePlugin
import pl.edu.pw.mini.sg.movingtargets.MovingTargetsGamePlugin
import scala.io.Source
import scala.collection.JavaConverters._

object DumpMatrix {

  sealed trait Player
  final object Defender extends Player
  final object Attacker extends Player

  class CmdArgs(args: Seq[String]) extends ScallopConf(args) {
    implicit val gameVeriantConverter = org.rogach.scallop.singleArgConverter(_ match {
      case "SINGLE_MOVE_PER_TIMESTEP" => GameVariant.SingleMovePerTimestep
      case "MULTI_MOVE_PER_TIMESTEP" => GameVariant.MultiMovePerTimestep
      case "COMPACT_GAME" => GameVariant.CompactGame
      case "COORD_MOVE" => GameVariant.CoordMove
      case x => throw new IllegalArgumentException(s"Unknown game variant $x")
    })

    val gamePath = opt[JFile](name = "game-file", short = 'g', required = true)
    val roundLimit = opt[Int](name = "round-limit", short = 'r', descr = "If provided, game-file will be interpreted as a game on a graph. Otherwise extension will be matched")
    val outputFile = opt[JFile](name = "defender-output-file", short = 'o')
    val gameVariant = opt[GameVariant](name = "game-variant", short = 'V', descr = "Game variant to use. Single move per timestep is default", default = Some(GameVariant.SingleMovePerTimestep))
    val attackerOutputFile = opt[JFile](name = "attacker-output-file", short = 'a')

    verify()
  }

  final case class Matrix(rows: List[String], columns: List[String], matrix: Vector[Vector[(Double, Double)]]){
    def csvLines(player: Player) : Seq[Seq[String]] = {
      def extractor : ((Double, Double)) => String = player match {
        case Defender => _._1.toString
        case Attacker => _._2.toString
      }
      (""::columns)+:rows.zip(matrix).map{case (label, contents) => (label+:contents.map(extractor))}
    }
  }

  def main(args: Array[String]) : Unit = {
    val options = new CmdArgs(args)
    val gamePlugins: List[SimpleGamePlugin[_]] = List(
      MovingTargetsGamePlugin,
      MatrixGamePlugin
    )

    val runner: GameVariantRunner =
      if(options.roundLimit.isSupplied) {
        val aggc = DecodeAdvanceableGraphGameConfiguraionJson.readAggc(Source.fromFile(options.gamePath()).mkString)
        GraphGameVariantRunner(aggc, options.roundLimit())
      } else {
        gamePlugins.find(_.matchFile(options.gamePath())).map(_.fileToGameVariantRunner(options.gamePath())).getOrElse(throw new IllegalArgumentException("Unknown game extension"))
      }

    val matrix = options.gameVariant().runWithFactory(runner, new NormalizableFactoryAction[Matrix] {
      override def runWithNormalizableFactory[AM, DM <: DefenderMove,
        AS <: AdvanceableAttackerState[AM], DS <: AdvanceableDefenderState[DM],
        G <: SecurityGame with Cloneable[G] with DefenderAttackerGame[AM, DM, AS, DS, _]](
        factory: NormalizableFactory[AM,DM,G], gameLength: Int
      ) : Matrix = {
        val gameMatrices = GameUtil.calculateGameMatrices[AM, DM, AS, DS, G](factory, gameLength)

        Matrix(
          gameMatrices.getAttackerSequences.asScala.view.map(_.toString).toList,
          gameMatrices.getDefenderSequences.asScala.view.map(_.toString).toList,
        gameMatrices.getAttackerSequences.asScala.map(as => {
          gameMatrices.getDefenderSequences.asScala.map( ds => {
            val po = gameMatrices.getGamePayoff(ds, as)
            (po.getDefenderPayoff, po.getAttackerPayoff)
          }).toVector
        }).toVector
        )
      }
    }).methodResult

    import com.github.tototoshi.csv._
    if(options.outputFile.isSupplied) {
      val writer = CSVWriter.open(options.outputFile())
      writer.writeAll(matrix.csvLines(Defender))
      writer.close()
    } else {
      val writer = CSVWriter.open(Console.out)
      writer.writeAll(matrix.csvLines(Defender))
      writer.flush()
    }
    if(options.attackerOutputFile.isSupplied) {
      val writer = CSVWriter.open(options.attackerOutputFile())
      writer.writeAll(matrix.csvLines(Attacker))
      writer.close()
    }
  }
}
