package ocelot.desktop.geometry

object Rect2D {
  val Plane: Rect2D = Rect2D(0, 0, Float.PositiveInfinity, Float.PositiveInfinity)

  val Unit: Rect2D = Rect2D(0, 0, 1, 1)

  def apply(origin: Vector2D, size: Size2D): Rect2D = Rect2D(origin.x, origin.y, size.width, size.height)

  def apply(x: Double, y: Double, w: Double, h: Double): Rect2D = {
    Rect2D(x.toFloat, y.toFloat, w.toFloat, h.toFloat)
  }
}

//noinspection ScalaWeakerAccess,ScalaUnusedSymbol
case class Rect2D(x: Float, y: Float, w: Float, h: Float) {
  def contains(p: Vector2D): Boolean = p.x >= x && p.y >= y && p.x < x + w && p.y < y + h

  def contains(r: Rect2D): Boolean = r.x >= x && r.y >= y && r.x + r.w < x + w && r.y + r.h < y + h

  def origin: Vector2D = Vector2D(x, y)

  def min: Vector2D = origin

  def max: Vector2D = min + size.toVector

  def size: Size2D = Size2D(w, h)

  def extent: Vector2D = Vector2D(w / 2f, h / 2f)

  def collides(b: Rect2D): Boolean = {
    val a = this
    a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y
  }

  def intersect(other: Rect2D): Rect2D = {
    val newMin = min.max(other.min)
    val newMax = max.min(other.max)
    Rect2D(newMin, Size2D(newMax - newMin))
  }

  def minkowskiDifference(other: Rect2D): Rect2D = {
    val o = min - other.max
    val s = size + other.size
    Rect2D(o, s)
  }

  def closestPointOnBounds(point: Vector2D): Vector2D = {
    var minDist = math.abs(point.x - min.x)
    var boundsPoint = Vector2D(min.x, point.y)

    if (math.abs(max.x - point.x) < minDist) {
      minDist = Math.abs(max.x - point.x)
      boundsPoint = Vector2D(max.x, point.y)
    }

    if (math.abs(max.y - point.y) < minDist) {
      minDist = Math.abs(max.y - point.y)
      boundsPoint = Vector2D(point.x, max.y)
    }

    if (math.abs(min.y - point.y) < minDist) {
      minDist = Math.abs(min.y - point.y)
      boundsPoint = Vector2D(point.x, min.y)
    }

    boundsPoint
  }

  def fixNegative: Rect2D = {
    if (w < 0)
      Rect2D(x + w, y, -w, h).fixNegative
    else if (h < 0)
      Rect2D(x, y + h, w, -h).fixNegative
    else
      this
  }

  def inflated(dx: Float, dy: Float): Rect2D = Rect2D(x - dx, y - dy, w + dx * 2, h + dy * 2)

  def inflated(delta: Float): Rect2D = inflated(delta, delta)

  def center: Vector2D = {
    min + (size * 0.5f).toVector
  }

  def edgeCenters: Array[Vector2D] = Array(
    Vector2D(x + w / 2f, y),
    Vector2D(x + w, y + h / 2f),
    Vector2D(x + w / 2f, y + h),
    Vector2D(x, y + h / 2f),
  )

  def distanceTo(that: Rect2D): Float = {
    ((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).length
  }

  def manhattanDistanceTo(that: Rect2D): Float = {
    ((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).manhattanLength
  }

  def mapX(f: Float => Float): Rect2D = copy(x = f(x))
  def mapY(f: Float => Float): Rect2D = copy(y = f(y))
  def mapW(f: Float => Float): Rect2D = copy(w = f(w))
  def mapH(f: Float => Float): Rect2D = copy(h = f(h))
}
