package li.cil.oc.common.template

import li.cil.oc.api.driver.{Inventory, Memory, Processor, UpgradeContainer}
import li.cil.oc.common.{Slot, Tier}
import li.cil.oc.{Localization, Settings, api}
import net.minecraft.inventory.IInventory
import net.minecraft.item.ItemStack
import net.minecraft.util.ChatMessageComponent

import scala.collection.mutable

abstract class Template {
  protected val suggestedComponents = Array(
    "Screen" -> hasComponent("screen1") _,
    "Keyboard" -> hasComponent("keyboard") _,
    "GraphicsCard" -> ((inventory: IInventory) => Array("graphicsCard1", "graphicsCard2", "graphicsCard3").exists(name => hasComponent(name)(inventory))),
    "Inventory" -> hasInventory _,
    "OS" -> hasFileSystem _)

  protected def validateComputer(inventory: IInventory): Array[AnyRef] = {
    val hasCase = caseTier(inventory) != Tier.None
    val hasCPU = this.hasCPU(inventory)
    val hasRAM = this.hasRAM(inventory)
    val complexity = this.complexity(inventory)
    val maxComplexity = this.maxComplexity(inventory)

    val valid = hasCase && hasCPU && hasRAM && complexity <= maxComplexity

    val progress =
      if (!hasCPU) Localization.Assembler.InsertCPU
      else if (!hasRAM) Localization.Assembler.InsertRAM
      else Localization.Assembler.Complexity(complexity, maxComplexity)

    val warnings = mutable.ArrayBuffer.empty[ChatMessageComponent]
    for ((name, check) <- suggestedComponents) {
      if (!check(inventory)) {
        warnings += Localization.Assembler.Warning(name)
      }
    }
    if (warnings.length > 0) {
      warnings.prepend(Localization.Assembler.Warnings)
    }

    Array(valid: java.lang.Boolean, progress, warnings.toArray)
  }

  protected def exists(inventory: IInventory, p: ItemStack => Boolean) = {
    (0 until inventory.getSizeInventory).exists(slot => Option(inventory.getStackInSlot(slot)) match {
      case Some(stack) => p(stack)
      case _ => false
    })
  }

  protected def hasCPU(inventory: IInventory) = exists(inventory, api.Driver.driverFor(_) match {
    case _: Processor => true
    case _ => false
  })

  protected def hasRAM(inventory: IInventory) = exists(inventory, api.Driver.driverFor(_) match {
    case _: Memory => true
    case _ => false
  })

  protected def hasComponent(name: String)(inventory: IInventory) = exists(inventory, stack => Option(api.Items.get(stack)) match {
    case Some(descriptor) => descriptor.name == name
    case _ => false
  })

  protected def hasInventory(inventory: IInventory) = exists(inventory, api.Driver.driverFor(_) match {
    case _: Inventory => true
    case _ => false
  })

  protected def hasFileSystem(inventory: IInventory) = exists(inventory, stack => Option(api.Driver.driverFor(stack)) match {
    case Some(driver) => Slot(driver, stack) == Slot.Floppy || Slot(driver, stack) == Slot.HDD
    case _ => false
  })

  protected def complexity(inventory: IInventory) = {
    var acc = 0
    for (slot <- 1 until inventory.getSizeInventory) {
      val stack = inventory.getStackInSlot(slot)
      acc += (Option(api.Driver.driverFor(stack)) match {
        case Some(driver: Processor) => 0 // CPUs are exempt, since they control the limit.
        case Some(driver: UpgradeContainer) => (1 + driver.tier(stack)) * 2
        case Some(driver) => 1 + driver.tier(stack)
        case _ => 0
      })
    }
    acc
  }

  protected def maxComplexity(inventory: IInventory) = {
    val caseTier = this.caseTier(inventory)
    val cpuTier = (0 until inventory.getSizeInventory).foldRight(0)((slot, acc) => {
      val stack = inventory.getStackInSlot(slot)
      acc + (api.Driver.driverFor(stack) match {
        case processor: Processor => processor.tier(stack)
        case _ => 0
      })
    })
    if (caseTier >= Tier.One && cpuTier >= Tier.One) {
      Settings.deviceComplexityByTier(caseTier) - (math.min(2, caseTier) - cpuTier) * 6
    }
    else 0
  }

  protected def caseTier(inventory: IInventory): Int
}
