package edx.electrical.multimeter

import java.util.{ArrayList, HashSet, List, Set}

import codechicken.lib.data.{MCDataInput, MCDataOutput}
import codechicken.lib.vec
import codechicken.lib.vec.Cuboid6
import codechicken.multipart.{IRedstonePart, TMultiPart, TileMultipart}
import cpw.mods.fml.relauncher.{Side, SideOnly}
import edx.core.Electrodynamics
import edx.core.interfaces.TNodeMechanical
import edx.core.prefab.part.ChickenBonesWrapper._
import edx.core.prefab.part.PartFace
import edx.electrical.ElectricalContent
import io.netty.buffer.ByteBuf
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.tileentity.TileEntity
import net.minecraft.util.{ChatComponentText, MovingObjectPosition}
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.fluids.{FluidTankInfo, IFluidHandler}
import resonant.api.tile.{INodeProvider, IRemovable}
import resonant.lib.mod.compat.energy.Compatibility
import resonant.lib.network.discriminator.PacketType
import resonant.lib.network.handle.IPacketReceiver
import resonant.lib.transform.vector.Vector3
import resonant.lib.utility.WrenchUtility

import scala.collection.convert.wrapAll._

/** Block that detects power.
  *
  * @author Calclavia
  */
class PartMultimeter extends PartFace with IRedstonePart with IPacketReceiver with IRemovable.ISneakWrenchable
{

  var playersUsing: Set[EntityPlayer] = new HashSet[EntityPlayer]
  /** Detection */
  var redstoneTriggerLimit: Double = .0
  var detectType: Byte = 0
  var graphType: Byte = 0
  var redstoneOn: Boolean = false
  var isPrimary: Boolean = false
  private var detectMode = DetectModes.NONE
  private var doDetect: Boolean = true
  private var grid: MultimeterGrid = null

  override def preRemove()
  {
    if (!world.isRemote)
      getGrid.remove(this)
  }

  def updateDesc()
  {
    writeDesc(getWriteStream)
  }

  override def activate(player: EntityPlayer, part: MovingObjectPosition, item: ItemStack): Boolean =
  {
    if (WrenchUtility.isUsableWrench(player, player.inventory.getCurrentItem, x, y, z))
    {
      if (!this.world.isRemote)
      {
        doDetect = !doDetect
        player.addChatMessage(new ChatComponentText("Multimeter detection set to: " + doDetect))
        WrenchUtility.damageWrench(player, player.inventory.getCurrentItem, x, y, z)
      }
      return true
    }
    player.openGui(Electrodynamics, placementSide.ordinal, world, x, y, z)
    return true
  }

  override def update()
  {
    super.update()

    if (!world.isRemote)
    {
      if (doDetect) updateDetections
      val detectedValue = getGrid.graphs(detectType).getDouble
      var outputRedstone = false

      detectMode match
      {
        case DetectModes.EQUAL =>
          outputRedstone = detectedValue == redstoneTriggerLimit
        case DetectModes.GREATER_THAN =>
          outputRedstone = detectedValue > redstoneTriggerLimit
        case DetectModes.GREATER_THAN_EQUAL =>
          outputRedstone = detectedValue >= redstoneTriggerLimit
        case DetectModes.LESS_THAN =>
          outputRedstone = detectedValue < redstoneTriggerLimit
        case DetectModes.LESS_THAN_EQUAL =>
          outputRedstone = detectedValue <= redstoneTriggerLimit
        case _ =>
      }

      getGrid.markUpdate
      if (ticks % 20 == 0)
      {
        if (outputRedstone != redstoneOn)
        {
          redstoneOn = outputRedstone
          tile.notifyPartChange(this)
        }
        updateGraph
      }
    }
    if (!world.isRemote)
    {
      for (player <- playersUsing)
      {
        updateGraph
      }
    }
  }

  def updateGraph()
  {
    sendPacket(2)
  }

  def updateDetections()
  {
    val receivingSide: ForgeDirection = getDirection.getOpposite
    val tileEntity: TileEntity = getDetectedTile

    if (tileEntity.isInstanceOf[INodeProvider])
    {
      val instance = ForgeDirection.values
        .map(dir => tileEntity.asInstanceOf[INodeProvider].getNode(classOf[TNodeMechanical], dir).asInstanceOf[TNodeMechanical])
        .headOption.orNull

      if (instance != null)
      {
        getGrid.torqueGraph.queue(instance.torque)
        getGrid.angularVelocityGraph.queue(instance.angularVelocity)
        getGrid.powerGraph.queue(instance.torque * instance.angularVelocity)
      }
    }
    if (tileEntity.isInstanceOf[IFluidHandler])
    {
      val fluidInfo: Array[FluidTankInfo] = tileEntity.asInstanceOf[IFluidHandler].getTankInfo(receivingSide)

      if (fluidInfo != null)
      {
        fluidInfo.filter(info => info != null && info.fluid != null).foreach(info => getGrid.fluidGraph.queue(info.fluid.amount))
      }
    }
    getGrid.energyGraph.queue(Compatibility.getEnergy(tileEntity, receivingSide))
    getGrid.energyCapacityGraph.queue(Compatibility.getMaxEnergy(tileEntity, receivingSide))
  }

  def getDetectedTile: TileEntity =
  {
    val direction: ForgeDirection = getDirection
    return world.getTileEntity(x + direction.offsetX, y + direction.offsetY, z + direction.offsetZ)
  }

  def getDirection: ForgeDirection =
  {
    return ForgeDirection.getOrientation(this.placementSide.ordinal)
  }

  override def write(packet: MCDataOutput, id: Int)
  {
    super.write(packet, id)

    id match
    {
      case 0 =>
      {
        packet.writeByte(placementSide.ordinal)
        packet.writeByte(facing)
        packet.writeByte(detectMode.id)
        packet.writeByte(detectType)
        packet.writeByte(graphType)
        packet.writeNBTTagCompound(getGrid.center.writeNBT(new NBTTagCompound))
        packet.writeNBTTagCompound(getGrid.size.writeNBT(new NBTTagCompound))
        packet.writeBoolean(getGrid.isEnabled)
      }
      case 2 =>
      {
        //Graph
        packet.writeByte(2)
        isPrimary = getGrid.isPrimary(this)
        packet.writeBoolean(isPrimary)
        if (isPrimary) packet.writeNBTTagCompound(getGrid.save)
      }
    }
  }

  override def read(packet: MCDataInput, packetID: Int)
  {
    packetID match
    {
      case 0 =>
      {
        placementSide = ForgeDirection.getOrientation(packet.readByte)
        facing = packet.readByte
        detectMode = DetectModes(packet.readByte).asInstanceOf[DetectModes.DetectMode]
        detectType = packet.readByte
        graphType = packet.readByte
        getGrid.center = new Vector3(packet.readNBTTagCompound)
        getGrid.size = new Vector3(packet.readNBTTagCompound)
        getGrid.isEnabled = packet.readBoolean
      }
      case 1 =>
      {
        redstoneTriggerLimit = packet.readLong
      }
      case 2 =>
      {
        isPrimary = packet.readBoolean
        if (isPrimary) getGrid.load(packet.readNBTTagCompound)
      }
    }
  }

  def getRemovedItems(entity: EntityPlayer): List[ItemStack] =
  {
    val list: List[ItemStack] = new ArrayList[ItemStack]
    list.add(new ItemStack(ElectricalContent.itemMultimeter))
    return list
  }

  def read(data: ByteBuf, player: EntityPlayer, `type`: PacketType)
  {
    detectMode = DetectModes(data.readByte).asInstanceOf[DetectModes.DetectMode]
    detectType = data.readByte
    graphType = data.readByte
    redstoneTriggerLimit = data.readDouble
  }

  def toggleGraphType
  {
    graphType = ((graphType + 1) % getGrid.graphs.size).asInstanceOf[Byte]
    updateServer
  }

  def toggleMode
  {
    detectMode = DetectModes((detectMode.id + 1) % DetectModes.values.size).asInstanceOf[DetectModes.DetectMode]
    updateServer
  }

  def toggleDetectionValue
  {
    detectType = ((detectType + 1) % getGrid.graphs.size).asInstanceOf[Byte]
    updateServer
  }

  def updateServer
  {
  }

  override def load(nbt: NBTTagCompound)
  {
    super.load(nbt)
    placementSide = ForgeDirection.getOrientation(nbt.getByte("side"))
    detectMode = DetectModes(nbt.getByte("detectMode")).asInstanceOf[DetectModes.DetectMode]
    detectType = nbt.getByte("detectionType")
    graphType = nbt.getByte("graphType")
    doDetect = nbt.getBoolean("doDetect")
    redstoneTriggerLimit = nbt.getDouble("triggerLimit")
  }

  override def save(nbt: NBTTagCompound)
  {
    super.save(nbt)
    nbt.setByte("side", placementSide.ordinal.asInstanceOf[Byte])
    nbt.setByte("detectMode", detectMode.id.asInstanceOf[Byte])
    nbt.setByte("detectionType", detectType)
    nbt.setByte("graphType", graphType)
    nbt.setBoolean("doDetect", doDetect)
    nbt.setDouble("triggerLimit", redstoneTriggerLimit)
  }

  def getMode = detectMode

  override def redstoneConductionMap: Int =
  {
    return 0x1F
  }

  override def solid(arg0: Int): Boolean =
  {
    return true
  }

  @SideOnly(Side.CLIENT)
  override def renderDynamic(pos: vec.Vector3, frame: Float, pass: Int)
  {
    if (pass == 0)
    {
      RenderMultimeter.render(this, pos.x, pos.y, pos.z)
    }
  }

  def canConnectRedstone(arg0: Int): Boolean =
  {
    return true
  }

  def strongPowerLevel(arg0: Int): Int =
  {
    return if (redstoneOn) 14 else 0
  }

  def weakPowerLevel(arg0: Int): Int =
  {
    return if (redstoneOn) 14 else 0
  }

  def canConnect(direction: ForgeDirection, obj: AnyRef): Boolean =
  {
    return obj.isInstanceOf[PartMultimeter]
  }

  def getConnections: Array[AnyRef] =
  {
    val connections: Array[AnyRef] = new Array[AnyRef](6)
    for (dir <- ForgeDirection.VALID_DIRECTIONS)
    {
      if (dir != getDirection && dir != getDirection.getOpposite)
      {
        val vector: Vector3 = getPosition.add(dir)
        if (hasMultimeter(vector.xi, vector.yi, vector.zi))
        {
          connections(dir.ordinal) = getMultimeter(vector.xi, vector.yi, vector.zi)
        }
      }
    }
    return connections
  }

  def hasMultimeter(x: Int, y: Int, z: Int): Boolean =
  {
    return getMultimeter(x, y, z) != null
  }

  /** Gets the multimeter on the same plane. */
  def getMultimeter(x: Int, y: Int, z: Int): PartMultimeter =
  {
    val tileEntity: TileEntity = world.getTileEntity(x, y, z)

    if (tileEntity.isInstanceOf[TileMultipart])
    {
      val part: TMultiPart = (tileEntity.asInstanceOf[TileMultipart]).partMap(placementSide.ordinal)
      if (part.isInstanceOf[PartMultimeter])
      {
        return part.asInstanceOf[PartMultimeter]
      }
    }

    return null
  }

  def getPosition: Vector3 =
  {
    return new Vector3(x, y, z)
  }

  @SideOnly(Side.CLIENT)
  override def getRenderBounds: Cuboid6 =
  {
    if (isPrimary) return Cuboid6.full.copy.expand(new Vector3(getGrid.size.x, getGrid.size.y, getGrid.size.z))
    return Cuboid6.full
  }

  def getGrid: MultimeterGrid =
  {
    if (grid == null)
    {
      grid = new MultimeterGrid
      grid.add(this)
    }
    return grid
  }

  def setGrid(network: MultimeterGrid)
  {
    grid = network
  }

  override def toString: String = "[PartMultimeter]" + x + "x " + y + "y " + z + "z " + getSlotMask + "s "

  protected def getItem: ItemStack =
  {
    return new ItemStack(ElectricalContent.itemMultimeter)
  }

  object DetectModes extends Enumeration
  {

    val NONE = DetectMode("none")
    val LESS_THAN = DetectMode("lessThan")
    val LESS_THAN_EQUAL = DetectMode("lessThanOrEqual")
    val EQUAL = DetectMode("equal")
    val GREATER_THAN_EQUAL = DetectMode("greaterThanOrEqual")
    val GREATER_THAN = DetectMode("greaterThan")

    case class DetectMode(display: String) extends super.Val(nextId)

  }

}