package petrus

import argonaut.Parse
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.classic.{ Level, LoggerContext }
import ch.qos.logback.core.{ ConsoleAppender, FileAppender }
import org.apache.commons.math3.random.MersenneTwister
import org.scalactic.TolerantNumerics
import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpec }
import org.slf4j.Logger
import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationality
import pl.edu.pw.mini.sg.game.deterministic.PureStrategy
import pl.edu.pw.mini.sg.game.deterministic.single.{ SimplePureStrategy}
import pl.edu.pw.mini.sg.matrix.deterministic.{ MatrixGame, MatrixGameState }
import pl.edu.pw.mini.sg.matrix.{ CoordMove, MatrixGameConfig }
import pl.edu.pw.mini.sg.mcts.propagation.SimplePropagation
import pl.edu.pw.mini.sg.mcts.tree.stat.{ MaxStatCalculator, MeanStatCalculator }
import pl.edu.pw.mini.sg.mcts.tree.simple.SimpleTreeFactory
import pl.edu.pw.mini.sg.mcts.{ MctsParameters, UniformRandomMove }
import pl.edu.pw.mini.sg.mcts.selection.UctSelection
import pl.edu.pw.mini.sg.mixed.FeasibleStrategy
import pl.edu.pw.mini.sg.mixed.adjuster.bisectrotate.BisectRotateAdjuster
import pl.edu.pw.mini.sg.mixed.adjuster.decreasing.DecreasingRateAdjuster
import pl.edu.pw.mini.sg.mixed.adjuster.inhibitor.LinearInhibitingStrategyAdjuster
import pl.edu.pw.mini.sg.mixed.expansion.MctsNodeExpansion
import pl.edu.pw.mini.sg.mixed.response.HistorySkipConfig
import pl.edu.pw.mini.sg.mixed.{ MixedMethodConfig, UctAttackerSampler }
import pl.edu.pw.mini.sg.mixed.expander.{ MctsExpander, NegativeAttacker, PositiveDefender }
import pl.edu.pw.mini.sg.mixed.expansion.RandomNodeExpansion
import pl.edu.pw.mini.sg.mixed.stchooser.ValueSubtreeChooser
import pl.edu.pw.mini.sg.mixed.adjuster.signchange.{SignChangeCounterGlobalAdjuster, SignChangeCounterLocalAdjuster}
import pl.edu.pw.mini.sg.mixed.adjuster.globaldamping.GlobalDampingAdjuster
import pl.edu.pw.mini.sg.mixed.adjuster.coorddamping.CoordDampingAdjuster
import pl.edu.pw.mini.sg.mixed.decision.AlwaysImproveDecision
import pl.edu.pw.mini.sg.mixed.adjuster.momentum.{MomentumAdjuster, MomentumDirectionLimitAdjuster}
import pl.edu.pw.mini.sg.mixed.adjuster.pushtoedge.{PushToEdgeAdjuster, IterativePushToEdgeAdjuster}
import pl.edu.pw.mini.sg.mixed.prune.NoPrune
import pl.edu.pw.mini.sg.matrix.deterministic.{MatrixGamePlugin, MatrixGameFactory}
import pl.edu.pw.mini.sg.mixed.response.provider.ExactResponseProvider
import scala.io.Source
import pl.edu.pw.mini.sg.se.RNGImpl


class MatrixPayoffValueSpec extends WordSpec with Matchers with BeforeAndAfterAll {
  override def beforeAll() = {
    ch.qos.logback.classic.BasicConfigurator.configureDefaultContext()
    val context: LoggerContext = org.slf4j.LoggerFactory.getILoggerFactory().asInstanceOf[LoggerContext];
    val rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME)
    rootLogger.detachAndStopAllAppenders()
    rootLogger.setLevel(Level.INFO)
    rootLogger.addAppender({
      val appender = new FileAppender[ILoggingEvent]()
      appender.setFile("matrix-test.log")
      appender.setContext(context)
      appender.setEncoder({
        val enc = new PatternLayoutEncoder()
        enc.setContext(context)
        enc.setPattern("[%thread] %-20logger %level - %msg%n")
        enc.start()
        enc
      })
      appender.start()
      appender
    })
    val peaLogger = context.getLogger("pl.edu.pw.mini.sg.mixed")
    peaLogger.setLevel(Level.DEBUG)
//    context.getLogger("pl.edu.pw.mini.sg.mixed.MixedMethod$").setLevel(Level.DEBUG)
//    context.getLogger("pl.edu.pw.mini.sg.mixed.UctAttackerSampler$").setLevel(Level.DEBUG)
//    context.start();
  }

  implicit val doubleEquality = TolerantNumerics.tolerantDoubleEquality(0.003)
  val rng = new RNGImpl(new org.apache.commons.math3.random.MersenneTwister())


  val mctsConfig = MctsParameters(
    simulationsInRoot = 300,
    mctsSelection = UctSelection(1.4),
    mcPhaseMove = UniformRandomMove,
    propagation = SimplePropagation,
    treeFactory = SimpleTreeFactory(MeanStatCalculator)
  )

  val mixedMethodConfig = MixedMethodConfig(
    allRefinementIterations = 5000,
    feasibilityTryIterations = 5000,
    //strategyAdjuster = InhibitingStrategyAdjuster(1),
    //strategyAdjuster = LinearInhibitingStrategyAdjuster(1),
    strategyAdjuster = MomentumAdjuster,
    //strategyAdjuster = MomentumDirectionLimitAdjuster,
    //strategyAdjuster = CoordDampingAdjuster,
    //strategyAdjuster = GlobalDampingAdjuster,
//    strategyAdjuster = new PushToEdgeAdjuster(new DecreasingRateAdjuster(1)),
//    strategyAdjuster = new BisectRotateAdjuster(new DecreasingRateAdjuster(1),30.0),
    // expansionDecision = RandomNodeExpansion(0.8,0.7,
    //   initialExpander = MctsExpander(mctsConfig, PositiveDefender),
    //   positiveExpander = MctsExpander(mctsConfig, PositiveDefender),
    //   negativeExpander = MctsExpander(mctsConfig, NegativeAttacker),
    //   expandWorsePayoff = false
    // ),
    expansionDecision = MctsNodeExpansion(mctsConfig.copy(treeFactory = SimpleTreeFactory(MaxStatCalculator)), false, 0.4),
    subtreeChooser = ValueSubtreeChooser(0.6),
    poorImprovementIterations = 1000,
    poorImprovementEps = 1e-6,
    improveDecision = AlwaysImproveDecision,
    attackerOracleProvider = ExactResponseProvider(HistorySkipConfig.noSkip),
    treePruneDecision = NoPrune,
    boundedRationalityStrategy = BoundedRationality.fullyRational
  )

  val matrixGamePlugin = MatrixGamePlugin

  final def matrixTest(filename: String, optimalAttacker: Int, expectedOptimalDefenderPayoff: Double) = {
    implicit val configDecoder = matrixGamePlugin.configJsonDecode
    val gameConfig = Parse.decode[MatrixGameConfig](
      Source.fromInputStream(getClass.getResourceAsStream(filename)).mkString
    ) match {
      case Right(v) => v
    }

    val game = MatrixGameFactory.create(gameConfig)
    val optimalAttackerStrategy = new SimplePureStrategy[Int, MatrixGameState](optimalAttacker, game.attackerState, Map())

    s"give payoff ${expectedOptimalDefenderPayoff} against optimal attacker" in {
      UctAttackerSampler.defenderForGivenAttacker[
        Int, Int,
        MatrixGameState, MatrixGameState,
        MatrixGame
      ](mixedMethodConfig)(game, optimalAttackerStrategy)()(rng) match {
        case FeasibleStrategy(optimalDefender, optimalDefenderPayoff) => optimalDefenderPayoff.real.defender should equal(expectedOptimalDefenderPayoff)
        case _ => fail("No feasible strategy found")
      }
    }
  }

  "matrix game 1" should {
    matrixTest("mat-100-20-02-1.nfgame", 10, 0.9820)
  }

  "matrix game 2" should {
    matrixTest("mat-100-20-02-2.nfgame",16 , .99664)
  }

  "matrix game 3" should {
    matrixTest("mat-100-20-02-3.nfgame",16 , .9892255)
  }
}
