package ocelot.desktop.inventory

import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.item._
import ocelot.desktop.util.Logging
import ocelot.desktop.util.ReflectionUtils.linearizationOrder
import totoro.ocelot.brain.entity.tape.Tape
import totoro.ocelot.brain.loot.Loot
import totoro.ocelot.brain.util.ExtendedTier.ExtendedTier
import totoro.ocelot.brain.util.Tier.Tier
import totoro.ocelot.brain.util.{ExtendedTier, Tier}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

object Items extends Logging {
  private val _groups = ArrayBuffer.empty[ItemGroup]
  private val _recoverers = mutable.Map.empty[Class[_], ItemRecoverer[_, _]]

  // this is just to force load the class during initialization
  def init(): Unit = {}

  logger.debug("Initialize item serialization...")

  registerTiered("CPU", Tier.One to Tier.Three)(new CpuItem.Factory(_))
  registerTiered("APU", Tier.Two to Tier.Creative)(tier => new ApuItem.Factory(tier.saturatingSub(1)))

  // Magical memory looks like Tier 4, but behaves like Tier 1
  // Anyway we don't want to see its icon as default one in context menu
  registerArbitrary(
    "Memory",
    IconSource.Items.Memory(ExtendedTier.ThreeHalf),
    (ExtendedTier.One to ExtendedTier.Creative).iterator
      .map {
        case ExtendedTier.Creative => new MagicalMemoryItem.Factory()
        case tier => new MemoryItem.Factory(tier)
      }
      .map(factory => (factory.name, factory)),
  )

  registerTiered("HDD", Tier.One to Tier.Three)(new HddItem.Factory(managed = true, _))

  registerArbitrary(
    "Floppy",
    FloppyItem.Factory.Empty.icon,
    Loot.Floppies.iterator
      .map(new FloppyItem.Factory.Loot(_))
      .map(factory => (factory.name, factory)) ++ Some(("Empty", FloppyItem.Factory.Empty)),
  )

  registerArbitrary(
    "EEPROM",
    EepromItem.Factory.Empty.icon,
    Loot.Eeproms.iterator
      .map(new EepromItem.Factory.Loot(_))
      .map(factory => (factory.name, factory)) ++ Some(("Empty", EepromItem.Factory.Empty)),
  )

  registerTiered("Graphics Card", Tier.One to Tier.Three)(new GraphicsCardItem.Factory(_))
  registerSingleton(NetworkCardItem.Factory)

  registerTiered("Wireless Net. Card", Tier.One to Tier.Two) {
    case Tier.One => WirelessNetworkCardItem.Tier1.Factory
    case Tier.Two => WirelessNetworkCardItem.Tier2.Factory
  }

  registerSingleton(LinkedCardItem.Factory)
  registerSingleton(InternetCardItem.Factory)

  registerTiered("Redstone Card", Tier.One to Tier.Two) {
    case Tier.One => RedstoneCardItem.Tier1.Factory
    case Tier.Two => RedstoneCardItem.Tier2.Factory
  }

  registerTiered("Data Card", Tier.One to Tier.Three) {
    case Tier.One => DataCardItem.Tier1.Factory
    case Tier.Two => DataCardItem.Tier2.Factory
    case Tier.Three => DataCardItem.Tier3.Factory
  }

  registerSingleton(SoundCardItem.Factory)
  registerSingleton(SelfDestructingCardItem.Factory)
  registerSingleton(OcelotCardItem.Factory)

  registerTiered("Server", Tier.One to Tier.Creative)(new ServerItem.Factory(_))
  registerTiered("Component bus", Tier.One to Tier.Creative)(new ComponentBusItem.Factory(_))
  registerSingleton(DiskDriveMountableItem.Factory)

  registerArbitrary(
    "Tape",
    new TapeItem.Factory(Tape.Kind.Iron).icon,
    Tape.Kind.values.iterator
      .map(new TapeItem.Factory(_))
      .map(factory => (f"${factory.name}%s (${Tape.lengthMinutes(factory.kind)}%.0f min)", factory)),
  )

  logger.debug("Item serialization initialization finished.")

  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  /** Registers a recoverer for [[ItemRecoverer.sourceClass]]. */
  private def registerRecoverer(recoverer: ItemRecoverer[_, _]): Unit = {
    if (!_recoverers.contains(recoverer.sourceClass)) {
      _recoverers(recoverer.sourceClass) = recoverer
      logger.debug(s"Registered a recoverer for ${recoverer.sourceClass.getName}")
    }
  }

  private def registerItemFactoryRecoverers(factory: ItemFactory): Unit = {
    for (recoverer <- factory.recoverers) {
      registerRecoverer(recoverer)
    }
  }

  def registerSingleton(factory: ItemFactory): Unit = {
    _groups += SingletonItemGroup(factory.name, factory)
    registerItemFactoryRecoverers(factory)
  }

  def registerTiered(name: String, tiers: IterableOnce[Tier])(factory: Tier => ItemFactory): Unit = {
    val group = TieredItemGroup(name, tiers.iterator.map(tier => (tier, factory(tier))).toSeq)
    _groups += group

    for ((_, factory) <- group.factories) {
      registerItemFactoryRecoverers(factory)
    }
  }

  def registerExtendedTiered(name: String, tiers: IterableOnce[ExtendedTier])(
    factory: ExtendedTier => ItemFactory
  ): Unit = {
    val group = ExtendedTieredItemGroup(name, tiers.iterator.map(tier => (tier, factory(tier))).toSeq)
    _groups += group

    for ((_, factory) <- group.factories) {
      registerItemFactoryRecoverers(factory)
    }
  }

  def registerArbitrary(name: String, icon: IconSource, factories: IterableOnce[(String, ItemFactory)]): Unit = {
    val group = ArbitraryItemGroup(name, icon, factories.iterator.toSeq)
    _groups += group

    for ((_, factory) <- group.factories) {
      registerItemFactoryRecoverers(factory)
    }
  }

  def groups: Iterable[ItemGroup] = _groups

  /** Attempts to recover an [[Item]] from `source`.
    *
    * Checks superclasses and traits while looking for a recoverer.
    */
  def recover[A](source: A): Option[Item] = {
    linearizationOrder(source.getClass.asInstanceOf[Class[_]])
      .flatMap(_recoverers.get)
      .map(_.asInstanceOf[ItemRecoverer[_ >: A, _ <: Item]].recover(source))
      .nextOption()
  }

  sealed trait ItemGroup {
    def name: String
  }

  case class SingletonItemGroup(name: String, factory: ItemFactory) extends ItemGroup

  case class TieredItemGroup(name: String, factories: Seq[(Tier, ItemFactory)]) extends ItemGroup

  case class ExtendedTieredItemGroup(name: String, factories: Seq[(ExtendedTier, ItemFactory)]) extends ItemGroup

  case class ArbitraryItemGroup(name: String, icon: IconSource, factories: Seq[(String, ItemFactory)]) extends ItemGroup
}
