package ocelot.desktop.util

import ocelot.desktop.ColorScheme
import ocelot.desktop.color.{Color, RGBAColor, RGBAColorNorm}
import ocelot.desktop.geometry.{Rect2D, Vector2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.graphics.IconSource.{DiskActivityIconSource, NetworkActivityIconSource, PowerIconSource}
import ocelot.desktop.node.Node.NoHighlightSize
import totoro.ocelot.brain.entity.machine.Machine
import totoro.ocelot.brain.entity.traits.{DiskActivityAware, NetworkActivityAware}

object DrawUtils {
  def screenBorder(
    g: Graphics,
    outer: Rect2D,
    inner: Rect2D,
    color: Color = RGBAColor(255, 255, 255),
  ): Unit = {
    val thicknessOuterTop = 12
    val thickness = 8
    val top = thickness + thicknessOuterTop
    val side = 2 * thickness
    val horizontal = 2 * side
    val vertical = top + side

    val rightBorderX = outer.max.x - side
    val bottomBorderY = outer.max.y - side

    // filler (only visible if the screen view is too small).
    g.rect(
      outer.x + thickness,
      outer.y + thicknessOuterTop,
      outer.w - 2 * thickness,
      outer.h - thickness - thicknessOuterTop,
      ColorScheme("WindowBackground").withAlpha(color.toRGBANorm.a),
    )

    // inner border: corners.
    g.sprite(IconSource.Screen.InnerCornerTL, inner.x - thickness, inner.y - thickness, thickness, thickness, color)
    g.sprite(IconSource.Screen.InnerCornerTR, inner.max.x, inner.y - thickness, thickness, thickness, color)
    g.sprite(IconSource.Screen.InnerCornerBL, inner.x - thickness, inner.max.y, thickness, thickness, color)
    g.sprite(IconSource.Screen.InnerCornerBR, inner.max.x, inner.max.y, thickness, thickness, color)

    // inner border: top and bottom.
    g.sprite(IconSource.Screen.InnerBorderT, inner.x, inner.y - thickness, inner.w, thickness, color)
    g.sprite(IconSource.Screen.InnerBorderB, inner.x, inner.max.y, inner.w, thickness, color)

    // inner border: left.
    g.save()
    g.translate(inner.x - thickness, inner.y)
    g.rotate(270.toRadians)
    g.sprite(IconSource.Screen.InnerBorderT, -inner.h, 0, inner.h, thickness, color)
    g.restore()

    // inner border: right.
    g.save()
    g.translate(inner.max.x, inner.y)
    g.rotate(270.toRadians)
    g.sprite(IconSource.Screen.InnerBorderB, -inner.h, 0, inner.h, thickness, color)
    g.restore()

    // outer border: corners.
    g.sprite(IconSource.Screen.OuterCornerTL, outer.x, outer.y, side, top, color)
    g.sprite(IconSource.Screen.OuterCornerTR, rightBorderX, outer.y, side, top, color)
    g.sprite(IconSource.Screen.OuterCornerBL, outer.x, bottomBorderY, side, side, color)
    g.sprite(IconSource.Screen.OuterCornerBR, rightBorderX, bottomBorderY, side, side, color)

    // outer border: top and bottom.
    g.sprite(IconSource.Screen.OuterBorderT, outer.x + side, outer.y, outer.w - horizontal, thicknessOuterTop, color)
    g.sprite(
      IconSource.Screen.InnerBorderT,
      outer.x + side,
      outer.max.y - thickness,
      outer.w - horizontal,
      thickness,
      color,
    )

    // outer border: left.
    g.save()
    g.translate(outer.x, outer.y + top)
    g.rotate(270.toRadians)
    g.sprite(IconSource.Screen.InnerBorderB, -outer.h + vertical, 0, outer.h - vertical, thickness, color)
    g.restore()

    // outer border: right.
    g.save()
    g.translate(outer.max.x - thickness, outer.y + top)
    g.rotate(270.toRadians)
    g.sprite(IconSource.Screen.InnerBorderT, -outer.h + vertical, 0, outer.h - vertical, thickness, color)
    g.restore()
  }

  def panel(g: Graphics, x: Float, y: Float, w: Float, h: Float): Unit = {
    g.sprite(IconSource.Panel.CornerTL, x, y, 4, 4)
    g.sprite(IconSource.Panel.CornerTR, x + w - 4, y, 4, 4)
    g.sprite(IconSource.Panel.CornerBL, x, y + h - 4, 4, 4)
    g.sprite(IconSource.Panel.CornerBR, x + w - 4, y + h - 4, 4, 4)

    g.sprite(IconSource.Panel.BorderT, x + 4, y, w - 8, 4)
    g.sprite(IconSource.Panel.BorderB, x + 4, y + h - 4, w - 8, 4)
    g.sprite(IconSource.Panel.BorderL, x, y + 4, 4, h - 8)
    g.sprite(IconSource.Panel.BorderR, x + w - 4, y + 4, 4, h - 8)

    g.sprite(IconSource.Panel.Fill, x + 4, y + 4, w - 8, h - 8)
  }

  def isValidPolyline(points: Array[Vector2D]): Boolean = {
    if (points.length < 3) return true
    var start = points(0)
    for ((end, i) <- points.iterator.zipWithIndex.slice(1, points.length - 1)) {
      val a = end - start
      val b = end - points(i + 1)
      if (a.angle(b).abs < math.Pi / 2f)
        return false
      start = end
    }
    true
  }

  def polyline(
    g: Graphics,
    points: Array[Vector2D],
    thickness: Float = 4,
    color: Color = RGBAColor(255, 255, 255),
  ): Unit = {
    if (points.length < 2) return
    if (!isValidPolyline(points)) return

    val alpha = color.toRGBANorm.a
    val col = color.toRGBANorm.copy(a = 1)

    if (alpha < 1f) g.beginGroupAlpha()

    var start = points(0)
    for ((end, i) <- points.iterator.zipWithIndex.drop(1)) {
      if (end != points.last) {
        val a = end - start
        val b = end - points(i + 1)
        val addition = thickness / (2f * math.tan(a.angle(b) / 2f).abs.toFloat)
        g.line(start, end + a.normalize * addition, thickness, col)
        start = end + b.normalize * addition
      } else {
        g.line(start, end, thickness, col)
      }
    }

    if (alpha < 1f) g.endGroupAlpha(alpha)
  }

  type BorderRenderer = (Graphics, Float, Float, Float, Float, Color) => Unit

  def windowWithShadow(
    g: Graphics,
    x: Float,
    y: Float,
    w: Float,
    h: Float,
    backgroundAlpha: Float,
    shadowAlpha: Float,
  ): Unit = {
    DrawUtils.shadow(g, x - 8, y - 8, w + 16, h + 20, shadowAlpha)
    DrawUtils.windowBorder(g, x, y, w, h, RGBAColorNorm(1, 1, 1, backgroundAlpha))
  }

  def ring(
    g: Graphics,
    x: Float,
    y: Float,
    w: Float,
    h: Float,
    thickness: Float = 4,
    color: Color = RGBAColor(255, 255, 255),
  ): Unit = {
    g.rect(x, y, thickness, h, color)
    g.rect(x + w - thickness, y, thickness, h, color)
    g.rect(x + thickness, y, w - thickness * 2f, thickness, color)
    g.rect(x + thickness, y + h - thickness, w - thickness * 2f, thickness, color)
  }

  def windowBorder(
    g: Graphics,
    x: Float,
    y: Float,
    w: Float,
    h: Float,
    color: Color = RGBAColor(255, 255, 255),
  ): Unit = {
    g.sprite(IconSource.Window.CornerTL, x, y, 8, 8, color)
    g.sprite(IconSource.Window.CornerTR, x + w - 8, y, 8, 8, color)
    g.sprite(IconSource.Window.CornerBL, x, y + h - 8, 8, 8, color)
    g.sprite(IconSource.Window.CornerBR, x + w - 8, y + h - 8, 8, 8, color)

    g.sprite(IconSource.Window.BorderLight, x + 8, y, w - 16, 8, color)
    g.sprite(IconSource.Window.BorderDark, x + 8, y + h - 8, w - 16, 8, color)

    g.save()
    g.translate(x, y + 8)
    g.rotate(270.toRadians)
    g.sprite(IconSource.Window.BorderLight, 0, 0, -h + 16, 8, color)
    g.sprite(IconSource.Window.BorderDark, 0, w - 8, -h + 16, 8, color)
    g.restore()

    g.rect(x + 8, y + 8, w - 16, h - 16, RGBAColor(198, 198, 198, color.toRGBA.a))
  }

  def shadow(g: Graphics, x: Float, y: Float, w: Float, h: Float, a: Float = 0.8f): Unit = {
    val col = RGBAColorNorm(1, 1, 1, a)

    rotSprite(g, IconSource.ShadowCorner, x, y, 24, 24, 180.toRadians, col)
    rotSprite(g, IconSource.ShadowCorner, x + w - 24, y, 24, 24, 270.toRadians, col)
    rotSprite(g, IconSource.ShadowCorner, x, y + h - 24, 24, 24, 90.toRadians, col)
    g.sprite(IconSource.ShadowCorner, x + w - 24, y + h - 24, 24, 24, col)

    g.sprite(IconSource.ShadowBorder, x + 24, y + 24, w - 48, -24, col)
    g.sprite(IconSource.ShadowBorder, x + 24, y + h - 24, w - 48, 24, col)

    g.save()
    g.translate(x + 24, y + 24)
    g.rotate(90.toRadians)
    g.sprite(IconSource.ShadowBorder, 0, 0, h - 48, 24, col)
    g.restore()

    g.save()
    g.translate(x + w - 24, y + h - 24)
    g.rotate(270.toRadians)
    g.sprite(IconSource.ShadowBorder, 0, 0, h - 48, 24, col)
    g.restore()

    g.rect(x + 24, y + 24, w - 48, h - 48, RGBAColorNorm(0, 0, 0, a))
  }

  private def rotSprite(
    g: Graphics,
    icon: IconSource,
    x: Float,
    y: Float,
    w: Float,
    h: Float,
    angle: Float,
    col: Color = RGBAColorNorm(1, 1, 1),
  ): Unit = {
    g.save()
    g.translate(x + w / 2f, y + h / 2f)
    g.rotate(angle)
    g.sprite(icon, -w / 2f, -h / 2f, w, h, col)
    g.restore()
  }

  def borderedText(g: Graphics, x: Float, y: Float, text: String, borderColor: Color = RGBAColor(0, 0, 0)): Unit = {
    val col = g.foreground
    g.foreground = borderColor
    g.text(x + 1, y, text)
    g.text(x - 1, y, text)
    g.text(x, y + 1, text)
    g.text(x, y - 1, text)
    g.text(x + 1, y + 1, text)
    g.text(x - 1, y + 1, text)
    g.text(x + 1, y - 1, text)
    g.text(x - 1, y - 1, text)
    g.foreground = col
    g.text(x, y, text)
  }

  def drawPowerOrError(
    g: Graphics,
    x: Float,
    y: Float,
    machine: Machine,
    iconSource: PowerIconSource,
  ): Unit = {
    def tryDraw(icon: IconSource, state: Boolean): Unit = {
      if (state)
        g.sprite(icon, x, y, NoHighlightSize, NoHighlightSize)
    }

    val hasErred = machine.lastError != null

    tryDraw(iconSource.On, machine.isRunning && !hasErred)
    tryDraw(iconSource.Error, !machine.isRunning && hasErred)
  }

  def drawFilesystemActivity(
    g: Graphics,
    x: Float,
    y: Float,
    iconSource: DiskActivityIconSource,
  ): Unit = {
    g.sprite(iconSource.DiskActivity, x, y, NoHighlightSize, NoHighlightSize)
  }

  def drawFilesystemActivity(
    g: Graphics,
    x: Float,
    y: Float,
    diskActivityAware: DiskActivityAware,
    iconSource: DiskActivityIconSource,
  ): Unit = {
    if (diskActivityAware.shouldVisualizeDiskActivity) {
      drawFilesystemActivity(
        g,
        x,
        y,
        iconSource,
      )
    }
  }

  def drawNetworkActivity(
    g: Graphics,
    x: Float,
    y: Float,
    networkActivityAware: NetworkActivityAware,
    iconSource: NetworkActivityIconSource,
  ): Unit = {
    if (networkActivityAware.shouldVisualizeNetworkActivity)
      g.sprite(iconSource.NetworkActivity, x, y, NoHighlightSize, NoHighlightSize)
  }

  def drawTextLines(g: Graphics, x: Float, y: Float, lines: Iterable[String]): Unit = {
    for ((line, idx) <- lines.zipWithIndex) {
      g.text(x, y + idx * g.font.fontSize, line)
    }
  }
}
