package pneumaticCraft.common.ai;

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

import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidHandler;
import pneumaticCraft.common.semiblock.IProvidingInventoryListener;
import pneumaticCraft.common.semiblock.ISpecificProvider;
import pneumaticCraft.common.semiblock.ISpecificRequester;
import pneumaticCraft.common.semiblock.SemiBlockLogistics;
import pneumaticCraft.common.semiblock.SemiBlockLogistics.FluidStackWrapper;
import pneumaticCraft.common.util.IOHelper;

public class LogisticsManager{

    private final List<SemiBlockLogistics>[] logistics = new List[4];

    public LogisticsManager(){
        for(int i = 0; i < logistics.length; i++) {
            logistics[i] = new ArrayList<SemiBlockLogistics>();
        }
    }

    public void clearLogistics(){
        for(List<SemiBlockLogistics> list : logistics) {
            list.clear();
        }
    }

    public void addLogisticFrame(SemiBlockLogistics frame){
        logistics[frame.getPriority()].add(frame);
    }

    public PriorityQueue<LogisticsTask> getTasks(Object holdingStack){
        ItemStack item = holdingStack instanceof ItemStack ? (ItemStack)holdingStack : null;
        FluidStack fluid = holdingStack instanceof FluidStack ? (FluidStack)holdingStack : null;
        PriorityQueue<LogisticsTask> tasks = new PriorityQueue<LogisticsTask>();
        for(int priority = logistics.length - 1; priority >= 0; priority--) {
            for(SemiBlockLogistics requester : logistics[priority]) {
                for(int i = 0; i < priority; i++) {
                    for(SemiBlockLogistics provider : logistics[i]) {
                        if(provider.shouldProvideTo(priority)) {
                            if(item != null) {
                                int requestedAmount = getRequestedAmount(requester, item);
                                if(requestedAmount > 0) {
                                    ItemStack stack = item.copy();
                                    stack.stackSize = requestedAmount;
                                    tasks.add(new LogisticsTask(provider, requester, stack));
                                    return tasks;
                                }
                            } else if(fluid != null) {
                                int requestedAmount = getRequestedAmount(requester, fluid);
                                if(requestedAmount > 0) {
                                    fluid = fluid.copy();
                                    fluid.amount = requestedAmount;
                                    tasks.add(new LogisticsTask(provider, requester, new FluidStackWrapper(fluid)));
                                    return tasks;
                                }
                            } else {
                                tryProvide(provider, requester, tasks);
                            }
                        }
                    }
                }
            }
        }
        return tasks;
    }

    //curAI = new DroneAILiquidImport(drone, new FakeWidgetLogistics(provider.getPos(), providingStack));
    // transportingFluid = new FluidStackWrapper(providingStack);

    private void tryProvide(SemiBlockLogistics provider, SemiBlockLogistics requester, PriorityQueue<LogisticsTask> tasks){
        IInventory providingInventory = IOHelper.getInventoryForTE(provider.getTileEntity());
        if(providingInventory != null) {
            if(requester instanceof IProvidingInventoryListener) ((IProvidingInventoryListener)requester).notify(provider.getTileEntity());
            for(int i = 0; i < providingInventory.getSizeInventory(); i++) {
                ItemStack providingStack = providingInventory.getStackInSlot(i);
                if(providingStack != null && (!(provider instanceof ISpecificProvider) || ((ISpecificProvider)provider).canProvide(providingStack)) && IOHelper.canExtractItemFromInventory(providingInventory, providingStack, i, 0)) {
                    int requestedAmount = getRequestedAmount(requester, providingStack);
                    if(requestedAmount > 0) {
                        ItemStack stack = providingStack.copy();
                        stack.stackSize = requestedAmount;
                        tasks.add(new LogisticsTask(provider, requester, stack));
                    }
                }
            }
        }
        if(provider.getTileEntity() instanceof IFluidHandler) {
            IFluidHandler providingTank = (IFluidHandler)provider.getTileEntity();
            for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
                FluidStack providingStack = providingTank.drain(d, 16000, false);
                if(providingStack != null && (!(provider instanceof ISpecificProvider) || ((ISpecificProvider)provider).canProvide(providingStack)) && providingTank.canDrain(d, providingStack.getFluid())) {
                    int requestedAmount = getRequestedAmount(requester, providingStack);
                    if(requestedAmount > 0) {
                        FluidStack stack = providingStack.copy();
                        stack.amount = requestedAmount;
                        tasks.add(new LogisticsTask(provider, requester, new FluidStackWrapper(stack)));
                    }
                }
            }
        }
    }

    public static int getRequestedAmount(SemiBlockLogistics requester, ItemStack providingStack){
        TileEntity te = requester.getTileEntity();
        if(!(te instanceof IInventory)) return 0;
        int requestedAmount = requester instanceof ISpecificRequester ? ((ISpecificRequester)requester).amountRequested(providingStack) : providingStack.stackSize;
        if(requestedAmount == 0) return 0;
        providingStack = providingStack.copy();
        providingStack.stackSize = requestedAmount;
        ItemStack remainder = providingStack.copy();
        remainder.stackSize += requester.getIncomingItems(providingStack);
        for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
            remainder = IOHelper.insert(te, remainder, d, true);
            if(remainder == null) break;
        }
        if(remainder != null) providingStack.stackSize -= remainder.stackSize;
        if(providingStack.stackSize <= 0) return 0;
        return providingStack.stackSize;
    }

    public static int getRequestedAmount(SemiBlockLogistics requester, FluidStack providingStack){
        int requestedAmount = requester instanceof ISpecificRequester ? ((ISpecificRequester)requester).amountRequested(providingStack) : providingStack.amount;
        if(requestedAmount == 0) return 0;
        providingStack = providingStack.copy();
        providingStack.amount = requestedAmount;
        FluidStack remainder = providingStack.copy();
        remainder.amount += requester.getIncomingFluid(remainder.getFluid());
        TileEntity te = requester.getTileEntity();
        if(te instanceof IFluidHandler) {
            IFluidHandler fluidHandler = (IFluidHandler)te;
            for(ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
                int fluidFilled = fluidHandler.fill(d, remainder, false);
                if(fluidFilled > 0) {
                    remainder.amount -= fluidFilled;
                    break;
                }
            }
        }
        providingStack.amount -= remainder.amount;
        if(providingStack.amount <= 0) return 0;
        return providingStack.amount;
    }

    public static class LogisticsTask implements Comparable<LogisticsTask>{

        public final SemiBlockLogistics provider, requester;
        public final ItemStack transportingItem;
        public final FluidStackWrapper transportingFluid;

        public LogisticsTask(SemiBlockLogistics provider, SemiBlockLogistics requester, ItemStack transportingItem){
            this.provider = provider;
            this.requester = requester;
            this.transportingItem = transportingItem;
            transportingFluid = null;
        }

        public LogisticsTask(SemiBlockLogistics provider, SemiBlockLogistics requester,
                FluidStackWrapper transportingFluid){
            this.provider = provider;
            this.requester = requester;
            this.transportingFluid = transportingFluid;
            transportingItem = null;
        }

        public void informRequester(){
            if(transportingItem != null) {
                requester.informIncomingStack(transportingItem);
            } else {
                requester.informIncomingStack(transportingFluid);
            }
        }

        public boolean isStillValid(Object stack){
            if(transportingItem != null && stack instanceof ItemStack) {
                int requestedAmount = getRequestedAmount(requester, (ItemStack)stack);
                return requestedAmount == ((ItemStack)stack).stackSize;
            } else if(transportingFluid != null && stack instanceof FluidStack) {
                int requestedAmount = getRequestedAmount(requester, (FluidStack)stack);
                return requestedAmount == ((FluidStack)stack).amount;
            } else {
                return false;
            }
        }

        @Override
        public int compareTo(LogisticsTask task){
            int value = transportingItem != null ? transportingItem.stackSize * 100 : transportingFluid.stack.amount;
            int otherValue = task.transportingItem != null ? task.transportingItem.stackSize * 100 : task.transportingFluid.stack.amount;
            return otherValue - value;
        }

    }
}