package ocelot.desktop.node.nodes

import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.inventory.Item
import ocelot.desktop.inventory.item.{DiskDriveMountableItem, SelfDestructingCardItem, ServerItem}
import ocelot.desktop.inventory.traits.RackMountableItem
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size, TexelCount}
import ocelot.desktop.node.{ComputerAwareNode, NodePort, WindowedNode}
import ocelot.desktop.ui.event.{BrainEvent, ClickEvent}
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.util.DrawUtils
import ocelot.desktop.windows.RackWindow
import totoro.ocelot.brain.entity.traits.{ComponentInventory, Environment, Inventory, RackMountable}
import totoro.ocelot.brain.entity.{Rack, Server}
import totoro.ocelot.brain.event.NetworkActivityEvent
import totoro.ocelot.brain.network
import totoro.ocelot.brain.util.Direction

class RackNode(val rack: Rack) extends ComputerAwareNode(rack) with WindowedNode[RackWindow] {
  override val icon: IconSource = IconSource.Nodes.Rack.Empty
  override def exposeAddress = false

  override def ports: Array[NodePort] = Array(
    NodePort(Some(Direction.Bottom)),
    NodePort(Some(Direction.Top)),
    NodePort(Some(Direction.Back)),
    NodePort(Some(Direction.Right)),
    NodePort(Some(Direction.Left)),
  )

  override def getNodeByPort(port: NodePort): network.Node = rack.sidedNode(port.direction.get)

  override def rotatable: Boolean = true

  override def shouldReceiveEventsFor(address: String): Boolean = {
    super.shouldReceiveEventsFor(address) ||
    rack.inventory.entities.exists {
      case mountable: RackMountable if mountable.node.address == address => true
      case mountable: RackMountable with ComponentInventory => mountable.inventory.entities.exists {
          case environment: Environment => environment.node.address == address
          case _ => false
        }
      case _ => false
    }
  }

  override protected def selfDestructingCards: IterableOnce[SelfDestructingCardItem] = {
    inventoryIterator.flatMap(_.get).collect({
      case item: ServerItem =>
        item.inventoryIterator.flatMap(_.get).collect {
          case card: SelfDestructingCardItem => card
        }
    }).flatten
  }

  override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
    RackNode.addContextMenuEntriesOfMountable(menu, getMountableByClick(event))

    super.setupContextMenu(menu, event)
  }

  override def update(): Unit = {
    super.update()

    for (i <- 0 until 4) {
      Slot(i).get match {
        case Some(serverItem: ServerItem) =>
          serverItem.updateRunningSound()

        case _ =>
      }
    }
  }

  override def drawOverlay(g: Graphics): Unit = {
    val x = position.x + HighlightThickness
    val y = position.y + HighlightThickness

    for (i <- 0 until 4) {
      Slot(i).get match {
        case Some(serverItem: ServerItem) =>
          val serverIconSource = IconSource.Nodes.Rack.Server(i)

          // Background
          g.sprite(serverIconSource.Default, x, y, NoHighlightSize, NoHighlightSize)

          DrawUtils.drawPowerOrError(g, x, y, serverItem.server.machine, serverIconSource)
          DrawUtils.drawFilesystemActivity(g, x, y, serverItem.server.machine, serverIconSource)
          DrawUtils.drawNetworkActivity(g, x, y, serverItem.server, serverIconSource)

        case Some(diskDriveMountableItem: DiskDriveMountableItem) =>
          val driveIconSource = IconSource.Nodes.Rack.Drive(i)

          // Background
          g.sprite(driveIconSource.Default, x, y, NoHighlightSize, NoHighlightSize)

          diskDriveMountableItem.drawActivityAndFloppy(
            g,
            Rect2D(
              x,
              y,
              NoHighlightSize,
              NoHighlightSize,
            ),
            driveIconSource,
          )

        case _ =>
      }
    }

    super.drawOverlay(g)
  }

  override def dispose(): Unit = {
    for (i <- 0 until 4) {
      Slot(i).get match {
        case Some(serverItem: ServerItem) => serverItem.onRemoved()
        case _ =>
      }
    }

    super.dispose()
  }

  // -------------------------------- Inventory --------------------------------

  override type I = Item with RackMountableItem
  override def brainInventory: Inventory = rack.inventory.owner

  // -------------------------------- Window --------------------------------

  override def createWindow(): RackWindow = new RackWindow(this)

  // ---------------------------- ShiftClickNode ----------------------------

  private def getMountableByClick(event: ClickEvent): Option[RackMountableItem] = {
    val horizontalMargin = HighlightThickness + (1 / TexelCount) * Size
    val verticalMargin = HighlightThickness + (2 / TexelCount) * Size

    val localPosition = Vector2D(
      event.mousePos.x - position.x - horizontalMargin,
      event.mousePos.y - position.y - verticalMargin,
    )

    val m = Size2D(
      this.width - horizontalMargin * 2,
      this.height - verticalMargin * 2,
    )

    // Checking if click was inside mountables area
    if (localPosition.x < 0 || localPosition.y < 0 || localPosition.x > m.width || localPosition.y > m.height)
      return None

    val mountableIndex = (localPosition.y / m.height * 4).toInt

    Slot(mountableIndex).get.collect {
      case item: RackMountableItem => item
    }
  }

  override protected def onShiftClick(event: ClickEvent): Unit = {
    getMountableByClick(event) match {
      case Some(serverItem: ServerItem) =>
        serverItem.toggleIsTurnedOn()

      case Some(diskDriveMountableItem: DiskDriveMountableItem) =>
        if (diskDriveMountableItem.isFloppyItemPresent)
          diskDriveMountableItem.eject()

      case _ =>
    }
  }

  override protected def hoveredShiftStatusBarText: String = "Turn server on/off"

  // ---------------------------- Event handlers ----------------------------

  eventHandlers += {
    case BrainEvent(event: NetworkActivityEvent) =>
      for (slot <- 0 until rack.getSizeInventory) {
        rack.getMountable(slot) match {
          case server: Server =>
            if (server.componentSlot(event.address) >= 0)
              server.resetLastNetworkAccess()
          case _ =>
        }
      }
  }
}

object RackNode {
  def addContextMenuEntriesOfMountable(menu: ContextMenu, item: Option[RackMountableItem]): Unit = {
    item match {
      case Some(serverItem: ServerItem) =>
        menu.addEntry(ContextMenuEntry("Set up", IconSource.Icons.Window) {
          serverItem.window.open()
        })

        if (serverItem.isInRack) {
          menu.addSeparator()

          serverItem.addPowerContextMenuEntries(menu)
        }

        serverItem.addTierContextMenuEntries(menu)

        menu.addSeparator()

      case Some(diskDriveMountableItem: DiskDriveMountableItem) =>
        if (diskDriveMountableItem.isFloppyItemPresent)
          diskDriveMountableItem.addEjectContextMenuEntry(menu)

        menu.addEntry(ContextMenuEntry(
          if (diskDriveMountableItem.isFloppyItemPresent)
            "Change floppy"
          else
            "Set floppy",
          IconSource.Icons.Save,
        ) {
          diskDriveMountableItem.window.open()
        })

        menu.addSeparator()

      case _ =>
    }
  }
}
