package ocelot.desktop.node.nodes

import ocelot.desktop.color.{Color, IntColor}
import ocelot.desktop.geometry.{Rect2D, Size2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size}
import ocelot.desktop.node.nodes.ScreenNode.{BorderSize, FontHeight, FontWidth}
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, WindowedNode}
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.widget.ScreenAspectRatioDialog
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.util.TierColor
import ocelot.desktop.windows.ScreenWindow
import ocelot.desktop.{OcelotDesktop, Settings}
import totoro.ocelot.brain.entity.{Keyboard, Screen}
import totoro.ocelot.brain.nbt.NBTTagCompound
import totoro.ocelot.brain.util.PackedColor

class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEntityNode with WindowedNode[ScreenWindow] {
  override def minimumSize: Size2D = Size2D(
    Size * screen.aspectRatio._1,
    Size * screen.aspectRatio._2
  )

  override def maximumSize: Size2D = minimumSize

  // -------------------------------

  private var keyboard: Option[Keyboard] = None

  override def load(nbt: NBTTagCompound): Unit = {
    super.load(nbt)

    if (nbt.hasKey("string")) {
      keyboard = OcelotDesktop.workspace.entityByAddress(nbt.getString("string")).map(_.asInstanceOf[Keyboard])
    }
  }

  override def save(nbt: NBTTagCompound): Unit = {
    super.save(nbt)

    if (keyboard.isDefined) {
      nbt.setString("keyboard", keyboard.get.node.address)
    }
  }

  override def dispose(): Unit = {
    super.dispose()
    if (keyboard.isDefined) {
      OcelotDesktop.workspace.remove(keyboard.get)
    }
  }

  def attachKeyboard(): Unit = {
    val kbd = new Keyboard
    OcelotDesktop.workspace.add(kbd)
    screen.connect(kbd)
    keyboard = Some(kbd)
  }

  override def icon: String = "nodes/screen/Standalone"

  override def iconColor: Color = TierColor.get(screen.tier)

  override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
    if (screen.getPowerState)
      menu.addEntry(ContextMenuEntry("Turn off", IconSource.Power) {
        screen.setPowerState(false)
      })
    else
      menu.addEntry(ContextMenuEntry("Turn on", IconSource.Power) {
        screen.setPowerState(true)
      })

    menu.addEntry(ContextMenuEntry("Set aspect ratio", IconSource.AspectRatio) {
      new ScreenAspectRatioDialog(this).show()
    })

    menu.addSeparator()

    super.setupContextMenu(menu, event)
  }

  def drawScreenData(g: Graphics, startX: Float, startY: Float, scaleX: Float, scaleY: Float): Unit = {
    g.save()
    g.scale(scaleX, scaleY)

    val startXScaled = startX / scaleX
    val startYScaled = startY / scaleY
    var color: Short = 0

    for (y <- 0 until screen.getHeight) {
      for (x <- 0 until screen.getWidth) {
        if (x == 0 || g.font.charWidth(screen.data.buffer(y)(x - 1)) != 16) {
          color = screen.data.color(y)(x)

          g.background = IntColor(PackedColor.unpackBackground(color, screen.data.format))
          g.foreground = IntColor(PackedColor.unpackForeground(color, screen.data.format))

          g.char(
            startXScaled + x * FontWidth,
            startYScaled + y * FontHeight,
            screen.data.buffer(y)(x)
          )
        }
      }
    }

    g.restore()
  }

  override def draw(g: Graphics): Unit = {
    drawHighlight(g)

    // Drawing multiblock screen sprite
    def drawScreenPart(name: String, x: Float, y: Float, width: Float, height: Float): Unit = {
      g.sprite(
        name,
        x,
        y,
        width,
        height,
        iconColor
      )
    }

    val aspectRatioWidth = screen.aspectRatio._1.toInt
    val aspectRatioHeight = screen.aspectRatio._2.toInt

    // 1 x n
    if (aspectRatioWidth == 1) {
      // 1 x 1
      if (aspectRatioHeight == 1) {
        drawScreenPart(
          "nodes/screen/Standalone",
          position.x + HighlightThickness,
          position.y + HighlightThickness,
          NoHighlightSize,
          NoHighlightSize,
        )
      }
      // 1 x n
      else {
        // Top
        drawScreenPart(
          "nodes/screen/ColumnTop",
          position.x + HighlightThickness,
          position.y + HighlightThickness,
          NoHighlightSize,
          Size - HighlightThickness,
        )

        // Middle
        for (y <- 1 until aspectRatioHeight - 1) {
          drawScreenPart(
            "nodes/screen/ColumnMiddle",
            position.x + HighlightThickness,
            position.y + y * Size,
            NoHighlightSize,
            Size
          )
        }

        // Bottom
        drawScreenPart(
          "nodes/screen/ColumnBottom",
          position.x + HighlightThickness,
          position.y + (aspectRatioHeight - 1) * Size,
          NoHighlightSize,
          NoHighlightSize,
        )
      }
    }
    // n x n
    else {
      // n x 1
      if (aspectRatioHeight == 1) {
        // Left
        drawScreenPart(
          "nodes/screen/RowLeft",
          position.x + HighlightThickness,
          position.y + HighlightThickness,
          Size - HighlightThickness,
          NoHighlightSize,
        )

        // Middle
        for (x <- 1 until aspectRatioWidth - 1)
          drawScreenPart(
            "nodes/screen/RowMiddle",
            position.x + x * Size,
            position.y + HighlightThickness,
            Size,
            NoHighlightSize
          )

        // Right
        drawScreenPart(
          "nodes/screen/RowRight",
          position.x + (aspectRatioWidth - 1) * Size,
          position.y + HighlightThickness,
          Size - HighlightThickness,
          NoHighlightSize
        )
      }
      // n x n
      else {
        def drawLine(y: Float, height: Float, leftName: String, middleName: String, rightName: String): Unit = {
          // Left
          drawScreenPart(
            leftName,
            position.x + HighlightThickness,
            y,
            Size,
            height
          )

          // Middle
          for (x <- 1 until aspectRatioWidth - 1)
            drawScreenPart(
              middleName,
              position.x + x * Size,
              y,
              Size,
              height
            )

          // Right
          drawScreenPart(
            rightName,
            position.x + (aspectRatioWidth - 1) * Size - HighlightThickness,
            y,
            Size,
            height
          )
        }

        // Top
        drawLine(
          position.y + HighlightThickness,
          Size - HighlightThickness,
          "nodes/screen/TopLeft",
          "nodes/screen/TopMiddle",
          "nodes/screen/TopRight"
        )

        // Middle
        for (y <- 1 until aspectRatioHeight - 1)
          drawLine(
            position.y + (y * Size),
            Size,
            "nodes/screen/MiddleLeft",
            "nodes/screen/Middle",
            "nodes/screen/MiddleRight"
          )

        // Bottom
        drawLine(
          position.y + (aspectRatioHeight - 1) * Size,
          Size - HighlightThickness,
          "nodes/screen/BottomLeft",
          "nodes/screen/BottomMiddle",
          "nodes/screen/BottomRight"
        )
      }
    }

    // If screen is on
    if (screen.getPowerState) {
      // If realtime rendering of screen data is allowed
      if (Settings.get.renderScreenDataOnNodes) {
        val virtualScreenBounds = Rect2D(
          position.x + BorderSize,
          position.y + BorderSize,
          size.width - BorderSize * 2,
          size.height - BorderSize * 2
        )

        // Black background rect
        g.rect(
          virtualScreenBounds.x,
          virtualScreenBounds.y,
          virtualScreenBounds.w,
          virtualScreenBounds.h,
          Color.Black
        )

        // Calculating pixel data bounds, so that they fit perfectly into the virtual screen
        val pixelDataSize = Size2D(
          screen.getWidth * FontWidth,
          screen.getHeight * FontHeight
        )

        val scale = Math.min(
          virtualScreenBounds.w / pixelDataSize.width,
          virtualScreenBounds.h / pixelDataSize.height
        )

        // Drawing pixel data
        drawScreenData(
          g,
          virtualScreenBounds.x + virtualScreenBounds.w / 2 - (pixelDataSize.width * scale) / 2,
          virtualScreenBounds.y + virtualScreenBounds.h / 2 - (pixelDataSize.height * scale) / 2,
          scale,
          scale
        )
      }
      // Drawing simple overlay otherwise
      else {
        g.sprite(
          "nodes/screen/PowerOnOverlay",
          position.x + HighlightThickness,
          position.y + HighlightThickness,
          NoHighlightSize,
          NoHighlightSize
        )
      }
    }
  }

  override def createWindow(): ScreenWindow = new ScreenWindow(this)
}

object ScreenNode {
  val FontWidth = 8f
  val FontHeight = 16f
  val BorderSize = 8f
}
