package pneumaticCraft.common.tileentity;

import java.util.Iterator;

import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemArmor;
import net.minecraft.item.ItemFood;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidHandler;
import pneumaticCraft.common.PneumaticCraftAPIHandler;
import pneumaticCraft.common.block.Blockss;
import pneumaticCraft.common.item.ItemMachineUpgrade;
import pneumaticCraft.common.item.Itemss;
import pneumaticCraft.common.network.GuiSynced;
import pneumaticCraft.common.util.PneumaticCraftUtils;
import pneumaticCraft.lib.ModIds;
import pneumaticCraft.lib.PneumaticValues;
import cofh.api.energy.EnergyStorage;
import cofh.api.energy.IEnergyContainerItem;
import cofh.api.energy.IEnergyReceiver;
import cofh.api.tileentity.IEnergyInfo;

import com.mojang.authlib.GameProfile;

import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Optional;

@Optional.InterfaceList({@Optional.Interface(iface = "cofh.api.energy.IEnergyReceiver", modid = ModIds.COFH_CORE), @Optional.Interface(iface = "cofh.api.tileentity.IEnergyInfo", modid = ModIds.COFH_CORE)})
public class TileEntityAerialInterface extends TileEntityPneumaticBase implements ISidedInventory, IFluidHandler,
        IMinWorkingPressure, IRedstoneControl, IEnergyReceiver, IEnergyInfo{
    private ItemStack[] inventory;

    private final int INVENTORY_SIZE = 4;

    private static final int UPGRADE_SLOT_START = 0;
    private static final int UPGRADE_SLOT_END = 3;

    @GuiSynced
    public String playerName = "";
    public String playerUUID = "";

    private Fluid curXpFluid;

    @GuiSynced
    public int redstoneMode;
    @GuiSynced
    public int feedMode;
    private boolean oldRedstoneStatus;
    private boolean updateNeighbours;
    @GuiSynced
    public boolean isConnectedToPlayer;
    private boolean dispenserUpgradeInserted;

    public TileEntityAerialInterface(){
        super(PneumaticValues.DANGER_PRESSURE_AERIAL_INTERFACE, PneumaticValues.MAX_PRESSURE_AERIAL_INTERFACE, PneumaticValues.VOLUME_AERIAL_INTERFACE);
        inventory = new ItemStack[INVENTORY_SIZE];
        setUpgradeSlots(new int[]{UPGRADE_SLOT_START, 1, 2, UPGRADE_SLOT_END});
        if(isRFAvailable()) initRF();
    }

    public void setPlayer(GameProfile gameProfile){
        setPlayer(gameProfile.getName(), gameProfile.getId() != null ? gameProfile.getId().toString() : "");
    }

    public void setPlayer(String username, String uuid){
        playerName = username;
        playerUUID = uuid;
        updateNeighbours = true;
    }

    @Override
    public void updateEntity(){
        if(!worldObj.isRemote && updateNeighbours) {
            updateNeighbours = false;
            updateNeighbours();
        }
        if(!worldObj.isRemote) {
            if(getPressure(ForgeDirection.UNKNOWN) > PneumaticValues.MIN_PRESSURE_AERIAL_INTERFACE && isConnectedToPlayer) {
                if(energyRF != null) tickRF();
                addAir(-PneumaticValues.USAGE_AERIAL_INTERFACE, ForgeDirection.UNKNOWN);
                if(worldObj.getTotalWorldTime() % 40 == 0) dispenserUpgradeInserted = getUpgrades(ItemMachineUpgrade.UPGRADE_DISPENSER_DAMAGE) > 0;
                if(worldObj.getTotalWorldTime() % 20 == 0) {
                    EntityPlayer player = getPlayer();
                    if(player != null && player.getAir() <= 280) {
                        player.setAir(player.getAir() + 20);
                        addAir(-4000, null);
                    }
                }
            }
            if(worldObj.getTotalWorldTime() % 20 == 0) getPlayerInventory();
        }

        if(oldRedstoneStatus != shouldEmitRedstone()) {
            oldRedstoneStatus = shouldEmitRedstone();
            updateNeighbours = true;
        }

        super.updateEntity();

    }

    @Override
    public void handleGUIButtonPress(int buttonID, EntityPlayer player){
        if(buttonID == 0) {
            redstoneMode++;
            if(redstoneMode > 1) redstoneMode = 0;
            // updateNeighbours();
        } else if(buttonID >= 1 && buttonID < 4) {
            feedMode = buttonID - 1;
        }
    }

    public boolean shouldEmitRedstone(){
        // if(!worldObj.isRemote) System.out.println("redstone mode: " +
        // redstoneMode + ", charging: " + charging + ",discharging: " +
        // disCharging);

        switch(redstoneMode){
            case 0:
                return false;
            case 1:
                return isConnectedToPlayer;

        }
        return false;
    }

    private InventoryPlayer getPlayerInventory(){
        EntityPlayer player = getPlayer();
        return player != null ? player.inventory : null;
    }

    private EntityPlayer getPlayer(){
        if(worldObj != null && !worldObj.isRemote) {
            EntityPlayer player = PneumaticCraftUtils.getPlayerFromId(playerUUID);
            isConnectedToPlayer = player != null;
            return player;
        }
        return null;
    }

    @Override
    public boolean hasCustomInventoryName(){
        return false;
    }

    /**
     * Returns the number of slots in the inventory.
     */
    @Override
    public int getSizeInventory(){
        InventoryPlayer inventoryPlayer = getPlayerInventory();
        return inventory.length + (inventoryPlayer != null ? inventoryPlayer.getSizeInventory() + (dispenserUpgradeInserted ? 1 : 0) : 0);
    }

    /**
     * Returns the stack in slot i
     */
    @Override
    public ItemStack getStackInSlot(int slot){
        if(slot < 4) {
            return inventory[slot];
        } else {
            InventoryPlayer inventoryPlayer = getPlayerInventory();
            return inventoryPlayer != null ? slot == inventory.length + inventoryPlayer.getSizeInventory() ? null : inventoryPlayer.getStackInSlot(slot - 4) : null;
        }
    }

    @Override
    public ItemStack decrStackSize(int slot, int amount){
        if(slot < 4) {
            ItemStack itemStack = getStackInSlot(slot);
            if(itemStack != null) {
                if(itemStack.stackSize <= amount) {
                    setInventorySlotContents(slot, null);
                } else {
                    itemStack = itemStack.splitStack(amount);
                    if(itemStack.stackSize == 0) {
                        setInventorySlotContents(slot, null);
                    }
                }
            }

            return itemStack;
        } else {
            InventoryPlayer inventoryPlayer = getPlayerInventory();
            return inventoryPlayer != null ? inventoryPlayer.decrStackSize(slot - 4, amount) : null;
        }
    }

    @Override
    public ItemStack getStackInSlotOnClosing(int slot){
        if(slot < 4) {
            ItemStack itemStack = getStackInSlot(slot);
            if(itemStack != null) {
                setInventorySlotContents(slot, null);
            }
            return itemStack;
        } else {
            InventoryPlayer inventoryPlayer = getPlayerInventory();
            return inventoryPlayer != null ? inventoryPlayer.getStackInSlotOnClosing(slot - 4) : null;
        }
    }

    @Override
    public void setInventorySlotContents(int slot, ItemStack itemStack){
        if(slot < 4) {
            inventory[slot] = itemStack;
            if(itemStack != null && itemStack.stackSize > getInventoryStackLimit()) {
                itemStack.stackSize = getInventoryStackLimit();
            }
        } else {
            EntityPlayer player = getPlayer();
            if(dispenserUpgradeInserted) {
                if(itemStack != null) {
                    int startValue = itemStack.stackSize;
                    while(itemStack.stackSize > 0) {
                        ItemStack remainingItem = itemStack.onFoodEaten(player.worldObj, player);
                        remainingItem = ForgeEventFactory.onItemUseFinish(player, itemStack, 0, remainingItem);
                        if(remainingItem != null && remainingItem.stackSize > 0 && (remainingItem != itemStack || remainingItem.stackSize != startValue)) {
                            if(!player.inventory.addItemStackToInventory(remainingItem) && remainingItem.stackSize > 0) {
                                player.dropPlayerItemWithRandomChoice(remainingItem, false);
                            }
                        }
                        if(itemStack.stackSize == startValue) break;
                    }
                }
            } else {
                InventoryPlayer inventoryPlayer = player != null ? player.inventory : null;
                if(inventoryPlayer != null) {
                    inventoryPlayer.setInventorySlotContents(slot - 4, itemStack);
                } else if(worldObj != null && !worldObj.isRemote) {
                    EntityItem item = new EntityItem(worldObj, xCoord, yCoord, zCoord, itemStack);
                    worldObj.spawnEntityInWorld(item);
                }
            }
        }

    }

    @Override
    public String getInventoryName(){

        return Blockss.aerialInterface.getUnlocalizedName();
    }

    @Override
    public int getInventoryStackLimit(){

        return 64;
    }

    @Override
    public void readFromNBT(NBTTagCompound tag){

        super.readFromNBT(tag);
        // Read in the ItemStacks in the inventory from NBT

        redstoneMode = tag.getInteger("redstoneMode");
        feedMode = tag.getInteger("feedMode");
        setPlayer(tag.getString("playerName"), tag.getString("playerUUID"));
        isConnectedToPlayer = tag.getBoolean("connected");
        if(tag.hasKey("curXpFluid")) curXpFluid = FluidRegistry.getFluid(tag.getString("curXpFluid"));
        if(energyRF != null) readRF(tag);

        NBTTagList tagList = tag.getTagList("Items", 10);
        inventory = new ItemStack[4];
        for(int i = 0; i < tagList.tagCount(); ++i) {
            NBTTagCompound tagCompound = tagList.getCompoundTagAt(i);
            byte slot = tagCompound.getByte("Slot");
            if(slot >= 0 && slot < inventory.length) {
                inventory[slot] = ItemStack.loadItemStackFromNBT(tagCompound);
            }
        }
        dispenserUpgradeInserted = getUpgrades(ItemMachineUpgrade.UPGRADE_DISPENSER_DAMAGE) > 0;
    }

    @Override
    public void writeToNBT(NBTTagCompound tag){

        super.writeToNBT(tag);
        // Write the ItemStacks in the inventory to NBT
        tag.setInteger("redstoneMode", redstoneMode);
        tag.setInteger("feedMode", feedMode);
        tag.setString("playerName", playerName);
        tag.setString("playerUUID", playerUUID);
        if(curXpFluid != null) tag.setString("curXpFluid", curXpFluid.getName());
        if(energyRF != null) saveRF(tag);

        tag.setBoolean("connected", isConnectedToPlayer);
        NBTTagList tagList = new NBTTagList();
        for(int currentIndex = 0; currentIndex < inventory.length; ++currentIndex) {
            if(inventory[currentIndex] != null) {
                NBTTagCompound tagCompound = new NBTTagCompound();
                tagCompound.setByte("Slot", (byte)currentIndex);
                inventory[currentIndex].writeToNBT(tagCompound);
                tagList.appendTag(tagCompound);
            }
        }
        tag.setTag("Items", tagList);
    }

    @Override
    public boolean isItemValidForSlot(int i, ItemStack itemstack){
        return i >= 4 || itemstack != null && itemstack.getItem() == Itemss.machineUpgrade;
    }

    @Override
    // upgrades in bottom, fuel in the rest.
    public int[] getAccessibleSlotsFromSide(int var1){
        if(ForgeDirection.getOrientation(var1) == ForgeDirection.UP) {
            return new int[]{0, 1, 2, 3};
        } else if(getPlayerInventory() == null) {
            return new int[0];
        } else if(ForgeDirection.getOrientation(var1) == ForgeDirection.DOWN) {
            return new int[]{40, 41, 42, 43};
        } else if(dispenserUpgradeInserted) {
            return new int[]{44};
        } else {
            int[] mainInv = new int[36];
            for(int i = 0; i < 36; i++) {
                mainInv[i] = i + 4;
            }
            return mainInv;
        }

    }

    @Override
    public boolean canInsertItem(int i, ItemStack itemstack, int j){
        if(i < 4) return true;
        EntityPlayer player = getPlayer();
        if(player == null) return false;
        if(getPressure(ForgeDirection.UNKNOWN) > PneumaticValues.MIN_PRESSURE_AERIAL_INTERFACE) {
            if(!dispenserUpgradeInserted || i >= 40 && i <= 43) {
                return i < 40 || itemstack != null && itemstack.getItem() instanceof ItemArmor && ((ItemArmor)itemstack.getItem()).armorType == 43 - i;
            } else {
                if(i == 4 + player.inventory.getSizeInventory() && getFoodValue(itemstack) > 0) {
                    int foodValue = getFoodValue(itemstack);
                    int curFoodLevel = player.getFoodStats().getFoodLevel();
                    int feedMode = this.feedMode;
                    if(feedMode == 2) {
                        feedMode = player.getMaxHealth() - player.getHealth() > 0 ? 1 : 0;
                    }
                    switch(feedMode){
                        case 0:
                            return 20 - curFoodLevel >= foodValue * itemstack.stackSize;
                        case 1:
                            return 20 - curFoodLevel >= foodValue * (itemstack.stackSize - 1) + 1;
                    }
                }
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    public boolean canExtractItem(int i, ItemStack itemstack, int j){
        return i < 4 || getPressure(ForgeDirection.UNKNOWN) > PneumaticValues.MIN_PRESSURE_AERIAL_INTERFACE;
    }

    @Override
    public boolean isUseableByPlayer(EntityPlayer var1){
        return isGuiUseableByPlayer(var1);
    }

    @Override
    public void openInventory(){}

    @Override
    public void closeInventory(){}

    private int getFoodValue(ItemStack item){
        return item != null && item.getItem() instanceof ItemFood ? ((ItemFood)item.getItem()).func_150905_g(item) : 0;
    }

    /*
     * 
     * ------------------------ Liquid XP handling
     * 
     */

    @Override
    public int fill(ForgeDirection from, FluidStack resource, boolean doFill){
        if(resource != null && canFill(from, resource.getFluid())) {
            EntityPlayer player = getPlayer();
            if(player != null) {
                int liquidToXP = PneumaticCraftAPIHandler.getInstance().liquidXPs.get(resource.getFluid());
                int xpPoints = resource.amount / liquidToXP;
                if(doFill) {
                    player.addExperience(xpPoints);
                    curXpFluid = resource.getFluid();
                }
                return xpPoints * liquidToXP;
            }
        }
        return 0;
    }

    @Override
    public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain){
        if(resource != null && canDrain(from, resource.getFluid())) {
            EntityPlayer player = getPlayer();
            if(player != null) {
                int liquidToXP = PneumaticCraftAPIHandler.getInstance().liquidXPs.get(resource.getFluid());
                int pointsDrained = Math.min(getPlayerXP(player), resource.amount / liquidToXP);
                if(doDrain) addPlayerXP(player, -pointsDrained);
                return new FluidStack(resource.getFluid(), pointsDrained * liquidToXP);
            }
        }
        return null;
    }

    @Override
    public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain){
        updateXpFluid();
        if(curXpFluid == null) return null;
        return drain(from, new FluidStack(curXpFluid, maxDrain), doDrain);
    }

    private void updateXpFluid(){
        if(curXpFluid == null) {
            Iterator<Fluid> fluids = PneumaticCraftAPIHandler.getInstance().liquidXPs.keySet().iterator();
            if(fluids.hasNext()) curXpFluid = fluids.next();
        }
    }

    @Override
    public boolean canFill(ForgeDirection from, Fluid fluid){
        return fluid != null && PneumaticCraftAPIHandler.getInstance().liquidXPs.containsKey(fluid) && getPlayer() != null && getPressure(ForgeDirection.UNKNOWN) > PneumaticValues.MIN_PRESSURE_AERIAL_INTERFACE && dispenserUpgradeInserted;
    }

    @Override
    public boolean canDrain(ForgeDirection from, Fluid fluid){
        return (fluid == null || PneumaticCraftAPIHandler.getInstance().liquidXPs.containsKey(fluid)) && getPlayer() != null && getPressure(ForgeDirection.UNKNOWN) > PneumaticValues.MIN_PRESSURE_AERIAL_INTERFACE && dispenserUpgradeInserted;
    }

    @Override
    public FluidTankInfo[] getTankInfo(ForgeDirection from){
        updateXpFluid();
        if(curXpFluid != null) {
            EntityPlayer player = getPlayer();
            if(player != null) {
                return new FluidTankInfo[]{new FluidTankInfo(new FluidStack(curXpFluid, getPlayerXP(player) * PneumaticCraftAPIHandler.getInstance().liquidXPs.get(curXpFluid)), Integer.MAX_VALUE)};
            }
        }
        return null;
    }

    /**
     * This method is copied from OpenMods' OpenModsLib
     * https://github.com/OpenMods/OpenModsLib/blob/master/src/main/java/openmods/utils/EnchantmentUtils.java
     * @param player
     * @return
     */

    private static int getPlayerXP(EntityPlayer player){
        return (int)(getExperienceForLevel(player.experienceLevel) + player.experience * player.xpBarCap());
    }

    /**
     * This method is copied from OpenMods' OpenModsLib
     * https://github.com/OpenMods/OpenModsLib/blob/master/src/main/java/openmods/utils/EnchantmentUtils.java
     * @param player
     * @return
     */

    private static int getExperienceForLevel(int level){
        if(level == 0) {
            return 0;
        }
        if(level > 0 && level < 16) {
            return level * 17;
        } else if(level > 15 && level < 31) {
            return (int)(1.5 * Math.pow(level, 2) - 29.5 * level + 360);
        } else {
            return (int)(3.5 * Math.pow(level, 2) - 151.5 * level + 2220);
        }
    }

    /**
     * This method is copied from OpenMods' OpenModsLib
     * https://github.com/OpenMods/OpenModsLib/blob/master/src/main/java/openmods/utils/EnchantmentUtils.java
     * @param player
     * @return
     */

    private static void addPlayerXP(EntityPlayer player, int amount){
        int experience = getPlayerXP(player) + amount;
        player.experienceTotal = experience;
        player.experienceLevel = getLevelForExperience(experience);
        int expForLevel = getExperienceForLevel(player.experienceLevel);
        player.experience = (float)(experience - expForLevel) / (float)player.xpBarCap();
    }

    /**
     * This method is copied from OpenMods' OpenModsLib
     * https://github.com/OpenMods/OpenModsLib/blob/master/src/main/java/openmods/utils/EnchantmentUtils.java
     * @param player
     * @return
     */

    private static int getLevelForExperience(int experience){
        int i = 0;
        while(getExperienceForLevel(i) <= experience) {
            i++;
        }
        return i - 1;
    }

    @Override
    public float getMinWorkingPressure(){
        return PneumaticValues.MIN_PRESSURE_AERIAL_INTERFACE;
    }

    @Override
    public int getRedstoneMode(){
        return redstoneMode;
    }

    /**
     * RF integration
     */

    private Object energyRF;
    private static final int RF_PER_TICK = 1000;

    private boolean isRFAvailable(){
        return Loader.isModLoaded(ModIds.COFH_CORE);
    }

    private void initRF(){
        energyRF = new EnergyStorage(100000);
    }

    private void saveRF(NBTTagCompound tag){
        getEnergy().writeToNBT(tag);
    }

    private void readRF(NBTTagCompound tag){
        getEnergy().readFromNBT(tag);
    }

    private void tickRF(){

        if(getEnergyStored(null) > 0) {
            InventoryPlayer inv = getPlayerInventory();
            if(inv != null) {
                for(int i = 0; i < inv.getSizeInventory(); i++) {
                    ItemStack stack = inv.getStackInSlot(i);
                    if(stack != null && stack.getItem() instanceof IEnergyContainerItem) {
                        IEnergyContainerItem chargingItem = (IEnergyContainerItem)stack.getItem();
                        int energyLeft = getEnergyStored(null);
                        if(energyLeft > 0) {
                            getEnergy().extractEnergy(chargingItem.receiveEnergy(stack, energyLeft, false), false);
                        } else {
                            break;
                        }
                    }
                }
            }
        }
    }

    @Optional.Method(modid = ModIds.COFH_CORE)
    private EnergyStorage getEnergy(){
        return (EnergyStorage)energyRF;
    }

    @Override
    public boolean canConnectEnergy(ForgeDirection from){
        return true;
    }

    @Override
    public int getInfoEnergyPerTick(){
        return RF_PER_TICK;
    }

    @Override
    public int getInfoMaxEnergyPerTick(){
        return RF_PER_TICK;
    }

    @Override
    public int getInfoEnergyStored(){
        return getEnergy().getEnergyStored();
    }

    @Override
    public int getInfoMaxEnergyStored(){
        return getEnergy().getMaxEnergyStored();
    }

    @Override
    public int receiveEnergy(ForgeDirection from, int maxReceive, boolean simulate){
        return getEnergy().receiveEnergy(maxReceive, simulate);
    }

    @Override
    public int getEnergyStored(ForgeDirection from){
        return getEnergy().getEnergyStored();
    }

    @Override
    public int getMaxEnergyStored(ForgeDirection from){
        return getEnergy().getMaxEnergyStored();
    }

}