package ocelot.desktop.util

import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.item._
import ocelot.desktop.inventory.traits.EntityItem
import ocelot.desktop.inventory.{Item, SyncedInventory}
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuIcon, ContextMenuSubmenu}
import ocelot.desktop.ui.widget.slot._
import ocelot.desktop.ui.widget.window.Windowed
import ocelot.desktop.util.ComputerType.ComputerType
import ocelot.desktop.windows.ComputerWindow
import totoro.ocelot.brain.entity.traits.{Computer, TieredPersistable}
import totoro.ocelot.brain.loot.Loot
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier

import scala.math.Ordering.Implicits.infixOrderingOps
import scala.reflect.ClassTag

trait ComputerAware
  extends Logging
    with SyncedInventory
    with DefaultSlotItemsFillable
    with Windowed[ComputerWindow]
    with Disposable {

  override type I = Item with EntityItem

  def computer: Computer with TieredPersistable

  def computerType: ComputerType

  def turnOn(): Unit = {
    computer.machine.start()
  }

  def turnOff(): Unit = {
    computer.machine.stop()
  }

  def toggleIsTurnedOn(): Unit = {
    if (computer.machine.isRunning) {
      turnOff()
    } else {
      turnOn()
    }
  }

  def addPowerContextMenuEntries(menu: ContextMenu): Unit = {
    if (computer.machine.isRunning) {
      menu.addEntry(ContextMenuEntry("Turn off", IconSource.Icons.Power) {
        turnOff()
      })

      menu.addEntry(ContextMenuEntry("Reboot", IconSource.Icons.Restart) {
        turnOff()
        turnOn()
      })
    } else {
      menu.addEntry(ContextMenuEntry("Turn on", IconSource.Icons.Power) {
        turnOn()
      })
    }
  }

  def addTierContextMenuEntries(menu: ContextMenu, maxNonCreativeTier: Tier = Tier.Three): Unit = {
    menu.addEntry(
      new ContextMenuSubmenu("Change tier", Some(ContextMenuIcon(IconSource.Icons.Tiers))) {
        def addTierEntry(tier: Tier): Unit = {
          val entry = ContextMenuEntry(s"${tier.label}${if (tier == computer.tier) " (current)" else ""}") {
            changeTier(tier)
          }
          entry.setEnabled(tier != computer.tier)
          addEntry(entry)
        }

        for (tier <- Tier.One to maxNonCreativeTier)
          addTierEntry(tier)

        addTierEntry(Tier.Creative)
      }
    )
  }

  var eepromSlot: EepromSlotWidget = _
  var cpuSlot: ComputerCpuSlotWidget = _
  var componentBusSlots: Array[ComponentBusSlotWidget] = Array.empty
  var memorySlots: Array[MemorySlotWidget] = Array.empty
  var cardSlots: Array[CardSlotWidget] = Array.empty
  var diskSlots: Array[HddSlotWidget] = Array.empty
  var floppySlot: Option[FloppySlotWidget] = None

  private var nextSlotIndex = 0

  protected def slots: IterableOnce[SlotWidget[I]] = (
    // slots may be null during initialization
    Option(eepromSlot).iterator ++
      Option(cpuSlot).iterator ++
      componentBusSlots.iterator ++
      memorySlots.iterator ++
      cardSlots.iterator ++
      diskSlots.iterator ++
      floppySlot.iterator
  ).map(_.asInstanceOf[SlotWidget[I]])

  private def addSlot(): Slot = {
    val result = Slot(nextSlotIndex)
    nextSlotIndex += 1
    result
  }

  private def insertItems(items: IterableOnce[I]): Unit = {
    def findBestSlot[A <: I](item: A, candidates: IterableOnce[SlotWidget[A]]): Option[SlotWidget[A]] = {
      candidates.iterator
        .filter(_.item.isEmpty)
        .filter(_.isItemAccepted(item.factory))
        .minByOption(_.slotTier)
    }

    for (item <- items) {
      findBestSlot(item, slots) match{
        case Some(newSlot) => newSlot.item = item
        case None => item.dispose()
      }
    }
  }

  protected def addSlotWidget[T <: SlotWidget[_]](factory: Slot => T): T = {
    val slot = addSlot()
    val widget = factory(slot)

    widget
  }

  protected def addSlotWidgets[T <: SlotWidget[_]: ClassTag](factories: (Slot => T)*): Array[T] = {
    val array = Array.newBuilder[T]

    for (factory <- factories)
      array += addSlotWidget(factory)

    array.result()
  }

  protected def addSlotsBasedOnTier(): Unit

  private final def createSlots(): Unit = {
    for (slot <- slots)
      slot.dispose()

    nextSlotIndex = 0

    addSlotsBasedOnTier()
  }

  // ---------------------------- DefaultSlotItemsFillable ----------------------------

  override def fillSlotsWithDefaultItems(): Unit = {
    cardSlots(0).item = new GraphicsCardItem.Factory(computer.tier min Tier.Three).build()

    diskSlots(0).item = new HddItem.Factory(true, computer.tier min Tier.Three).build()

    cpuSlot.item = new CpuItem.Factory(computer.tier min Tier.Three).build()

    for (componentBusSlot <- componentBusSlots)
      componentBusSlot.item = new ComponentBusItem.Factory(computer.tier min Tier.Three).build()

    for (memorySlot <- memorySlots)
      memorySlot.item = new MemoryItem.Factory((computer.tier min Tier.Three).toExtended(true)).build()

    for (floppySlot <- floppySlot)
      floppySlot.item = new FloppyItem.Factory.Loot(Loot.OpenOsFloppy).build()

    eepromSlot.item = new EepromItem.Factory.Loot(Loot.LuaBiosEEPROM).build()
  }

  private def changeTier(tier: Tier): Unit = {
    computer.tier = tier

    val items = slots.iterator.flatMap(_.item).toArray
    clearInventory()
    createSlots()
    insertItems(items)

    if (windowCreated)
      window.reloadWindow()
  }

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

  createSlots()
}
