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

import pl.edu.pw.mini.sg.movingtargets.{AttackStart, DefenderPosition, MovingTargetsGameConfig, MovingTargetsImmutableGame}
import canes.game.{DefenderPosition => DDefenderPosition}
import pl.edu.pw.mini.jk.sg.game.immutable.Payoffs
import pl.edu.pw.mini.sg.compactlp.model.{AttackerWithPayoffs, Flow, GridPosition, GridSpacetime}
import pl.edu.pw.mini.sg.compactlp.util.SparseUtils._
import pl.edu.pw.mini.sg.compactlp.util.{Attacker, Position}
import pl.edu.pw.mini.sg.util.TraversableUtils._
import util.MathUtils.interpolation2d

class MovingTargetsGameWrapper(game: MovingTargetsGameConfig) extends CompactGameWrapper {

  private implicit val gC = game

  override def getAttackerStrategiesWithPayoffs: Seq[AttackerWithPayoffs] = {
    (for {t <- 1 to getTimeSteps(sparsity = 1); target <- game.targets}
      yield {
        val times = t to math.min(t + getAttackLength, getTimeSteps(sparsity = 1))
        if (times.length == getAttackLength + 1)
          AttackerWithPayoffs(Attacker(target.id, t), target.attackerUtilitySuccessfulAttack, target.attackerUtilityUnuccessfulAttack,
            target.defenderUtilitySuccessfulAttack, target.defenderUtilityUnuccessfulAttack)
        else
          AttackerWithPayoffs(Attacker(target.id, t), 0.0, target.attackerUtilityUnuccessfulAttack,
            0.0, target.defenderUtilityUnuccessfulAttack)
      }) :+ AttackerWithPayoffs(Attacker(-1, -1), 0.0, 0.0, 0.0, 0.0) //Jak atakujacy nic nie robi
  }

  private def getAttackerPositions(attacker: Attacker): Seq[(Int, Double, Double)] = {
    attacker match {
      case Attacker(target, t) =>
        if (t < 0 || target < 0)
          return Seq.empty[(Int, Double, Double)]
        (t to math.min(t + getAttackLength, getTimeSteps(sparsity = 1))).map {
          time => {
            val targetPos = game.getTargetPosition(target, time)
            (time, targetPos._1, targetPos._2)
          }
        }
    }
  }

  override def getPayoffs(attackerStrategy: Option[Attacker], defenderPositions: List[Seq[Position]]): (Double, Double) = {
    if (attackerStrategy.isEmpty)
      return (0.0, 0.0)

    val times = attackerStrategy.get.startTime to math.min(attackerStrategy.get.startTime + getAttackLength, getTimeSteps(sparsity = 1))
    var lastAttack = attackerStrategy.map(s => AttackStart(s.startTime, s.target))
    val payoffs = times.map(t => {
      val d = defenderPositions(t - 1)
      val rewardAttack = game.getRewardForAS(lastAttack, d.map(p => {
        val pos = game.realPositionToGrid(DDefenderPosition(p.x, p.y))
        DefenderPosition.cached(pos._1.round.toInt, pos._2.round.toInt)
      }),
        t)
      lastAttack = rewardAttack._2
      rewardAttack._1
    }).foldLeft(Payoffs(0.0, 0.0, 0, 0))((sum, i) => Payoffs(sum.defenderPayoff + i.defenderPayoff, sum.attackerPayoff + i.attackerPayoff, 0, 0))
    (payoffs.defenderPayoff, payoffs.attackerPayoff)
  }

  override def getZones(sparsity: Int): Seq[GridPosition] = {
    val boundsX = getXBounds(sparsity)
    val boundsY = getYBounds(sparsity)
    ((boundsX._1 to boundsX._2) cross (boundsY._1 to boundsY._2)).map(p => GridPosition(p._1, p._2)).toSeq
  }

  private def getXBounds(sparsity: Int): (Int, Int) = {
    ((game.leftGridLimit / sparsity.toDouble).ceil.toInt, (game.rightGridLimit / sparsity.toDouble).ceil.toInt)
  }

  private def getYBounds(sparsity: Int): (Int, Int) = {
    ((game.bottomGridLimit / sparsity.toDouble).ceil.toInt, (game.topGridLimit / sparsity.toDouble).ceil.toInt)
  }

  override def gridToRealPosition(position: GridPosition): Position = {
    val (x, y) = DefenderPosition.cached(position.x, position.y).asPositionTuple
    Position(x, y)
  }

  override def getTimeSteps(sparsity: Int): Int = (game.gameLength / sparsity.toDouble).ceil.toInt

  override def getDefenderCount: Int = game.defenderCount

  override def getDefenderStartingPositions(sparsity: Int): Seq[GridPosition] = {
    game.defenderStartingPositions.map(p => positionToSparse(GridPosition(p), sparsity))
  }

  override def getAttackLength: Int = game.attackLength

  //Wszystkie te pola, ktore chronia dane pozycje
  override def getNeighbours(attacker: Attacker): Seq[GridSpacetime] = {
    val grid = getZones(sparsity = 1) cross (1 to getTimeSteps(sparsity = 1))
    val positions = getAttackerPositions(attacker)
    grid.filter {
      case (GridPosition(x, y), t) =>
        val defPos = DefenderPosition.cached(x, y).asPositionTuple
        positions.exists {
          case (_t, _x, _y) =>
            val dx = defPos._1 - _x
            val dy = defPos._2 - _y
            t == _t && dx * dx + dy * dy <= game.defenderRangeSq
        }
    }.map { case (p, t) => GridSpacetime(t, p) }.toSeq
  }

  def getCoverage(flow: Flow, attackerNeighbours: Seq[GridSpacetime], sparsity: Int): Double = {
    val realI = sparseToPosition(flow.from, sparsity)
    val realJ = sparseToPosition(flow.to, sparsity)
    val realTime = flow.time * sparsity
    val realEndTime = math.min(getTimeSteps(sparsity = 1), realTime + sparsity)
    val interp: Int => GridPosition = interpolation2d(realI, realJ, realTime, realTime + sparsity)
    //Tylko dla czasu 0 uwzgledniamy pierwszy krok, inaczej bylby wielokrotnie uwzgledniony
    ((realTime + (if (flow.time == 0) 0 else 1)) to realEndTime).flatMap(t => {
      interp(t) match {
        case GridPosition(x, y) =>
          val xRange = (x - (sparsity / 2.0).ceil.toInt + 1) to (x + (sparsity / 2.0).floor.toInt)
          val yRange = (y - (sparsity / 2.0).ceil.toInt + 1) to (y + (sparsity / 2.0).floor.toInt)

          (xRange cross yRange).map(p => GridSpacetime(t, GridPosition(p._1, p._2)))
      }
    }).count(attackerNeighbours.contains(_)).toDouble / (sparsity * sparsity)
  }

  override def getGridNeighbours(position: GridPosition, sparsity: Int): Seq[GridPosition] = {
    position match {
      case GridPosition(x, y) =>
        game.possiblePositions(DefenderPosition.cached(x, y)).map(_.asTuple).flatMap {
          case (_x, _y) =>
            val boundsX = getXBounds(sparsity)
            val boundsY = getYBounds(sparsity)

            if (_x < boundsX._1 || _x > boundsX._2 || _y < boundsY._1 || _y > boundsY._2)
              None
            else
              Some(GridPosition(_x, _y))
        }
    }
  }

}
