package pl.edu.pw.mini.game.flipit.deterministic.noinfo

import argonaut.Parse
import org.scalactic.{ Tolerance, TolerantNumerics }
import org.scalatest.{ Matchers, WordSpec }
import pl.edu.pw.mini.game.flipit.deterministic.{ Flip, FlipItConfig, FlipItMove, NoOp }
import pl.edu.pw.mini.sg.game.graph.{ Edge, GraphConfig }
import scala.annotation.tailrec


class NoInfoFlipItGameSpec extends WordSpec with Matchers {
  "game with 3 rounds and simple graph" when {
    val config = FlipItConfig(
      id = null,
      graph = GraphConfig(2, Vector(Edge(0, 1))),
      rounds = 3, entryNodes = Set(0), defenderRewards = Vector(0.1, 0.2),
      defenderCosts=Vector(0.5, 0.2),
      attackerRewards=Vector(0.04, 0.4),
      attackerCosts=Vector(0.12, 0.67)
    )
    val game = NoInfoFlipItGameFactory.create(config)

    "playing noop moves" should {
      "end in two moves" in {
        val gg = game.makeMove(NoOp, NoOp).makeMove(NoOp, NoOp)
        gg.attackerState.possibleMoves should not be empty
        gg.isLeaf should equal(false)
            }
      "end in one move" in {
        val gg = game.makeMove(NoOp, NoOp)
        gg.attackerState.possibleMoves should not be empty
        gg.isLeaf should equal(false)
      }
      "end in three moves" in {
        val gg = game.makeMove(NoOp, NoOp).makeMove(NoOp, NoOp).makeMove(NoOp, NoOp)
        gg.attackerState.possibleMoves.isEmpty should be(true)
        gg.isLeaf should equal(true)
      }
    }
  }

  implicit val doubleEquality = TolerantNumerics.tolerantDoubleEquality(0.001)


  "game 4c-10" when {
    val config =
      Parse.decodeEither[FlipItConfig](
        scala.io.Source.fromInputStream(getClass.getResourceAsStream("flipit-4c-10.niflip")).mkString
      ).right.get
    val game = NoInfoFlipItGameFactory.create(config)

    def gameTest(defenderMove: List[FlipItMove], attackerMove: List[FlipItMove],
      defenderPayoff: Double, attackerPayoff: Double) = {
      require(defenderMove.length == attackerMove.length)
      @tailrec def checkRound(gameState: NoInfoFlipItGame, defenderMove: List[FlipItMove],
        attackerMove: List[FlipItMove], round: Int): Unit = {
        (attackerMove, defenderMove) match {
          case (am::ams, dm::dms) => {
            s"allow A: ${am} in round ${round}" in {
              gameState.attackerState.possibleMoves should contain(am)
            }
            s"allow D: ${dm} in round ${round}" in {
              gameState.defenderState.possibleMoves should contain(dm)
            }

            checkRound(gameState.makeMove(dm, am), dms, ams, round+1)
          }
          case _ => {
            "be in terminal state after all moves" in {
              gameState.defenderState.possibleMoves should equal(Nil)
              gameState.attackerState.possibleMoves should equal(Nil)
            }
            s"give defender reward of ${defenderPayoff}" in {
              gameState.defenderPayoff should equal(defenderPayoff)
            }
            s"give attcker reward of ${attackerPayoff}" in {
              gameState.attackerPayoff should equal(attackerPayoff)
            }
          }
        }
      }
      s"playing sequence D(${defenderMove}) A(${attackerMove})" should {
        checkRound(game, defenderMove, attackerMove, 1)
      }
    }

    gameTest(List(Flip(0), NoOp, NoOp, NoOp, Flip(1)),
      List(Flip(0), NoOp, Flip(1), Flip(3), NoOp), 97.841, -11.798)

    gameTest(List(Flip(0), Flip(0), Flip(0), Flip(0), Flip(1)),
      List(Flip(0), NoOp, Flip(1), Flip(3), NoOp), 80.526, -11.798)

    gameTest(List(NoOp, NoOp, Flip(0), NoOp, Flip(1)),
      List(Flip(0), NoOp, Flip(1), Flip(3), NoOp), 71.459, 14.583)
    
  }
}
