package ocelot.desktop.windows

import ocelot.desktop.audio.{ClickSoundSourceFactory, SoundSource}
import ocelot.desktop.geometry.{Padding2D, Size2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.inventory.item.ServerItem
import ocelot.desktop.ui.layout.{Layout, LinearLayout}
import ocelot.desktop.ui.widget._
import ocelot.desktop.ui.widget.traits.HoverHighlight
import ocelot.desktop.ui.widget.window.{BasicWindow, TitleBar}
import ocelot.desktop.util.animation.UnitAnimation
import ocelot.desktop.util.{ComputerAware, ComputerType, DrawUtils, Orientation}
import ocelot.desktop.{ColorScheme, OcelotDesktop}
import totoro.ocelot.brain.nbt.NBTTagCompound

import scala.collection.immutable.ArraySeq

class ComputerWindow(computerAware: ComputerAware) extends BasicWindow {
  private def isServerMachineType: Boolean = computerAware.computerType == ComputerType.Server

  private var bottomDrawer: Widget = _

  private val bottomDrawerAnimation = UnitAnimation.easeInOutQuad(0.2f)
  bottomDrawerAnimation.goDown()

  private val drawerButton = new IconButton(
    IconSource.Buttons.BottomDrawerOpen,
    IconSource.Buttons.BottomDrawerClose,
    mode = IconButton.Mode.Switch,
    darkenActiveColorFactor = 0.2f,
    tooltip = Some("Toggle computer usage histogram"),
  ) {
    override def onPressed(): Unit = bottomDrawerAnimation.goUp()
    override def onReleased(): Unit = bottomDrawerAnimation.goDown()
  }

  private def windowWidth(): Int = computerAware.computerType match {
    case ComputerType.Server => 318
    case ComputerType.Microcontroller => 220
    case _ => 267
  }

  private def makeChildren(): ArraySeq[Widget] = {
    var children = ArraySeq.empty[Widget]

    bottomDrawer = new Widget {
      override def shouldClip: Boolean = true

      override def minimumSize: Size2D = Size2D.Zero

      children :+= new PaddingBox(
        new Widget {
          children :+= new PaddingBox(
            new Widget {
              override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical)

              class PerTickHistogram extends Histogram with TickUpdatable {
                override def minimumSize: Size2D = Size2D(windowWidth(), 70)
                override def maximumSize: Size2D = minimumSize

                def historySize: Int = 200

                def appendHistoryValue(value: Float): Unit = {
                  if (history.length > historySize) {
                    history.remove(0)
                  }

                  history.append(value)
                }

                def reset(): Unit = {
                  value = "N/A"
                  history.clear()
                }

                override def tickUpdate(): Unit = {
                  super.tickUpdate()
                  if (!computerAware.computer.machine.isRunning) reset()
                }
              }

              children :+= new ComponentUsageBar {
                override def minimumSize: Size2D = Size2D(windowWidth(), 34)
                override def maximumSize: Size2D = minimumSize
                override def tickUpdate(): Unit = {
                  super.tickUpdate()
                  maxValue = computerAware.computer.machine.maxComponents
                  currentValue = computerAware.computer.machine.componentCount
                }
              }

              children :+= new PerTickHistogram {
                title = "Memory usage"

                override def tickUpdate(): Unit = {
                  super.tickUpdate()
                  val (free, total) = computerAware.computer.machine.latestMemoryUsage
                  val used = total - free

                  val ratio = if (total == 0) 0 else used.toFloat / total.toFloat

                  value = f"${used.toFloat / 1024f / 1024f}%.1fM"
                  appendHistoryValue(ratio)
                }
              }

              children :+= new PaddingBox(
                new PerTickHistogram {
                  title = "CPU usage"

                  override def tickUpdate(): Unit = {
                    super.tickUpdate()
                    val (start, _end) = computerAware.computer.machine.latestExecutionInfo
                    val end = if (start < _end) _end else System.nanoTime()
                    val cpuUsage = if (start == 0) 0
                    else ((end - start).toFloat / 1000000000f * OcelotDesktop.tpsCounter.fps).min(1f)
                    value = f"${cpuUsage * 100}%.0f%%"
                    appendHistoryValue(cpuUsage)
                  }
                },
                Padding2D(top = 8),
              )

              children :+= new PaddingBox(
                new PerTickHistogram {
                  title = "Call budget usage"

                  override def tickUpdate(): Unit = {
                    super.tickUpdate()
                    val (budget, maxBudget) = computerAware.computer.machine.latestCallBudget
                    value = f"${maxBudget - budget}%.1f"
                    appendHistoryValue((1.0 - budget / maxBudget).toFloat)
                  }
                },
                Padding2D(top = 8),
              )
            },
            Padding2D.equal(7),
          )

          override def draw(g: Graphics): Unit = {
            DrawUtils.panel(g, position.x, position.y, width, height)

            super.draw(g)
          }
        },
        Padding2D(10, 12, 0, 12),
      )

      override def draw(g: Graphics): Unit = {
        if (height < 1)
          return

        super.draw(g)

        g.rect(position.x + 6, position.y + 2, width - 12, 2, ColorScheme("BottomDrawerBorder"))
      }
    }

    children :+= new PaddingBox(
      new Widget {
        override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical)

        children :+= new PaddingBox(
          new Widget {
            override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical)

            // Title bar
            children :+= new PaddingBox(
              new TitleBar(ComputerWindow.this) {
                override def title: String = {
                  val name = computerAware.computerType match {
                    case ComputerType.Server => "Server"
                    case ComputerType.Microcontroller => "Microcontroller"
                    case _ => "Computer"
                  }

                  s"$name ${computerAware.computer.machine.node.address}"
                }

                override def titleMaxLength: Int = computerAware.computerType match {
                  case ComputerType.Server => 35
                  case ComputerType.Microcontroller => 24
                  case _ => 27
                }
              },
              Padding2D(bottom = 8),
            )

            children :+= new Widget {
              // Drawer button
              children :+= new PaddingBox(drawerButton, Padding2D(top = if (isServerMachineType) 134 else 120))

              // EEPROM & power button
              children :+= new PaddingBox(
                new Widget {
                  // Microcontrollers have different EEPROM location
                  if (computerAware.computerType != ComputerType.Microcontroller)
                    children :+= new PaddingBox(computerAware.eepromSlot, Padding2D(right = 10))

                  // Power button
                  children :+= new IconButton(
                    IconSource.Buttons.PowerOff,
                    IconSource.Buttons.PowerOn,
                    mode = IconButton.Mode.Switch,
                    sizeMultiplier = 2,
                    model = IconButton.ReadOnlyModel(computerAware.computer.machine.isRunning),
                  ) with HoverHighlight {
                    override def enabled: Boolean =
                      !isServerMachineType || computerAware.asInstanceOf[ServerItem].isInRack

                    override def draw(g: Graphics): Unit = {
                      if (enabled) {
                        super.draw(g)
                      }
                    }

                    override def onPressed(): Unit = computerAware.turnOn()

                    override def onReleased(): Unit = computerAware.turnOff()

                    override protected def clickSoundSource: ClickSoundSourceFactory = SoundSource.MinecraftClick
                  }
                },
                Padding2D(
                  top = if (isServerMachineType) 58 else 44,
                  left = 22,
                  right = if (isServerMachineType) 10 else 0,
                ),
              )

              // Motherboard
              children :+= {
                // Slots order
                val rows = computerAware.computerType match {
                  case ComputerType.Server => Array(
                      (8, computerAware.cardSlots),
                      (12, Array(computerAware.cpuSlot) ++ computerAware.componentBusSlots),
                      (12, computerAware.memorySlots),
                      (12, computerAware.diskSlots),
                    )
                  case ComputerType.Microcontroller => Array(
                      (19, computerAware.cardSlots),
                      (8, Array(computerAware.cpuSlot) ++ computerAware.memorySlots),
                      (8, Array(computerAware.eepromSlot)),
                    )
                  case _ => Array(
                      (19, computerAware.cardSlots),
                      (8, Array(computerAware.cpuSlot) ++ computerAware.memorySlots),
                      (8, computerAware.diskSlots ++ computerAware.floppySlot.toArray),
                    )
                }

                // Slot widgets
                new Widget {
                  for ((padding, row) <- rows) {
                    children :+= new PaddingBox(
                      new Widget {
                        override protected val layout: Layout =
                          new LinearLayout(this, orientation = Orientation.Vertical)

                        for (slot <- row)
                          children :+= slot
                      },
                      Padding2D(
                        left = padding.toFloat,
                        top = if (isServerMachineType) 4 else 8,
                      ),
                    )
                  }

                  override def minimumSize: Size2D = computerAware.computerType match {
                    case ComputerType.Server => Size2D(200, 156)
                    case _ => Size2D(158, 140)
                  }

                  override def draw(g: Graphics): Unit = {
                    // Background image
                    g.sprite(
                      if (isServerMachineType) {
                        IconSource.Window.Rack.Motherboard
                      } else {
                        IconSource.Window.Case.Motherboard
                      },
                      position.x,
                      position.y,
                      width,
                      height,
                    )

                    drawChildren(g)
                  }
                }
              }
            }
          },
          Padding2D(8, 12, 0, 12),
        )
        children :+= bottomDrawer
      },
      Padding2D(bottom = 10),
    )

    children
  }

  def reloadWindow(): Unit = {
    children = makeChildren()
  }

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

    bottomDrawerAnimation.update()
    height = minimumSize.height + (bottomDrawerAnimation.value * bottomDrawer.maximumSize.height).round
  }

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

    bottomDrawerAnimation.save(nbt, "drawerAnimation")
  }

  override def load(nbt: NBTTagCompound): Unit = {
    bottomDrawerAnimation.load(nbt, "drawerAnimation")
    drawerButton.model.pressed = bottomDrawerAnimation.isGoingUp

    super.load(nbt)
  }

  children = makeChildren()
}
