package pneumaticCraft.common.block.tubes;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemDye;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.StatCollector;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidHandler;

import org.lwjgl.opengl.GL11;

import pneumaticCraft.client.model.IBaseModel;
import pneumaticCraft.client.model.tubemodules.ModelLogisticsModule;
import pneumaticCraft.client.util.RenderUtils;
import pneumaticCraft.common.ai.LogisticsManager;
import pneumaticCraft.common.ai.LogisticsManager.LogisticsTask;
import pneumaticCraft.common.network.NetworkHandler;
import pneumaticCraft.common.network.PacketUpdateLogisticModule;
import pneumaticCraft.common.semiblock.ISemiBlock;
import pneumaticCraft.common.semiblock.SemiBlockLogistics;
import pneumaticCraft.common.semiblock.SemiBlockManager;
import pneumaticCraft.common.tileentity.TileEntityPlasticMixer;
import pneumaticCraft.common.util.IOHelper;
import pneumaticCraft.common.util.PneumaticCraftUtils;
import pneumaticCraft.lib.Names;
import pneumaticCraft.proxy.CommonProxy.EnumGuiId;

public class ModuleLogistics extends TubeModule{
    private static final ModelLogisticsModule model = new ModelLogisticsModule();
    private SemiBlockLogistics cachedFrame;
    private int colorChannel;
    private int ticksSinceAction = -1;//client sided timer used to display the blue color when doing a logistic task.
    private int ticksSinceNotEnoughAir = -1;
    private int ticksUntilNextCycle;
    private boolean powered;
    private static final double MIN_PRESSURE = 3;
    private static final double ITEM_TRANSPORT_COST = 5;
    private static final double FLUID_TRANSPORT_COST = 0.1;

    @Override
    public double getWidth(){
        return 13 / 16D;
    }

    @Override
    protected double getHeight(){
        return 4.5D / 16D;
    }

    @Override
    public String getType(){
        return Names.MODULE_LOGISTICS;
    }

    @Override
    public IBaseModel getModel(){
        if(ticksSinceAction >= 0) {
            model.base1 = model.action;
        } else if(ticksSinceNotEnoughAir >= 0) {
            model.base1 = model.notEnoughAir;
        } else {
            model.base1 = hasPower() ? model.powered : model.notPowered;
        }
        return model;
    }

    @Override
    protected void renderModule(){
        super.renderModule();
        RenderUtils.glColorHex(0xFF000000 | ItemDye.field_150922_c[getColorChannel()]);
        model.renderChannelColorFrame(1 / 16F);
        GL11.glColor4d(1, 1, 1, 1);
    }

    @Override
    protected EnumGuiId getGuiId(){
        return null;
    }

    public int getColorChannel(){
        return colorChannel;
    }

    public void setColorChannel(int colorChannel){
        this.colorChannel = colorChannel;
    }

    public boolean hasPower(){
        return powered;
    }

    public void onUpdatePacket(int status, int colorChannel){
        powered = status > 0;
        if(status == 2) ticksSinceAction = 0;
        if(status == 3) ticksSinceNotEnoughAir = 0;
        this.colorChannel = colorChannel;
    }

    @Override
    public void writeToNBT(NBTTagCompound nbt){
        super.writeToNBT(nbt);
        nbt.setBoolean("powered", powered);
        nbt.setByte("colorChannel", (byte)colorChannel);
    }

    @Override
    public void readFromNBT(NBTTagCompound nbt){
        super.readFromNBT(nbt);
        powered = nbt.getBoolean("powered");
        colorChannel = nbt.getByte("colorChannel");
    }

    public SemiBlockLogistics getFrame(){
        if(cachedFrame == null) {
            ISemiBlock semiBlock = SemiBlockManager.getInstance(getTube().world()).getSemiBlock(getTube().world(), getTube().x() + dir.offsetX, getTube().y() + dir.offsetY, getTube().z() + dir.offsetZ);
            if(semiBlock instanceof SemiBlockLogistics) cachedFrame = (SemiBlockLogistics)semiBlock;
        }
        return cachedFrame;
    }

    @Override
    public boolean onActivated(EntityPlayer player){
        if(player.getCurrentEquippedItem() != null) {
            int colorIndex = TileEntityPlasticMixer.getDyeIndex(player.getCurrentEquippedItem());
            if(colorIndex >= 0) {
                if(!player.worldObj.isRemote) {
                    colorChannel = colorIndex;
                    NetworkHandler.sendToAllAround(new PacketUpdateLogisticModule(this, 0), getTube().world());
                }
                return true;
            }
        }
        return super.onActivated(player);
    }

    @Override
    public void update(){
        super.update();
        if(cachedFrame != null && cachedFrame.isInvalid()) cachedFrame = null;
        if(!getTube().world().isRemote) {
            if(powered != getTube().getAirHandler().getPressure(null) >= MIN_PRESSURE) {
                powered = !powered;
                NetworkHandler.sendToAllAround(new PacketUpdateLogisticModule(this, 0), getTube().world());
            }
            if(--ticksUntilNextCycle <= 0) {
                LogisticsManager manager = new LogisticsManager();
                Map<SemiBlockLogistics, ModuleLogistics> frameToModuleMap = new HashMap<SemiBlockLogistics, ModuleLogistics>();
                for(TubeModule module : ModuleNetworkManager.getInstance().getConnectedModules(this)) {
                    if(module instanceof ModuleLogistics) {
                        ModuleLogistics logistics = (ModuleLogistics)module;
                        if(logistics.getColorChannel() == getColorChannel()) {
                            logistics.ticksUntilNextCycle = 100;//Make sure any connected module doesn't tick, set it to a 5 second timer. This is also a penalty value when no task is executed this tick.
                            if(logistics.hasPower() && logistics.getFrame() != null) {
                                frameToModuleMap.put(logistics.getFrame(), logistics);
                                manager.addLogisticFrame(logistics.getFrame());
                            }
                        }
                    }
                }
                PriorityQueue<LogisticsTask> tasks = manager.getTasks(null);
                for(LogisticsTask task : tasks) {
                    if(task.isStillValid(task.transportingItem != null ? task.transportingItem : task.transportingFluid.stack)) {
                        if(task.transportingItem != null) {
                            ItemStack remainder = IOHelper.insert(task.requester.getTileEntity(), task.transportingItem.copy(), true);
                            if(remainder == null || remainder.stackSize != task.transportingItem.stackSize) {
                                ItemStack toBeExtracted = task.transportingItem.copy();
                                if(remainder != null) toBeExtracted.stackSize -= remainder.stackSize;
                                ItemStack extractedStack = IOHelper.extract(task.provider.getTileEntity(), toBeExtracted, true);
                                if(extractedStack != null) {
                                    ModuleLogistics provider = frameToModuleMap.get(task.provider);
                                    ModuleLogistics requester = frameToModuleMap.get(task.requester);
                                    int airUsed = (int)(ITEM_TRANSPORT_COST * extractedStack.stackSize * Math.pow(PneumaticCraftUtils.distBetweenSq(provider.getTube().x(), provider.getTube().y(), provider.getTube().z(), requester.getTube().x(), requester.getTube().y(), requester.getTube().z()), 0.25));
                                    if(requester.getTube().getAirHandler().getCurrentAir(null) > airUsed) {
                                        sendModuleUpdate(provider, true);
                                        sendModuleUpdate(requester, true);
                                        requester.getTube().getAirHandler().addAir(-airUsed, null);
                                        IOHelper.extract(task.provider.getTileEntity(), extractedStack, false);
                                        IOHelper.insert(task.requester.getTileEntity(), extractedStack, false);
                                        ticksUntilNextCycle = 20;
                                    } else {
                                        sendModuleUpdate(provider, false);
                                        sendModuleUpdate(requester, false);
                                    }
                                }
                            }
                        } else {
                            TileEntity providingTE = task.provider.getTileEntity();
                            TileEntity requestingTE = task.requester.getTileEntity();
                            if(providingTE instanceof IFluidHandler && requestingTE instanceof IFluidHandler) {
                                IFluidHandler provider = (IFluidHandler)task.provider.getTileEntity();
                                IFluidHandler requester = (IFluidHandler)task.requester.getTileEntity();

                                for(ForgeDirection di : ForgeDirection.VALID_DIRECTIONS) {
                                    int amountFilled = requester.fill(di, task.transportingFluid.stack, false);
                                    if(amountFilled > 0) {
                                        FluidStack drainingFluid = task.transportingFluid.stack.copy();
                                        drainingFluid.amount = amountFilled;
                                        FluidStack extractedFluid = null;
                                        ModuleLogistics p = frameToModuleMap.get(task.provider);
                                        ModuleLogistics r = frameToModuleMap.get(task.requester);
                                        int airUsed = 0;
                                        for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
                                            extractedFluid = provider.drain(d, drainingFluid, false);
                                            if(extractedFluid != null) {
                                                airUsed = (int)(FLUID_TRANSPORT_COST * extractedFluid.amount * PneumaticCraftUtils.distBetween(p.getTube().x(), p.getTube().y(), p.getTube().z(), r.getTube().x(), r.getTube().y(), r.getTube().z()));
                                                if(r.getTube().getAirHandler().getCurrentAir(null) > airUsed) {
                                                    extractedFluid = provider.drain(d, drainingFluid, true);
                                                    break;
                                                } else {
                                                    sendModuleUpdate(p, false);
                                                    sendModuleUpdate(r, false);
                                                    extractedFluid = null;
                                                    break;
                                                }
                                            }
                                        }
                                        if(extractedFluid != null) {
                                            sendModuleUpdate(p, true);
                                            sendModuleUpdate(r, true);
                                            r.getTube().getAirHandler().addAir(-airUsed, null);
                                            requester.fill(di, extractedFluid, true);
                                            ticksUntilNextCycle = 20;
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } else {
            if(ticksSinceAction >= 0) {
                ticksSinceAction++;
                if(ticksSinceAction > 3) ticksSinceAction = -1;
            }
            if(ticksSinceNotEnoughAir >= 0) {
                ticksSinceNotEnoughAir++;
                if(ticksSinceNotEnoughAir > 20) ticksSinceNotEnoughAir = -1;
            }
        }
    }

    private void sendModuleUpdate(ModuleLogistics module, boolean enoughAir){
        NetworkHandler.sendToAllAround(new PacketUpdateLogisticModule(module, enoughAir ? 1 : 2), module.getTube().world());
    }

    @Override
    public void addInfo(List<String> curInfo){
        super.addInfo(curInfo);
        String status;
        if(ticksSinceAction >= 0) {
            status = "waila.logisticsModule.transporting";
        } else if(ticksSinceNotEnoughAir >= 0) {
            status = "waila.logisticsModule.notEnoughAir";
        } else if(hasPower()) {
            status = "waila.logisticsModule.powered";
        } else {
            status = "waila.logisticsModule.noPower";
        }
        curInfo.add(StatCollector.translateToLocal("hud.msg.state") + ": " + StatCollector.translateToLocal(status));
        curInfo.add(StatCollector.translateToLocal("waila.logisticsModule.channel") + " " + EnumChatFormatting.YELLOW + StatCollector.translateToLocal("item.fireworksCharge." + ItemDye.field_150923_a[colorChannel]));
    }
}