package edx.mechanical.fluid.pipe

import java.lang.{Iterable => JIterable}

import codechicken.lib.data.{MCDataInput, MCDataOutput}
import codechicken.lib.raytracer.IndexedCuboid6
import codechicken.lib.render.CCRenderState
import codechicken.lib.vec.Vector3
import cpw.mods.fml.relauncher.{Side, SideOnly}
import edx.core.prefab.part.CuboidShapes
import edx.core.prefab.part.connector.{PartFramedNode, TColorable, TMaterial}
import edx.mechanical.MechanicalContent
import edx.mechanical.fluid.pipe.PipeMaterials.PipeMaterial
import net.minecraft.client.renderer.RenderBlocks
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.fluids._
import resonant.lib.collection.EvictingList
import resonant.lib.wrapper.BitmaskWrapper._

import scala.collection.convert.wrapAll._
import scala.collection.mutable

/**
 * Fluid transport pipe
 *
 * @author Calclavia
 */
class PartPipe extends PartFramedNode with TMaterial[PipeMaterial] with TColorable with IFluidHandler
{
  override lazy val node = new NodePipe(this)

  /**
   * Computes the average fluid for client to render.
   */
  private val averageTankData = new EvictingList[Integer](20)
  private var markPacket = true

  material = PipeMaterials.ceramic
  node.onConnectionChanged = () => sendPacket(0)
  node.onFluidChanged = () => markPacket = true

  override def getSubParts: JIterable[IndexedCuboid6] =
  {
    val sideCuboids = CuboidShapes.thickSegment
    val list = mutable.Set.empty[IndexedCuboid6]
    list += CuboidShapes.thickCenter
    list ++= ForgeDirection.VALID_DIRECTIONS.filter(s => clientRenderMask.mask(s) || s == testingSide).map(s => sideCuboids(s.ordinal()))
    return list
  }

  def preparePlacement(meta: Int)
  {
    setMaterial(meta)
  }

  def setMaterial(i: Int)
  {
    material = PipeMaterials(i).asInstanceOf[PipeMaterial]
  }

  override def update()
  {
    super.update()

    averageTankData.add(node.getFluidAmount)

    if (!world.isRemote && markPacket)
    {
      sendPacket(3)
      markPacket = false
    }
  }

  /**
   * Packet Methods
   */
  override def write(packet: MCDataOutput, id: Int)
  {
    super[PartFramedNode].write(packet, id)
    super[TMaterial].write(packet, id)
    super[TColorable].write(packet, id)

    if (id == 3)
    {
      //node Packet
      val nbt = new NBTTagCompound
      val averageAmount = averageTankData.reduce(_ + _) / averageTankData.size
      val tempTank = node.getPrimaryTank //if (node.getFluid != null) new FluidTank(node.getFluid.getFluid, averageAmount, node.getCapacity) else new FluidTank(node.getCapacity)
      tempTank.writeToNBT(nbt)
      packet.writeInt(node.getCapacity).writeNBTTagCompound(nbt)
    }
  }

  override def read(packet: MCDataInput, packetID: Int)
  {
    super[PartFramedNode].read(packet, packetID)
    super[TMaterial].read(packet, packetID)
    super[TColorable].read(packet, packetID)

    if (packetID == 3 && world.isRemote)
    {
      node.setPrimaryTank(new FluidTank(packet.readInt))
      node.getPrimaryTank.readFromNBT(packet.readNBTTagCompound)
    }
  }

  /**
   * NBT Methods
   */
  override def load(nbt: NBTTagCompound)
  {
    super[PartFramedNode].load(nbt)
    super[TMaterial].load(nbt)
    super[TColorable].load(nbt)

    node.load(nbt)
  }

  override def save(nbt: NBTTagCompound)
  {
    super[PartFramedNode].save(nbt)
    super[TMaterial].save(nbt)
    super[TColorable].save(nbt)

    node.save(nbt)
  }

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

  def getItem: ItemStack = new ItemStack(MechanicalContent.itemPipe, 1, getMaterialID)

  def getMaterialID: Int = material.id

  override def fill(from: ForgeDirection, resource: FluidStack, doFill: Boolean): Int =
  {
    if (!world.isRemote)
    {
      if (doFill)
        markPacket = true

      return node.fill(resource, doFill)
    }
    return 0
  }

  override def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean): FluidStack =
  {
    return drain(from, resource.amount, doDrain)
  }

  override def drain(from: ForgeDirection, maxDrain: Int, doDrain: Boolean): FluidStack =
  {
    if (!world.isRemote)
    {
      if (doDrain)
        markPacket = true

      return node.drain(maxDrain, doDrain)
    }
    return null
  }

  override def canFill(from: ForgeDirection, fluid: Fluid): Boolean = true

  override def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = true

  override def getTankInfo(from: ForgeDirection): Array[FluidTankInfo] = Array[FluidTankInfo](node.getInfo)

  override def drawBreaking(renderBlocks: RenderBlocks)
  {
    CCRenderState.reset()
  }
}