package pl.edu.pw.mini.sg.compactlp

import java.util.UUID

import argonaut.{JsonIdentity, Parse}
import canes.game.{Depot, MovingTargetsGame, Target}
import org.scalatest.{Matchers, WordSpec}
import pl.edu.pw.mini.sg.movingtargets.{MovingTargetsGameConfig, MovingTargetsGameConfigReader}
import MovingTargetsGameConfigReader.gameDecode
import pl.edu.pw.mini.sg.compactlp.model.{AttackerWithPayoffs, Flow, GridPosition, GridSpacetime}
import pl.edu.pw.mini.sg.compactlp.util.Attacker

class MovingTargetGameWrapperSpec extends WordSpec with Matchers {
  private val gameSpec = MovingTargetsGame(UUID.randomUUID(),
    depots = List(Depot(1, 1, 1), Depot(2, 3, 3)),
    targets = List(Target(1, 1, 2, List(1), 5, -2, -2, 3)),
    targetSpeed = 1,
    defenderCount = 2,
    defenderSpeed = 2,
    defenderStartDepots = List(1,1),
    defenderRange = 1,
    gameTime = 5,
    timeInterval = 1,
    attackLengthRealtime = 0)

  private val gameSpecLongAttack = MovingTargetsGame(UUID.randomUUID(),
    depots = List(Depot(1, 1, 1), Depot(2, 3, 3)),
    targets = List(Target(1, 1, 2, List(1), 5, -2, -2, 3)),
    targetSpeed = 1,
    defenderCount = 2,
    defenderSpeed = 2,
    defenderStartDepots = List(1,1),
    defenderRange = 1,
    gameTime = 5,
    timeInterval = 1,
    attackLengthRealtime = 2)


  private val gameSpecMultiTarget = MovingTargetsGame(UUID.randomUUID(),
    depots = List(Depot(1, 1, 1), Depot(2, 3, 3)),
    targets = List(Target(1, 1, 2, List(1), 5, -2, -2, 3), Target(2, 2, 1, List(1), 4, -3, -4, 1)),
    targetSpeed = 1,
    defenderCount = 2,
    defenderSpeed = 2,
    defenderStartDepots = List(1,2),
    defenderRange = 1,
    gameTime = 5,
    timeInterval = 1,
    attackLengthRealtime = 0)

  "The MovingTargetGameWrapper class" should {
    val game = setupGame(gameSpec)
    val gameLongAttack = setupGame(gameSpecLongAttack)
    val gameMultiTarget = setupGame(gameSpecMultiTarget)
    "provide a getAttackerStrategiesWithPayoffs method" which {
      "returns correct strategies" in {
        val strategies = game.getAttackerStrategiesWithPayoffs
        strategies should contain theSameElementsAs Vector(
          AttackerWithPayoffs(Attacker(1, 1),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 2),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 3),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 4),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 5),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(-1, -1), 0, 0, 0 ,0)
        )
      }

      "returns strategies with 0 payoff for attacker when he cannot finish" in {
        val strategies = gameLongAttack.getAttackerStrategiesWithPayoffs
        strategies should contain theSameElementsAs Vector(
          AttackerWithPayoffs(Attacker(1, 1),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 2),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 3),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 4),  0, -2, 0, 3),
          AttackerWithPayoffs(Attacker(1, 5),  0, -2, 0, 3),
          AttackerWithPayoffs(Attacker(-1, -1), 0, 0, 0 ,0)
        )
      }

      "returns correct strategies for multi-target game" in {
        val strategies = gameMultiTarget.getAttackerStrategiesWithPayoffs
        strategies should contain theSameElementsAs Vector (
          AttackerWithPayoffs(Attacker(1, 1),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 2),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 3),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 4),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(1, 5),  5, -2, -2, 3),
          AttackerWithPayoffs(Attacker(2, 1),  4, -3, -4, 1),
          AttackerWithPayoffs(Attacker(2, 2),  4, -3, -4, 1),
          AttackerWithPayoffs(Attacker(2, 3),  4, -3, -4, 1),
          AttackerWithPayoffs(Attacker(2, 4),  4, -3, -4, 1),
          AttackerWithPayoffs(Attacker(2, 5),  4, -3, -4, 1),
          AttackerWithPayoffs(Attacker(-1, -1), 0, 0, 0 ,0)
        )
      }
    }

    "provide a getZones method" which {
      "returns original grid for sparisty 1" in {
        val grid = game.getZones(1)
        grid should contain theSameElementsAs Vector(
          GridPosition(0, 0), GridPosition(0, 1), GridPosition(0, 2),
          GridPosition(1, 0), GridPosition(1, 1), GridPosition(1, 2),
          GridPosition(2, 0), GridPosition(2, 1), GridPosition(2, 2)
        )
      }

      "returns aggregated grid for sparisty 2" in {
        val grid = game.getZones(2)
        grid should contain theSameElementsAs Vector(
          GridPosition(0, 0), GridPosition(0, 1),
          GridPosition(1, 0), GridPosition(1, 1)
        )
      }
    }

    "provide a getTimeSteps method" which {
      "returns original game time for sparsity 1" in {
        val time = game.getTimeSteps(1)
        time should equal (5)
      }

      "returns correct aggregated time for sparsity 4" in {
        val time = game.getTimeSteps(4)
        time should equal (2)
      }
    }

    "provide a getDefenderStartingPositions method" which {
      "returns original starting positions for sparsity 1" in {
        val positions = gameMultiTarget.getDefenderStartingPositions(1)
        positions should contain theSameElementsAs Vector(
          GridPosition(0, 0),
          GridPosition(2, 2)
        )
      }

      "returns aggregated positions for sparsity 2" in {
        val positions = gameMultiTarget.getDefenderStartingPositions(2)
        positions should contain theSameElementsAs Vector(
          GridPosition(0, 0),
          GridPosition(1, 1)
        )
      }
    }

    "provides a getNeighbours method" which {
      "returns positions which catch the given attacker" in {
        val positions = gameLongAttack.getNeighbours(Attacker(1, 1))
        positions should contain theSameElementsAs Vector(
          GridSpacetime(1, GridPosition(0, 0)), GridSpacetime(1, GridPosition(0, 1)), GridSpacetime(1, GridPosition(1, 0)),
          GridSpacetime(2, GridPosition(0, 0)), GridSpacetime(2, GridPosition(0, 1)), GridSpacetime(2, GridPosition(1, 0)),
          GridSpacetime(2, GridPosition(1, 1)),
          GridSpacetime(3, GridPosition(1, 1)), GridSpacetime(3, GridPosition(1, 2)), GridSpacetime(3, GridPosition(2, 1)),
          GridSpacetime(3, GridPosition(2, 2))
        )
      }
    }

    "provides a getGridNeighbours method" which {
      "returns reachable positions for the defender for sparsity 1" in {
        val positions = game.getGridNeighbours(GridPosition(2, 1), 1)
        positions should contain theSameElementsAs Vector(
          GridPosition(1, 0), GridPosition(0, 1), GridPosition(1, 1), GridPosition(2, 1), GridPosition(1, 2), GridPosition(2, 2),
          GridPosition(2, 0)
        )
      }

      "returns aggregated reachable positions for sparsity 2" in {
        val positions = game.getGridNeighbours(GridPosition(0, 0), 2)
        positions should contain theSameElementsAs Vector(
          GridPosition(1, 0), GridPosition(0, 1), GridPosition(1, 1), GridPosition(0, 0)
        )
      }
    }

    "provide a getCoverage method" which {
      "returns percentage of covered grid for sparsity 2" in {
        val coverage = game.getCoverage(Flow(GridPosition(0, 0), GridPosition(1, 1), 0), game.getNeighbours(Attacker(1, 1)), 2)
        coverage should equal (0.75)
      }
    }
  }

  private def setupGame(gameSpec: MovingTargetsGame): MovingTargetsGameWrapper = {
    val gameJson = JsonIdentity.ToJsonIdentity(gameSpec).asJson(canes.game.MovingTargetsGameConfigReader.gameDecode).toString
    val gameConfig = Parse.decode[MovingTargetsGameConfig](gameJson) match {
      case Right(c) => c
    }
    new MovingTargetsGameWrapper(gameConfig)
  }


}
