package resonantinduction.atomic.machine.reactor;

import java.util.ArrayList;
import java.util.List;

import net.minecraft.block.Block;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.packet.Packet;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeDirection;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidContainerRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTank;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidHandler;
import resonant.api.IReactor;
import resonant.api.IReactorComponent;
import resonant.api.event.PlasmaEvent.SpawnPlasmaEvent;
import resonant.lib.content.module.prefab.TileInventory;
import resonant.lib.multiblock.IMultiBlockStructure;
import resonant.lib.multiblock.MultiBlockHandler;
import resonant.lib.network.PacketHandler;
import resonant.lib.network.Synced;
import resonant.lib.network.Synced.SyncedInput;
import resonant.lib.network.Synced.SyncedOutput;
import resonant.lib.prefab.poison.PoisonRadiation;
import resonant.lib.thermal.ThermalGrid;
import resonant.lib.thermal.ThermalPhysics;
import resonant.lib.utility.inventory.InventoryUtility;
import resonantinduction.atomic.Atomic;
import resonantinduction.atomic.ReactorExplosion;
import resonantinduction.atomic.machine.plasma.TilePlasma;
import resonantinduction.core.Reference;
import resonantinduction.core.ResonantInduction;
import universalelectricity.api.UniversalElectricity;
import universalelectricity.api.vector.Vector3;
import universalelectricity.api.vector.VectorWorld;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

/** The primary reactor component cell used to build reactors with.
 * 
 * @author Calclavia */
public class TileReactorCell extends TileInventory implements IMultiBlockStructure<TileReactorCell>, IInventory, IReactor, IFluidHandler, ISidedInventory
{
    public static final int RADIUS = 2;
    public static final int MELTING_POINT = 2000;

    private final int specificHeatCapacity = 1000;
    private final float mass = ThermalPhysics.getMass(1000, 7);
    public FluidTank tank = new FluidTank(FluidContainerRegistry.BUCKET_VOLUME * 15);

    @Synced
    public float temperature = 295;

    private float previousTemperature = 295;

    private boolean shouldUpdate = false;

    private long prevInternalEnergy = 0;
    private long internalEnergy = 0;
    private int meltdownCounter = 0;
    private int meltdownCounterMaximum = 1000;

    /** Multiblock Methods. */
    private MultiBlockHandler<TileReactorCell> multiBlock;

    public TileReactorCell()
    {
        super(UniversalElectricity.machine);
        textureName = "machine";
        isOpaqueCube = false;
        normalRender = false;
        customItemRender = true;
    }

    @Override
    protected void onWorldJoin()
    {
        updatePositionStatus();
    }

    @Override
    protected void onNeighborChanged()
    {
        updatePositionStatus();
    }

    @Override
    public void initiate()
    {
        updatePositionStatus();
        super.initiate();
    }

    /** Called when the block is right clicked by the player */
    @Override
    protected boolean use(EntityPlayer player, int side, Vector3 hit)
    {
        if (!world().isRemote)
        {
            TileReactorCell tile = getMultiBlock().get();

            if (player.inventory.getCurrentItem() != null)
            {
                if (tile.getStackInSlot(0) == null)
                {
                    if (player.inventory.getCurrentItem().getItem() instanceof IReactorComponent)
                    {
                        ItemStack itemStack = player.inventory.getCurrentItem().copy();
                        itemStack.stackSize = 1;
                        tile.setInventorySlotContents(0, itemStack);
                        player.inventory.decrStackSize(player.inventory.currentItem, 1);
                        return true;
                    }
                }
            }
            else if (player.isSneaking() && tile.getStackInSlot(0) != null)
            {
                InventoryUtility.dropItemStack(world(), new Vector3(player), tile.getStackInSlot(0), 0);
                tile.setInventorySlotContents(0, null);
                return true;
            }
            else
            {
                player.openGui(Atomic.INSTANCE, 0, world(), tile.xCoord, tile.yCoord, tile.zCoord);
            }
        }

        return true;
    }

    @Override
    protected void markUpdate()
    {
        super.markUpdate();
        shouldUpdate = true;
    }

    @Override
    public void updateEntity()
    {
        super.updateEntity();

        /** Move fuel rod down into the primary cell block if possible */
        if (!getMultiBlock().isPrimary())
        {
            if (getStackInSlot(0) != null)
            {
                if (getMultiBlock().get().getStackInSlot(0) == null)
                {
                    getMultiBlock().get().setInventorySlotContents(0, getStackInSlot(0));
                    setInventorySlotContents(0, null);
                }
            }

            if (tank.getFluidAmount() > 0)
            {
                getMultiBlock().get().tank.fill(tank.drain(tank.getCapacity(), true), true);
            }
        }

        if (!worldObj.isRemote)
        {
            if (getMultiBlock().isPrimary() && tank.getFluid() != null && tank.getFluid().fluidID == Atomic.FLUID_PLASMA.getID())
            {
                /** Spawn plasma */
                FluidStack drain = tank.drain(FluidContainerRegistry.BUCKET_VOLUME, false);

                if (drain != null && drain.amount >= FluidContainerRegistry.BUCKET_VOLUME)
                {
                    ForgeDirection spawnDir = ForgeDirection.getOrientation(worldObj.rand.nextInt(3) + 2);
                    Vector3 spawnPos = new Vector3(this).translate(spawnDir, 2);
                    spawnPos.translate(0, Math.max(worldObj.rand.nextInt(getHeight()) - 1, 0), 0);

                    if (worldObj.isAirBlock(spawnPos.intX(), spawnPos.intY(), spawnPos.intZ()))
                    {
                        MinecraftForge.EVENT_BUS.post(new SpawnPlasmaEvent(worldObj, spawnPos.intX(), spawnPos.intY(), spawnPos.intZ(), TilePlasma.plasmaMaxTemperature));
                        tank.drain(FluidContainerRegistry.BUCKET_VOLUME, true);
                    }
                }
            }
            else
            {
                prevInternalEnergy = internalEnergy;

                /** Handle cell rod interactions. */
                ItemStack fuelRod = getMultiBlock().get().getStackInSlot(0);

                if (fuelRod != null)
                {
                    if (fuelRod.getItem() instanceof IReactorComponent)
                    {
                        // Activate rods.
                        ((IReactorComponent) fuelRod.getItem()).onReact(fuelRod, this);

                        if (!worldObj.isRemote)
                        {
                            if (fuelRod.getItemDamage() >= fuelRod.getMaxDamage())
                            {
                                getMultiBlock().get().setInventorySlotContents(0, null);
                            }
                        }

                        // Emit Radiations
                        if (ticks % 20 == 0)
                        {
                            if (worldObj.rand.nextFloat() > 0.65)
                            {
                                List<EntityLiving> entities = worldObj.getEntitiesWithinAABB(EntityLiving.class, AxisAlignedBB.getBoundingBox(xCoord - RADIUS * 2, yCoord - RADIUS * 2, zCoord - RADIUS * 2, xCoord + RADIUS * 2, yCoord + RADIUS * 2, zCoord + RADIUS * 2));

                                for (EntityLiving entity : entities)
                                {
                                    PoisonRadiation.INSTANCE.poisonEntity(new Vector3(this), entity);
                                }
                            }
                        }
                    }
                }

                // Update the temperature from the thermal grid.
                temperature = ThermalGrid.getTemperature(new VectorWorld(this));

                /** Only a small percentage of the internal energy is used for temperature. */
                if (internalEnergy - prevInternalEnergy > 0)
                {
                    float deltaT = ThermalPhysics.getTemperatureForEnergy(mass, specificHeatCapacity, (long) ((internalEnergy - prevInternalEnergy) * 0.15));

                    /** Check control rods */
                    int rods = 0;

                    for (int i = 2; i < 6; i++)
                    {
                        Vector3 checkAdjacent = new Vector3(this).translate(ForgeDirection.getOrientation(i));

                        if (checkAdjacent.getBlockID(worldObj) == Atomic.blockControlRod.blockID)
                        {
                            deltaT /= 1.1;
                            rods++;
                        }
                    }

                    // Add heat to surrounding blocks in the thermal grid.
                    ThermalGrid.addTemperature(new VectorWorld(this), deltaT);

                    // Sound of lava flowing randomly plays when above temperature to boil water.
                    if (worldObj.rand.nextInt(80) == 0 && this.getTemperature() >= 373)
                    {
                        worldObj.playSoundEffect(this.xCoord + 0.5F, this.yCoord + 0.5F, this.zCoord + 0.5F, "Fluid.lava", 0.5F, 2.1F + (worldObj.rand.nextFloat() - worldObj.rand.nextFloat()) * 0.85F);
                    }

                    // Sounds of lava popping randomly plays when above temperature to boil water.
                    if (worldObj.rand.nextInt(40) == 0 && this.getTemperature() >= 373)
                    {
                        worldObj.playSoundEffect(this.xCoord + 0.5F, this.yCoord + 0.5F, this.zCoord + 0.5F, "Fluid.lavapop", 0.5F, 2.6F + (worldObj.rand.nextFloat() - worldObj.rand.nextFloat()) * 0.8F);
                    }

                    // Reactor cell plays random idle noises while operating and above temperature to boil water.
                    if (worldObj.getWorldTime() % (Atomic.SECOND_IN_TICKS * 5.0F) == 0 && this.getTemperature() >= 373)
                    {
                        float percentage = Math.min(this.getTemperature() / TileReactorCell.MELTING_POINT, 1.0F);
                        worldObj.playSoundEffect(this.xCoord + 0.5F, this.yCoord + 0.5F, this.zCoord + 0.5F, Reference.PREFIX + "reactorcell", percentage, 1.0F);
                    }

                    if (previousTemperature != temperature && !shouldUpdate)
                    {
                        shouldUpdate = true;
                        previousTemperature = temperature;
                        //System.out.println("[Atomic Science] [Thermal Grid] Temperature: " + String.valueOf(previousTemperature));
                    }

                    if (previousTemperature >= MELTING_POINT && meltdownCounter < meltdownCounterMaximum)
                    {
                        shouldUpdate = true;
                        meltdownCounter++;
                        //System.out.println("[Atomic Science] [Reactor Cell] Meltdown Ticker: " + String.valueOf(temperature) + " @ " + String.valueOf(meltdownCounter) + "/" + String.valueOf(meltdownCounterMaximum));
                    }
                    else if (previousTemperature >= MELTING_POINT && meltdownCounter >= meltdownCounterMaximum)
                    {
                        //System.out.println("[Atomic Science] [Reactor Cell] Meltdown Ticker: REACTOR MELTDOWN!");
                        meltdownCounter = 0;
                        meltDown();
                        return;
                    }

                    // Reset meltdown ticker to give the reactor more of a 'goldilocks zone'.
                    if (previousTemperature < MELTING_POINT && meltdownCounter < meltdownCounterMaximum && meltdownCounter > 0)
                    {
                        meltdownCounter--;
                        //System.out.println("[Atomic Science] [Reactor Cell] Meltdown Ticker: " + String.valueOf(temperature) + " @ " + String.valueOf(meltdownCounter) + "/" + String.valueOf(meltdownCounterMaximum));
                    }
                }

                internalEnergy = 0;

                if (isOverToxic())
                {
                    /** Randomly leak toxic waste when it is too toxic */
                    VectorWorld leakPos = new VectorWorld(this).translate(worldObj.rand.nextInt(20) - 10, worldObj.rand.nextInt(20) - 10, worldObj.rand.nextInt(20) - 10);

                    int blockID = leakPos.getBlockID();

                    if (blockID == Block.grass.blockID)
                    {
                        leakPos.setBlock(worldObj, Atomic.blockRadioactive.blockID);
                        tank.drain(FluidContainerRegistry.BUCKET_VOLUME, true);
                    }
                    else if (blockID == 0 || Block.blocksList[blockID].isBlockReplaceable(worldObj, leakPos.intX(), leakPos.intY(), leakPos.intZ()))
                    {
                        if (tank.getFluid() != null)
                        {
                            leakPos.setBlock(worldObj, tank.getFluid().getFluid().getBlockID());
                            tank.drain(FluidContainerRegistry.BUCKET_VOLUME, true);
                        }
                    }
                }
            }

            if (ticks % 60 == 0 || shouldUpdate)
            {
                shouldUpdate = false;
                notifyChange();
                PacketHandler.sendPacketToClients(getDescriptionPacket(), worldObj, new Vector3(this), 50);
            }
        }
        else
        {
            // Particles of white smoke will rise from above the reactor chamber when above water boiling temperature.
            if (worldObj.rand.nextInt(5) == 0 && this.getTemperature() >= 373)
            {
                worldObj.spawnParticle("cloud", this.xCoord + worldObj.rand.nextInt(2), this.yCoord + 1.0F, this.zCoord + worldObj.rand.nextInt(2), 0, 0.1D, 0);
                worldObj.spawnParticle("bubble", this.xCoord + worldObj.rand.nextInt(5), this.yCoord, this.zCoord + worldObj.rand.nextInt(5), 0, 0, 0);
            }
        }
    }

    @Override
    public boolean isOverToxic()
    {
        return tank.getFluid() != null && tank.getFluid().fluidID == Atomic.FLUID_TOXIC_WASTE.getID() && tank.getFluid().amount >= tank.getCapacity();
    }

    /** Multiblock Methods */
    public void updatePositionStatus()
    {
        TileReactorCell mainTile = getLowest();
        mainTile.getMultiBlock().deconstruct();
        mainTile.getMultiBlock().construct();

        boolean top = new Vector3(this).add(new Vector3(0, 1, 0)).getTileEntity(worldObj) instanceof TileReactorCell;
        boolean bottom = new Vector3(this).add(new Vector3(0, -1, 0)).getTileEntity(worldObj) instanceof TileReactorCell;

        if (top && bottom)
        {
            worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 1, 3);
        }
        else if (top)
        {
            worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 0, 3);
        }
        else
        {
            worldObj.setBlockMetadataWithNotify(xCoord, yCoord, zCoord, 2, 3);
        }
    }

    @Override
    public void onMultiBlockChanged()
    {

    }

    @Override
    public Vector3[] getMultiBlockVectors()
    {
        List<Vector3> vectors = new ArrayList<Vector3>();

        Vector3 checkPosition = new Vector3(this);

        while (true)
        {
            TileEntity t = checkPosition.getTileEntity(this.worldObj);

            if (t instanceof TileReactorCell)
            {
                vectors.add(checkPosition.clone().subtract(getPosition()));
            }
            else
            {
                break;
            }

            checkPosition.y++;
        }

        return vectors.toArray(new Vector3[0]);
    }

    public TileReactorCell getLowest()
    {
        TileReactorCell lowest = this;
        Vector3 checkPosition = new Vector3(this);

        while (true)
        {
            TileEntity t = checkPosition.getTileEntity(this.worldObj);

            if (t instanceof TileReactorCell)
            {
                lowest = (TileReactorCell) t;
            }
            else
            {
                break;
            }

            checkPosition.y--;
        }

        return lowest;
    }

    @Override
    public World getWorld()
    {
        return worldObj;
    }

    @Override
    public Vector3 getPosition()
    {
        return new Vector3(this);
    }

    @Override
    public MultiBlockHandler<TileReactorCell> getMultiBlock()
    {
        if (multiBlock == null)
        {
            multiBlock = new MultiBlockHandler<TileReactorCell>(this);
        }

        return multiBlock;
    }

    public int getHeight()
    {
        int height = 0;
        Vector3 checkPosition = new Vector3(this);
        TileEntity tile = this;

        while (tile instanceof TileReactorCell)
        {
            checkPosition.y++;
            height++;
            tile = checkPosition.getTileEntity(worldObj);
        }

        return height;
    }

    @Override
    public Packet getDescriptionPacket()
    {
        return ResonantInduction.PACKET_ANNOTATION.getPacket(this);
    }

    private void meltDown()
    {
        if (worldObj != null && !worldObj.isRemote)
        {
            // Turn the reactor cell into a block of lava to imply it melted.
            this.worldObj.setBlock(Block.lavaStill.blockID, 0, this.xCoord, this.yCoord, this.zCoord, 3);

            ReactorExplosion reactorExplosion = new ReactorExplosion(worldObj, null, xCoord, yCoord, zCoord, 9f);
            reactorExplosion.doExplosionA();
            reactorExplosion.doExplosionB(true);
        }
    }

    /** Reads a tile entity from NBT. */
    @SyncedInput
    @Override
    public void readFromNBT(NBTTagCompound nbt)
    {
        super.readFromNBT(nbt);
        temperature = nbt.getFloat("temperature");
        tank.readFromNBT(nbt);
        getMultiBlock().load(nbt);
    }

    /** Writes a tile entity to NBT. */
    @SyncedOutput
    @Override
    public void writeToNBT(NBTTagCompound nbt)
    {
        super.writeToNBT(nbt);
        nbt.setFloat("temperature", temperature);
        tank.writeToNBT(nbt);
        getMultiBlock().save(nbt);
    }

    @Override
    public int getInventoryStackLimit()
    {
        return 1;
    }

    /** Returns true if automation can insert the given item in the given slot from the given side.
     * Args: Slot, item, side */
    @Override
    public boolean canInsertItem(int slot, ItemStack items, int side)
    {
        return this.isItemValidForSlot(slot, items);
    }

    @Override
    public boolean isUseableByPlayer(EntityPlayer par1EntityPlayer)
    {
        return worldObj.getBlockTileEntity(xCoord, yCoord, zCoord) != this ? false : par1EntityPlayer.getDistanceSq(xCoord + 0.5D, yCoord + 0.5D, zCoord + 0.5D) <= 64.0D;
    }

    @Override
    public String getInvName()
    {
        return getBlockType().getLocalizedName();
    }

    @Override
    public boolean isItemValidForSlot(int slotID, ItemStack itemStack)
    {
        if (getMultiBlock().isPrimary() && getMultiBlock().get().getStackInSlot(0) == null)
        {
            return itemStack.getItem() instanceof IReactorComponent;
        }

        return false;
    }

    /** Fluid Functions. */
    @Override
    public int fill(ForgeDirection from, FluidStack resource, boolean doFill)
    {
        return getMultiBlock().get().tank.fill(resource, doFill);
    }

    @Override
    public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain)
    {
        return tank.drain(maxDrain, doDrain);
    }

    @Override
    public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain)
    {
        if (resource == null || !resource.isFluidEqual(tank.getFluid()))
        {
            return null;
        }

        return tank.drain(resource.amount, doDrain);
    }

    @Override
    public boolean canFill(ForgeDirection from, Fluid fluid)
    {
        return fluid == Atomic.FLUID_PLASMA;
    }

    @Override
    public boolean canDrain(ForgeDirection from, Fluid fluid)
    {
        return fluid == Atomic.FLUID_TOXIC_WASTE;
    }

    @Override
    public FluidTankInfo[] getTankInfo(ForgeDirection from)
    {
        return new FluidTankInfo[] { tank.getInfo() };
    }

    @Override
    @SideOnly(Side.CLIENT)
    public AxisAlignedBB getRenderBoundingBox()
    {
        if (getMultiBlock().isPrimary() && getMultiBlock().isConstructed())
        {
            return INFINITE_EXTENT_AABB;
        }

        return super.getRenderBoundingBox();
    }

    @Override
    public void heat(long energy)
    {
        internalEnergy = Math.max(internalEnergy + energy, 0);
    }

    @Override
    public float getTemperature()
    {
        return temperature;
    }
}
