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

import pl.edu.pw.mini.sg.util.TraversableUtils._
import MathUtils.interpolation2d
import pl.edu.pw.mini.sg.compactlp.model.GridPosition

/**
  * Klasa pomocnicza sluzaca glownie do konwersji miedzy siatka zagregowana i oryginalna
  */
object SparseUtils {
  /**
    * Zwraca wspolrzedne punktu w siatce zagregowanej
    *
    * @param position Punkt z oryginalnej siatki
    * @param sparsity Wspolczynnik agregacji s
    * @return Punkt w zagregowanej siatce
    */
  def positionToSparse(position: GridPosition, sparsity: Int): GridPosition = {
    GridPosition((position.x / sparsity.toDouble).ceil.toInt, (position.y / sparsity.toDouble).ceil.toInt)
  }

  /**
    * Zwraca dla punktu z zagregowanej siatki jego odpowiednik w oryginalnej siatce
    * Dla parzystego s:
    * |_|_|_|_|
    * |_|x|_|_|
    * |_|_|_|_|
    * | | | | | lewy gorny punkt ze srodkowego kwadratu 2x2
    * Dla nieparzystego s:
    * |_|_|_|
    * |_|x|_|
    * |_|_|_| Srodkowy punkt
    *
    * @param sparsePosition Wspolrzedne punktu w siatce zagregowanej
    * @param sparsity       Wspolczynnik agregacji s
    * @return Wspolrzedne odpowiednika punktu z zagregowanej siatki w oryginalnej siatce
    */
  def sparseToPosition(sparsePosition: GridPosition, sparsity: Int): GridPosition = {
    GridPosition(sparsePosition.x * sparsity - sparsity / 2, sparsePosition.y * sparsity - sparsity / 2)
  }

  /**
    * Zwraca wszystkie punkty z kwadratu s na s wokol podanej pozycji w siatce oryginalej
    *
    * @param position Pozycja wokol ktorej generujemy kwadrat
    * @param sparsity Wspolczynnik agregacji s
    * @return Lista pozycji wokol punktu w siatce oryginalnej
    */
  def getSparseGridForPosition(position: GridPosition, sparsity: Int): Seq[GridPosition] = {
    val xRange = (-(sparsity - 1) / 2) to sparsity / 2
    val yRange = (-(sparsity - 1) / 2) to sparsity / 2
    (xRange cross yRange).map {
      case (dx, dy) => GridPosition(position.x + dx, position.y + dy)
    }.toSeq
  }

  /**
    * Zamienia sciezke w siatce zagregowanej na sciezke w siatce oryginalnej. Zamienia punkty z sciezki na punkty
    * w siatce oryginalnej oraz dodaje brakujace punkty pomiedzy nimi za pomoca interpolacji
    *
    * @param path      Sciezka w siatce zagregowanej
    * @param sparsity  Wspolczynnik agregacji s
    * @param timeSteps Dlugosc gry (niezagregowanej)
    * @return Podana sciezka w siatce oryginalnej
    */
  def sparsePathToReal(path: (Double, Seq[GridPosition]), sparsity: Int, timeSteps: Int): (Double, Seq[GridPosition]) = {
    var time = 0
    (path._1, path._2.sliding(2).flatMap(
      p => {
        val i = p(0)
        val j = p(1)
        val realTime = time * sparsity
        val realEndTime = math.min(timeSteps, realTime + sparsity)
        val realI = sparseToPosition(i, sparsity)
        val realJ = sparseToPosition(j, sparsity)
        val interp: Int => GridPosition = interpolation2d(realI, realJ, realTime, realTime + sparsity)
        val paths = ((realTime + (if (time == 0) 0 else 1)) to realEndTime).map(t => interp(t))
        time += 1
        paths
      }).toSeq)
  }
}

