package li.cil.oc.util

import java.util.logging.Level

import com.google.common.base.{Charsets, Strings}
import li.cil.oc.api.Persistable
import li.cil.oc.common.Tier
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.{Blocks, OpenComputers, Settings, api, server}
import net.minecraft.item.{Item, ItemMap, ItemStack}
import net.minecraft.nbt.{NBTBase, NBTTagCompound}
import net.minecraft.world.World

import scala.io.Source

object ItemUtils {
  def caseTier(stack: ItemStack) = {
    val descriptor = api.Items.get(stack)
    if (descriptor == api.Items.get("case1")) Tier.One
    else if (descriptor == api.Items.get("case2")) Tier.Two
    else if (descriptor == api.Items.get("case3")) Tier.Three
    else if (descriptor == api.Items.get("caseCreative")) Tier.Four
    else Tier.None
  }

  abstract class ItemData extends Persistable {
    def load(stack: ItemStack) {
      if (stack.hasTagCompound) {
        // Because ItemStack's load function doesn't copy the compound tag,
        // but keeps it as is, leading to oh so fun bugs!
        load(stack.getTagCompound.copy().asInstanceOf[NBTTagCompound])
      }
    }

    def save(stack: ItemStack) {
      if (!stack.hasTagCompound) {
        stack.setTagCompound(new NBTTagCompound("tag"))
      }
      save(stack.getTagCompound)
    }
  }

  class RobotData extends ItemData {
    def this(stack: ItemStack) {
      this()
      load(stack)
    }

    var name = ""

    // Overall energy including components.
    var totalEnergy = 0

    // Energy purely stored in robot component - this is what we have to restore manually.
    var robotEnergy = 0

    var tier = 0

    var components = Array.empty[ItemStack]

    var containers = Array.empty[ItemStack]

    override def load(nbt: NBTTagCompound) {
      if (nbt.hasKey("display") && nbt.getCompoundTag("display").hasKey("Name")) {
        name = nbt.getCompoundTag("display").getString("Name")
      }
      if (Strings.isNullOrEmpty(name)) {
        name = RobotData.randomName
      }
      totalEnergy = nbt.getInteger(Settings.namespace + "storedEnergy")
      robotEnergy = nbt.getInteger(Settings.namespace + "robotEnergy")
      if (nbt.hasKey(Settings.namespace + "components")) {
        tier = nbt.getInteger(Settings.namespace + "tier")
        components = nbt.getTagList(Settings.namespace + "components").map(ItemStack.loadItemStackFromNBT).toArray
        containers = nbt.getTagList(Settings.namespace + "containers").map(ItemStack.loadItemStackFromNBT).toArray
      }
      else {
        // Old robot, upgrade to new modular model.
        tier = 0
        val experienceUpgrade = api.Items.get("experienceUpgrade").createItemStack(1)
        server.driver.item.UpgradeExperience.dataTag(experienceUpgrade).setDouble(Settings.namespace + "xp", nbt.getDouble(Settings.namespace + "xp"))
        components = Array(
          api.Items.get("screen1").createItemStack(1),
          api.Items.get("keyboard").createItemStack(1),
          api.Items.get("inventoryUpgrade").createItemStack(1),
          experienceUpgrade,
          api.Items.get("openOS").createItemStack(1),
          api.Items.get("graphicsCard1").createItemStack(1),
          api.Items.get("cpu1").createItemStack(1),
          api.Items.get("ram2").createItemStack(1)
        )
        containers = Array(
          api.Items.get("cardContainer2").createItemStack(1),
          api.Items.get("diskDrive").createItemStack(1),
          api.Items.get("upgradeContainer3").createItemStack(1)
        )
        robotEnergy = totalEnergy
      }
    }

    override def save(nbt: NBTTagCompound) {
      if (name != null) {
        if (!nbt.hasKey("display")) {
          nbt.setTag("display", new NBTTagCompound())
        }
        nbt.getCompoundTag("display").setString("Name", name)
      }
      nbt.setInteger(Settings.namespace + "storedEnergy", totalEnergy)
      nbt.setInteger(Settings.namespace + "robotEnergy", robotEnergy)
      nbt.setInteger(Settings.namespace + "tier", tier)
      nbt.setNewTagList(Settings.namespace + "components", components.toIterable)
      nbt.setNewTagList(Settings.namespace + "containers", containers.toIterable)
    }

    def createItemStack() = {
      val stack = Blocks.robotProxy.createItemStack()
      save(stack)
      stack
    }

    def copyItemStack() = {
      val stack = createItemStack()
      // Forget all node addresses and so on. This is used when 'picking' a
      // robot in creative mode.
      val newInfo = new RobotData(stack)
      newInfo.components.foreach(cs => Option(api.Driver.driverFor(cs)) match {
        case Some(driver) if driver == server.driver.item.Screen =>
          val nbt = driver.dataTag(cs)
          nbt.getTags.toArray.foreach {
            case tag: NBTBase => nbt.removeTag(tag.getName)
            case _ =>
          }
        case _ =>
      })
      // Don't show energy info (because it's unreliable) but fill up the
      // internal buffer. This is for creative use only, anyway.
      newInfo.totalEnergy = 0
      newInfo.robotEnergy = 50000
      newInfo.save(stack)
      stack
    }
  }

  object RobotData {
    val names = try {
      Source.fromInputStream(getClass.getResourceAsStream(
        "/assets/" + Settings.resourceDomain + "/robot.names"))(Charsets.UTF_8).
        getLines().map(_.takeWhile(_ != '#').trim()).filter(_ != "").toArray
    }
    catch {
      case t: Throwable =>
        OpenComputers.log.log(Level.WARNING, "Failed loading robot name list.", t)
        Array.empty[String]
    }

    def randomName = if (names.length > 0) names((math.random * names.length).toInt) else "Robot"
  }

  class NavigationUpgradeData extends ItemData {
    def this(stack: ItemStack) {
      this()
      load(stack)
    }

    var map = new ItemStack(Item.map)

    def mapData(world: World) = try map.getItem.asInstanceOf[ItemMap].getMapData(map, world) catch {
      case _: Throwable => throw new Exception("invalid map")
    }

    override def load(stack: ItemStack) {
      if (stack.hasTagCompound) {
        load(stack.getTagCompound.getCompoundTag(Settings.namespace + "data"))
      }
    }

    override def save(stack: ItemStack) {
      if (!stack.hasTagCompound) {
        stack.setTagCompound(new NBTTagCompound("tag"))
      }
      save(stack.getCompoundTag(Settings.namespace + "data"))
    }

    override def load(nbt: NBTTagCompound) {
      if (nbt.hasKey(Settings.namespace + "map")) {
        map = ItemStack.loadItemStackFromNBT(nbt.getCompoundTag(Settings.namespace + "map"))
      }
    }

    override def save(nbt: NBTTagCompound) {
      if (map != null) {
        nbt.setNewCompoundTag(Settings.namespace + "map", map.writeToNBT)
      }
    }
  }

  class TabletData extends ItemData {
    def this(stack: ItemStack) {
      this()
      load(stack)
    }

    var items = Array.fill[Option[ItemStack]](32)(None)

    override def load(nbt: NBTTagCompound) {
      nbt.getTagList(Settings.namespace + "items").foreach[NBTTagCompound](slotNbt => {
        val slot = slotNbt.getByte("slot")
        if (slot >= 0 && slot < items.length) {
          items(slot) = Option(ItemStack.loadItemStackFromNBT(slotNbt.getCompoundTag("item")))
        }
      })
    }

    override def save(nbt: NBTTagCompound) {
      nbt.setNewTagList(Settings.namespace + "items",
        items.zipWithIndex collect {
          case (Some(stack), slot) => (stack, slot)
        } map {
          case (stack, slot) =>
            val slotNbt = new NBTTagCompound()
            slotNbt.setByte("slot", slot.toByte)
            slotNbt.setNewCompoundTag("item", stack.writeToNBT)
        })
    }
  }

}
