package li.cil.oc.util

import scala.collection.mutable

class RTree[Data](private val M: Int)(implicit val coordinate: Data => (Double, Double, Double)) {
  if (M < 2) throw new IllegalArgumentException("maxEntries must be larger or equal to 2.")

  // Used for quick checks whether values are in the tree, e.g. for updates.
  private val entries = mutable.Map.empty[Data, Leaf]

  private val m = math.max(M / 2, 1)

  private var root = new NonLeaf()

  def apply(value: Data): Option[(Double, Double, Double)] =
    entries.get(value).fold(None: Option[(Double, Double, Double)])(position => Some(position.bounds.min.asTuple))

  // Allows debug rendering of the tree.
  def allBounds = root.allBounds(0)

  def add(value: Data): Boolean = {
    val replaced = remove(value)
    val entry = new Leaf(value, new Point(value))
    entries += value -> entry
    root.add(entry) match {
      case newNode if newNode != root => root = new NonLeaf(newNode, root)
      case _ =>
    }
    !replaced
  }

  def remove(value: Data): Boolean =
    entries.remove(value) match {
      case Some(node) =>
        val change = root.remove(node)
        assert(change == Some(node) || change == Some(root))
        root.children.headOption match {
          case Some(nonLeaf: NonLeaf) if root.children.size == 1 =>
            root = nonLeaf
          case _ =>
            root.bounds = Rectangle.around(root.children)
        }
        true
      case _ => false
    }

  def query(from: (Double, Double, Double), to: (Double, Double, Double)) =
    root.query(new Rectangle(new Point(from), new Point(to)))

  private abstract class Node {
    def bounds: Rectangle

    def allBounds(level: Int) = Iterable((bounds.asTuple, level))

    def isLeaf = true

    def add(value: Node): Node

    def remove(value: Node): Option[Node]

    def query(query: Rectangle): Iterable[Data]
  }

  private class NonLeaf extends Node {
    def this(nodes: Node*) = {
      this()
      for (child <- nodes) {
        children += child
        bounds = bounds.including(child.bounds)
      }
    }

    val children = mutable.Set.empty[Node]

    var bounds = new Rectangle(Point.PositiveInfinity, Point.NegativeInfinity)

    override def allBounds(level: Int) = super.allBounds(level) ++ children.map(_.allBounds(level + 1)).flatten

    override def isLeaf = children.headOption.exists(_.isInstanceOf[Leaf])

    def add(value: Node): Node = {
      assert(value != this)
      uncheckedAdd(value)
      if (children.size > M) {
        split()
      }
      else {
        bounds = bounds.including(value.bounds)
        this
      }
    }

    private def uncheckedAdd(value: Node) {
      var bestChild: Option[Node] = null
      var bestGrowth = Double.PositiveInfinity
      var bestVolume = Double.PositiveInfinity
      for (child <- children if !child.isLeaf || value.isInstanceOf[Leaf]) {
        val oldVolume = child.bounds.volume
        val volume = child.bounds.including(value.bounds).volume
        val growth = volume - oldVolume
        if (growth < bestGrowth || (growth == bestGrowth && volume < bestVolume)) {
          bestChild = Some(child)
          bestGrowth = growth
          bestVolume = volume
        }
      }
      bestChild match {
        case Some(child) =>
          children += child.add(value)
        case _ =>
          // Empty root or node while inserting children of removing child node.
          children += value
      }
    }

    def remove(value: Node): Option[Node] = {
      if (bounds.intersects(value.bounds)) for (child <- children) {
        child.remove(value) match {
          case Some(change) =>
            if (change == child) {
              // Underflow after removing node or child was the node to remove.
              children -= child
              child match {
                case node: NonLeaf =>
                  for (child <- node.children) {
                    uncheckedAdd(child)
                  }
                  if (children.size > M) {
                    // Escalate overflow.
                    return Some(split())
                  }
                case leaf: Leaf => assert(leaf == value)
              }
              if (children.size < m) {
                // Escalate underflow.
                return Some(this)
              }
              // Done handling tree adjustment, bubble result up.
              bounds = Rectangle.around(children)
              return Some(value)
            }
            else if (change == value) {
              // Removal, bubble result up.
              bounds = Rectangle.around(children)
              return Some(value)
            }
            else {
              // Overflow due to split after underflow.
              assert(change.isInstanceOf[NonLeaf])
              uncheckedAdd(change)
              if (children.size > M) {
                // Escalate overflow.
                return Some(split())
              }
              else {
                // Done handling tree adjustment, bubble result up.
                bounds = Rectangle.around(children)
                return Some(value)
              }
            }
          case _ =>
        }
      }
      None
    }

    def query(query: Rectangle) =
      if (query.intersects(bounds))
        children.foldRight(Iterable.empty[Data])((child, result) => result ++ child.query(query))
      else Iterable.empty[Data]

    private def split() = {
      val values = children.toArray
      var seed1: Option[Node] = None
      var seed2: Option[Node] = None
      var worst = Double.NegativeInfinity
      for (i <- 0 until values.length) {
        val si = values(i)
        for (j <- i + 1 until values.length) {
          val sj = values(j)
          val vol1 = si.bounds.volume
          val vol2 = sj.bounds.volume
          val vol = si.bounds.including(sj.bounds).volume
          val d = vol - vol1 - vol2
          if (d > worst) {
            seed1 = Some(si)
            seed2 = Some(sj)
            worst = d
          }
        }
      }
      (seed1, seed2) match {
        case (Some(s1), Some(s2)) =>
          val r1 = new SplitResult(mutable.Set(s1), s1.bounds)
          val r2 = new SplitResult(mutable.Set(s2), s2.bounds)

          val list = mutable.Set.empty ++ values
          list -= s1
          list -= s2
          while (!list.isEmpty) {
            if (m - r1.set.size >= list.size) {
              list.foreach(r1.add)
              list.clear()
            }
            else if (m - r2.set.size >= list.size) {
              list.foreach(r2.add)
              list.clear()
            }
            else {
              var bestValue: Option[Node] = None
              var r = r1
              var best = Double.NegativeInfinity
              for (value <- list) {
                val newVol1 = r1.volumeIncluding(value)
                val newVol2 = r2.volumeIncluding(value)
                val growth1 = newVol1 - r1.volume
                val growth2 = newVol2 - r2.volume
                val d = math.abs(growth2 - growth1)
                if (d > best) {
                  bestValue = Some(value)
                  r = if (growth1 < growth2 || (growth1 == growth2 && newVol1 < newVol2)) r1 else r2
                  best = d
                }
              }
              bestValue match {
                case Some(value) =>
                  list -= value
                  r.add(value)
                case _ => throw new AssertionError()
              }
            }
          }

          children.clear()
          children ++= r1.set
          bounds = r1.bounds

          val LL = new NonLeaf()
          LL.children ++= r2.set
          LL.bounds = r2.bounds
          LL
        case _ => throw new AssertionError()
      }
    }
  }

  private class Leaf(val data: Data, point: Point) extends Node {
    val bounds = new Rectangle(point, point)

    def entries = Iterable(this)

    def add(value: Node) = value

    def remove(value: Node) =
      if (value == this) Some(this)
      else None

    def query(query: Rectangle) =
      if (query.intersects(bounds)) Iterable(data)
      else Iterable.empty
  }

  private class Point(val x: Double, val y: Double, val z: Double) {
    def this(p: (Double, Double, Double)) = this(p._1, p._2, p._3)

    def min(other: Point) = new Point(math.min(x, other.x), math.min(y, other.y), math.min(z, other.z))

    def max(other: Point) = new Point(math.max(x, other.x), math.max(y, other.y), math.max(z, other.z))

    def asTuple = (x, y, z)
  }

  private object Point {
    val NegativeInfinity = new Point(Double.NegativeInfinity, Double.NegativeInfinity, Double.NegativeInfinity)
    val PositiveInfinity = new Point(Double.PositiveInfinity, Double.PositiveInfinity, Double.PositiveInfinity)
  }

  private class Rectangle(val min: Point, val max: Point) {
    def including(value: Rectangle) = new Rectangle(value.min min min, value.max max max)

    def intersects(value: Rectangle) =
      value.min.x <= max.x && value.min.y <= max.y && value.min.z <= max.z &&
        value.max.x >= min.x && value.max.y >= min.y && value.max.z >= min.z

    def volume = {
      val sx = max.x - min.x
      val sy = max.y - min.y
      val sz = max.z - min.z
      sx * sy * sz
    }

    def asTuple = ((min.x, min.y, min.z), (max.x, max.y, max.z))
  }

  private object Rectangle {
    def around(values: Iterable[Node]) = {
      var min = Point.PositiveInfinity
      var max = Point.NegativeInfinity
      for (value <- values) {
        min = value.bounds.min min min
        max = value.bounds.max max max
      }
      new Rectangle(min, max)
    }
  }

  private class SplitResult(val set: mutable.Set[Node], var bounds: Rectangle) {
    def add(value: Node) {
      set += value
      bounds = bounds.including(value.bounds)
    }

    def volume = bounds.volume

    def volumeIncluding(value: Node) = bounds.including(value.bounds).volume
  }

}
