package li.cil.oc.integration.fmp

import codechicken.lib.packet.PacketCustom
import codechicken.lib.raytracer.RayTracer
import codechicken.lib.vec.BlockCoord
import codechicken.lib.vec.Vector3
import codechicken.multipart.TileMultipart
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import li.cil.oc.Constants
import li.cil.oc.Settings
import li.cil.oc.api.Items
import li.cil.oc.client.PacketSender
import li.cil.oc.common.block.SimpleBlock
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemBlock
import net.minecraft.network.play.client.C08PacketPlayerBlockPlacement
import net.minecraft.util.MovingObjectPosition
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent
import net.minecraftforge.event.entity.player.PlayerInteractEvent
import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action

object EventHandler {
  private var currentlyPlacing = false

  private val yaw2Direction = Array(ForgeDirection.SOUTH, ForgeDirection.WEST, ForgeDirection.NORTH, ForgeDirection.EAST)

  @SubscribeEvent
  def playerInteract(event: PlayerInteractEvent) {
    this.synchronized {
      if (currentlyPlacing) return
      try {
        currentlyPlacing = true
        val player = event.entityPlayer
        if (event.action == Action.RIGHT_CLICK_BLOCK && player.getEntityWorld.isRemote) {
          if (place(player)) {
            event.setCanceled(true)
          }
        }
      }
      finally {
        currentlyPlacing = false
      }
    }
  }

  def place(player: EntityPlayer) = {
    val world = player.getEntityWorld
    val hit = RayTracer.reTrace(world, player)
    if (hit != null && player.getHeldItem != null) player.getHeldItem.getItem match {
      case itemBlock: ItemBlock =>
        itemBlock.field_150939_a match {
          case simpleBlock: SimpleBlock =>
            if (simpleBlock == Items.get(Constants.BlockName.Cable).block()) {
              placeDelegatePart(player, hit, new CablePart())
            }
            else if (simpleBlock == Items.get(Constants.BlockName.Print).block()) {
              val part = new PrintPart()
              part.data.load(player.getHeldItem)
              part.facing = yaw2Direction((player.rotationYaw / 360 * 4).round & 3).getOpposite
              placeDelegatePart(player, hit, part)
            }
            else false
          case _ => false
        }
      case _ => false
    }
    else false
  }


  protected def placeDelegatePart(player: EntityPlayer, hit: MovingObjectPosition, part: SimpleBlockPart): Boolean = {
    val world = player.getEntityWorld
    if (world.isRemote && !player.isSneaking) {
      // Attempt to use block activated like normal and tell the server the right stuff
      val f = new Vector3(hit.hitVec.xCoord - hit.blockX, hit.hitVec.yCoord - hit.blockY, hit.hitVec.zCoord - hit.blockZ)
      val block = world.getBlock(hit.blockX, hit.blockY, hit.blockZ)
      if (block != null && block.onBlockActivated(world, hit.blockX, hit.blockY, hit.blockZ, player, hit.sideHit, f.x.toFloat, f.y.toFloat, f.z.toFloat)) {
        player.swingItem()
        PacketCustom.sendToServer(new C08PacketPlayerBlockPlacement(
          hit.blockX, hit.blockY, hit.blockZ, hit.sideHit,
          player.inventory.getCurrentItem,
          f.x.toFloat, f.y.toFloat, f.z.toFloat))
        return true
      }
    }

    val pos = new BlockCoord(hit.blockX, hit.blockY, hit.blockZ)
    val posOutside = pos.copy().offset(hit.sideHit)
    val inside = Option(TileMultipart.getOrConvertTile(world, pos))
    val outside = Option(TileMultipart.getOrConvertTile(world, posOutside))
    inside match {
      case Some(t) if t.canAddPart(part) && canAddPrint(t, part) => placeMultiPart(player, part, pos)
      case _ => outside match {
        case Some(t) if t.canAddPart(part) && canAddPrint(t, part) => placeMultiPart(player, part, posOutside)
        case _ => false
      }
    }
  }

  protected def canAddPrint(t: TileMultipart, p: SimpleBlockPart): Boolean = p match {
    case print: PrintPart =>
      val (offSum, onSum) = t.partList.foldLeft((print.data.stateOff.size, print.data.stateOn.size))((acc, part) => {
        val (offAcc, onAcc) = acc
        val (offCount, onCount) = part match {
          case innerPrint: PrintPart => (innerPrint.data.stateOff.size, innerPrint.data.stateOn.size)
          case _ => (0, 0)
        }
        (offAcc + offCount, onAcc + onCount)
      })
      offSum <= Settings.get.maxPrintComplexity && onSum <= Settings.get.maxPrintComplexity
    case _ => true
  }

  protected def placeMultiPart(player: EntityPlayer, part: SimpleBlockPart, pos: BlockCoord) = {
    val world = player.getEntityWorld
    if (world.isRemote) {
      player.swingItem()
      PacketSender.sendMultiPlace()
    }
    else {
      TileMultipart.addPart(world, pos, part)
      world.playSoundEffect(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5,
        part.simpleBlock.stepSound.func_150496_b,
        (part.simpleBlock.stepSound.getVolume + 1.0F) / 2.0F,
        part.simpleBlock.stepSound.getPitch * 0.8F)
      if (!player.capabilities.isCreativeMode) {
        val held = player.getHeldItem
        held.stackSize -= 1
        if (held.stackSize == 0) {
          player.inventory.mainInventory(player.inventory.currentItem) = null
          MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(player, held))
        }
      }
    }
    true
  }
}