package pl.edu.pw.mini.jk.sg.gteval

import java.io.{File => JFile}
import pl.edu.pw.mini.jk.sg.game.DefenderAttackerGame

import scala.collection.JavaConverters._

import com.google.common.collect.{ImmutableList, ImmutableMap}
import pl.edu.pw.mini.jk.sg.game.{GameVariantRunnerResult, NormalizableFactory, NormalizableFactoryAction}
import pl.edu.pw.mini.jk.sg.game.common.{DefenderMove, SecurityGame}
import pl.edu.pw.mini.jk.sg.game.common.advanceable.{AdvanceableAttackerState, AdvanceableDefenderState}
import pl.edu.pw.mini.jk.sg.gteval.config.GtevalOptions
import pl.edu.pw.mini.jk.sg.gteval.result.TimedTrainingResult
import pl.edu.pw.mini.jk.sg.mixeduct.attackerresponse.OptimalAttacker
import pl.edu.pw.mini.jk.sg.opt.util.{GameUtil, ScipUtil}
import better.files.{File, FileOps}
import scala.util.matching.Regex
import squants.time.{ Seconds, Time }

final class MilpStrategy

object MilpStrategy {


  def commonPrefixLength(s1: String, s2: String) : Int = {
    Range(0, Math.min(s1.length, s2.length)).find(i => s1.charAt(i) != s2.charAt(i))
      .getOrElse(Math.min(s1.length(), s2.length()))
  }

  def parseGurobiStatTime(file: File) : Option[Time] = {
    val cts = file.contentAsString()
    parseGurobiStatTime(cts)
  }

  def parseGurobiStatTime(statCts: String) : Option[Time] = {
    val regex = new Regex("in ([0-9]+.[0-9]+) seconds")
    val matcher = regex.pattern.matcher(statCts)
    if(matcher.find()) {
      Some(Seconds(matcher.group(1).toDouble))
    } else {
      None
    }

  }

  def milpPlayer[AM,
    DM <: DefenderMove,
    AS <: AdvanceableAttackerState[AM],
    DS <: AdvanceableDefenderState[DM],
    G <: pl.edu.pw.mini.jk.sg.game.common.Cloneable[G] with DefenderAttackerGame[AM, DM, AS, DS, _]
  ](
    gameMatrices: GameMatrices[AM, DM, AS, DS, G], solutionPath: JFile
  ) : TimedTrainingResult[DM, AM] = {
    val readSolutionColumnValues : java.util.Map[String, java.lang.Double]  =
      ScipUtil.readSolutionColumnValues(solutionPath);

    val solFile = solutionPath.toScala
    val solDir = solutionPath.toScala.parent
    val bestMatch = solDir.glob("*.gurobi.stat", true).maxBy(f => commonPrefixLength(f.name, solFile.name))

    val timeMs = parseGurobiStatTime(bestMatch).map(_.toMilliseconds).getOrElse(-1.0)

    val dcp2: java.util.Map[ImmutableList[DM], java.lang.Double]=
      ScipUtil.defenderCodeProbabilities(readSolutionColumnValues).entrySet
        .asScala.view.map(e => (gameMatrices.getDefenderSequences.get(e.getKey), e.getValue)).toMap.asJava
    val dap2 : java.util.Map[ImmutableList[AM], java.lang.Double] =
      OptimalAttacker.solveAttackerOptimalStrategyPlus(gameMatrices, dcp2).asScala
        .view.map(e => (e.getKey, e.getValue)).toMap.asJava
    new TimedTrainingResult(ImmutableMap.copyOf(dcp2), ImmutableMap.copyOf(dap2), gameMatrices, timeMs.toLong);
  }

  def milpPlayer(options: GtevalOptions) : GameVariantRunnerResult[TimedTrainingResult[_,_]] = {

    val runner = new NormalizableFactoryAction[TimedTrainingResult[_,_]] {
      override def runWithNormalizableFactory[AM, DM <: DefenderMove, AS <: AdvanceableAttackerState[AM],
        DS <: AdvanceableDefenderState[DM],
        G <: SecurityGame with pl.edu.pw.mini.jk.sg.game.common.Cloneable[G] with DefenderAttackerGame[AM, DM, AS, DS, _]] (
          factory: NormalizableFactory[AM, DM, G], roundLimit: Int
      ) : TimedTrainingResult[DM, AM] = {
        milpPlayer[AM, DM, AS, DS, G](GameUtil.calculateGameMatrices(factory, roundLimit), options.getStackelbergSolutionPath())
      }
    }

    options.getGameVariantRunner.runWithCompactGameFactory(runner)
  }
}
