package pl.edu.pw.mini.sg.movingtargets.coverage

import com.vividsolutions.jts.algorithm.ConvexHull
import com.vividsolutions.jts.geom.{ Coordinate, GeometryFactory }
import org.log4s._
import pl.edu.pw.mini.sg.movingtargets.{ DefenderPosition, MovingTargetsGameConfig }
import scala.annotation.tailrec
import scala.collection.immutable.BitSet
import scala.collection.immutable.SortedSet
import scala.math.Ordering



final case class CoveragePositionsHelper(game: MovingTargetsGameConfig) {
  val logger = getLogger
  logger.debug("Position helper start")

  val allPossiblePositions: Vector[DefenderPosition] = {
    val geometryFactory = new GeometryFactory()

    val routesHull = new ConvexHull(
      (game.defenderStartDepots.map(game.depotPositionToGrid(_))++game.targets.flatMap(t =>
          List(game.depotPositionToGrid(t.depot1Id), game.depotPositionToGrid(t.depot2Id))
      )).distinct.map{case (x,y) => new Coordinate(x, y)}.toArray, geometryFactory
    )

    def withinConvexHull(pos: DefenderPosition) : Boolean =
      routesHull.getConvexHull.distance(geometryFactory.createPoint(new Coordinate(pos.x.toDouble, pos.y.toDouble))) <= game.defenderStepInTime + game.spaceDiscretization

    // TODO zrobić otoczkę wypukłą
    // Najlepiej zrobić taki układ: pokrycie wszystkich tras + odejście na tyle daleko, żeby ich nie pokrywać
    // + dodanie punktów tak, żeby uzyskana struktura była wypukła
    // Szkice:
    // na pałę policzyć odległości wszystkich punktów od wszystkich odcinków, dodać te, które
    // są odpowiednio blisko. Po dodaniu wszystkich dodać otoczkę w odległości +1
    // Pytanie jak potem łatwo zrobić wypukłą?
    // Albo nawet niełatwo, ale w miarę małym kosztem
    val (in, out) = {
      val x = Range.inclusive(game.leftGridLimit, game.rightGridLimit)
      val y = Range.inclusive(game.bottomGridLimit, game.topGridLimit)
      val m = x.view.flatMap(x => y.map(DefenderPosition.cached(x, _))).groupBy(withinConvexHull)
      (m.get(true).map(_.toVector).getOrElse(Vector()), m.get(false).map(_.toVector).getOrElse(Vector()))
    } 

    (out.filter{case DefenderPosition(ox, oy) => in.exists{case DefenderPosition(ix, iy) => {
      val dx: Int = Math.abs(ox-ix)
      val dy: Int = Math.abs(oy-iy)
      dx == 1 || dy == 1
    }}} ++ in).sorted(
      Ordering.by[DefenderPosition, (Int, Int)](dp => (dp.x, dp.y))
    )
  }

  val allPossibleConfigurations: Vector[List[DefenderPosition]] = {
    logger.debug("apc start")
    val out = 1.to(game.defenderCount).view.map(i => allPossiblePositions).foldLeft(Vector(List[DefenderPosition]()))((vv, pos) => vv.flatMap(v => pos.view.map(p => p +: v)))
    logger.debug("apc finished")
    out
  }

  val allPossibleCoverages: Vector[SortedSet[Int]] = game.targets.view.map(_.id).foldLeft(Vector(SortedSet[Int]()))((ss, t) => ss.flatMap(s => Vector(s, s+t)))

  def deduplicateCovergate(coverage: SortedSet[Int]): SortedSet[Int] = allPossibleCoverages.find(_.equals(coverage)).getOrElse(coverage)

  logger.debug("position coverages start")
  val positionCoverages : Vector[Vector[SortedSet[Int]]] =
    (0 to game.roundLimit).view.map(i => allPossibleConfigurations.map(cf => deduplicateCovergate(game.getCoveredTargetIds(cf.toVector, i)))).toVector

  logger.debug("coverage positions start")
  val coveragePositions: Map[Int, Map[SortedSet[Int], Vector[List[DefenderPosition]]]] = 
    (1 to game.roundLimit).view.map(t => (t, allPossibleConfigurations.groupBy(cf => game.getCoveredTargetIds(cf.toVector, t)))).toMap

  @tailrec
  final def checkConfigurationDistance(c1: List[DefenderPosition], c2: List[DefenderPosition]) : Boolean =
    if(c1.isEmpty || c2.isEmpty) true
    else if (!game.isProperMove(c1.head, c2.head)) false
    else checkConfigurationDistance(c1.tail, c2.tail)

  logger.debug(s"possible successsions ${allPossibleConfigurations.length}")
  val possibleSuccessions : Vector[BitSet] = allPossibleConfigurations.map(cp => {
    BitSet(allPossibleConfigurations.map(cn => checkConfigurationDistance(cp, cn)).zipWithIndex.filter(_._1).map(_._2) :_*)
  })

  logger.debug("Position helper finish")
}
