package WayofTime.alchemicalWizardry.common.tileEntity;

import WayofTime.alchemicalWizardry.api.ColourAndCoords;
import WayofTime.alchemicalWizardry.api.Int3;
import WayofTime.alchemicalWizardry.api.alchemy.energy.*;
import WayofTime.alchemicalWizardry.common.entity.projectile.EntityParticleBeam;
import WayofTime.alchemicalWizardry.common.spell.complex.effect.SpellHelper;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S35PacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.ForgeDirection;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class TEReagentConduit extends TileSegmentedReagentHandler
{
    public List<ColourAndCoords> destinationList; //These are offsets
    public Map<Reagent, List<Int3>> reagentTargetList;
    public Map<Reagent, Integer> reagentTankDesignationList;
    public int tickRate = 20; //Rate that the reagents are sent

    int hasChanged = 0;
    public boolean affectedByRedstone = true;

    public int maxConnextions = 5;

    public int renderCount = 0;

    public TEReagentConduit()
    {
        this(2, 2000);
    }

    public TEReagentConduit(int numberOfTanks, int size)
    {
        super(numberOfTanks, size);

        destinationList = new LinkedList();
        reagentTargetList = new HashMap();
        reagentTankDesignationList = new HashMap();
    }

    public Int3 getColour()
    {
        int[] redMap = new int[this.tanks.length];
        int[] greenMap = new int[this.tanks.length];
        int[] blueMap = new int[this.tanks.length];

        for (int i = 0; i < this.tanks.length; i++)
        {
            ReagentContainer container = this.tanks[i];
            if (container != null && container.getReagent() != null)
            {
                Reagent reagent = container.getReagent().reagent;

                redMap[i] = reagent.getColourRed();
                greenMap[i] = reagent.getColourGreen();
                blueMap[i] = reagent.getColourBlue();
            }
        }

        int red = 0;
        int green = 0;
        int blue = 0;

        for (int i = 0; i < this.tanks.length; i++)
        {
            red += redMap[i];
            green += greenMap[i];
            blue += blueMap[i];
        }

        red /= this.tanks.length;
        green /= this.tanks.length;
        blue /= this.tanks.length;

        return new Int3(red, green, blue);
    }

    @Override
    public void writeToNBT(NBTTagCompound tag)
    {
        super.writeToNBT(tag);

        tag.setInteger("hasChanged", hasChanged);

        NBTTagList tagList = new NBTTagList();

        for (int i = 0; i < destinationList.size(); i++)
        {
            NBTTagCompound savedTag = new NBTTagCompound();
            tagList.appendTag(destinationList.get(i).writeToNBT(savedTag));
        }

        tag.setTag("destinationList", tagList);

        NBTTagList reagentTagList = new NBTTagList(); //TODO

        for (Entry<Reagent, List<Int3>> entry : reagentTargetList.entrySet())
        {
            NBTTagCompound savedTag = new NBTTagCompound();
            savedTag.setString("reagent", ReagentRegistry.getKeyForReagent(entry.getKey()));

            NBTTagList coordinateTagList = new NBTTagList();

            for (Int3 coord : entry.getValue())
            {
                NBTTagCompound coordinateTag = new NBTTagCompound();

                coord.writeToNBT(coordinateTag);

                coordinateTagList.appendTag(coordinateTag);
            }

            savedTag.setTag("coordinateList", coordinateTagList);

            reagentTagList.appendTag(savedTag);
        }

        tag.setTag("reagentTargetList", reagentTagList);

        NBTTagList tankDesignationList = new NBTTagList();

        for (Entry<Reagent, Integer> entry : this.reagentTankDesignationList.entrySet())
        {
            NBTTagCompound savedTag = new NBTTagCompound();

            savedTag.setString("reagent", ReagentRegistry.getKeyForReagent(entry.getKey()));
            savedTag.setInteger("integer", entry.getValue());

            tankDesignationList.appendTag(savedTag);
        }

        tag.setTag("tankDesignationList", tankDesignationList);

    }

    @Override
    public void readFromNBT(NBTTagCompound tag)
    {
        super.readFromNBT(tag);

        hasChanged = tag.getInteger("hasChanged");

        NBTTagList tagList = tag.getTagList("destinationList", Constants.NBT.TAG_COMPOUND);

        destinationList = new LinkedList();

        for (int i = 0; i < tagList.tagCount(); i++)
        {
            NBTTagCompound savedTag = tagList.getCompoundTagAt(i);

            destinationList.add(ColourAndCoords.readFromNBT(savedTag));
        }

        reagentTargetList = new HashMap();

        NBTTagList reagentTagList = tag.getTagList("reagentTargetList", Constants.NBT.TAG_COMPOUND);

        for (int i = 0; i < reagentTagList.tagCount(); i++)
        {
            NBTTagCompound savedTag = reagentTagList.getCompoundTagAt(i);

            Reagent reagent = ReagentRegistry.getReagentForKey(savedTag.getString("reagent"));

            List<Int3> coordList = new LinkedList();

            NBTTagList coordinateList = savedTag.getTagList("coordinateList", Constants.NBT.TAG_COMPOUND);

            for (int j = 0; j < coordinateList.tagCount(); j++)
            {
                coordList.add(Int3.readFromNBT(coordinateList.getCompoundTagAt(j)));
            }

            reagentTargetList.put(reagent, coordList);
        }

        reagentTankDesignationList = new HashMap();

        NBTTagList tankDesignationList = tag.getTagList("tankDesignationList", Constants.NBT.TAG_COMPOUND);

        for (int i = 0; i < tankDesignationList.tagCount(); i++)
        {
            NBTTagCompound savedTag = tankDesignationList.getCompoundTagAt(i);

            this.reagentTankDesignationList.put(ReagentRegistry.getReagentForKey(savedTag.getString("reagent")), new Integer(savedTag.getInteger("integer")));
        }
    }

    public void readClientNBT(NBTTagCompound tag)
    {
        NBTTagList tagList = tag.getTagList("destinationList", Constants.NBT.TAG_COMPOUND);

        destinationList = new LinkedList();

        for (int i = 0; i < tagList.tagCount(); i++)
        {
            NBTTagCompound savedTag = tagList.getCompoundTagAt(i);

            destinationList.add(ColourAndCoords.readFromNBT(savedTag));
        }

        NBTTagList reagentTagList = tag.getTagList("reagentTanks", Constants.NBT.TAG_COMPOUND);

        int size = reagentTagList.tagCount();
        this.tanks = new ReagentContainer[size];

        for (int i = 0; i < size; i++)
        {
            NBTTagCompound savedTag = reagentTagList.getCompoundTagAt(i);
            this.tanks[i] = ReagentContainer.readFromNBT(savedTag);
        }
    }

    public void writeClientNBT(NBTTagCompound tag)
    {
        NBTTagList tagList = new NBTTagList();

        for (int i = 0; i < destinationList.size(); i++)
        {
            NBTTagCompound savedTag = new NBTTagCompound();
            tagList.appendTag(destinationList.get(i).writeToNBT(savedTag));
        }

        tag.setTag("destinationList", tagList);

        NBTTagList reagentTagList = new NBTTagList();

        for (int i = 0; i < this.tanks.length; i++)
        {
            NBTTagCompound savedTag = new NBTTagCompound();
            if (this.tanks[i] != null)
            {
                this.tanks[i].writeToNBT(savedTag);
            }
            reagentTagList.appendTag(savedTag);
        }

        tag.setTag("reagentTanks", reagentTagList);
    }

    @Override
    public void updateEntity()
    {
        if (!worldObj.isRemote)
        {
            if (hasChanged > 1)
            {
                hasChanged = 1;
            } else if (hasChanged == 1)
            {
                hasChanged = 0;
            }

            if (worldObj.getWorldTime() % 100 == 99)
            {
                this.updateColourList();
            }

            if (affectedByRedstone && worldObj.isBlockIndirectlyGettingPowered(xCoord, yCoord, zCoord))
            {
                return;
            }

            int totalTransfered = 0;

            for (Entry<Reagent, List<Int3>> entry : this.reagentTargetList.entrySet())
            {
                for (Int3 coord : entry.getValue())
                {
                    if (totalTransfered >= this.tickRate)
                    {
                        break;
                    }

                    ReagentStack maxDrainAmount = this.drain(ForgeDirection.UNKNOWN, new ReagentStack(entry.getKey(), this.tickRate - totalTransfered), false);

                    if (maxDrainAmount == null)
                    {
                        continue;
                    }

                    int amountLeft = maxDrainAmount.amount;

                    if (amountLeft <= 0)
                    {
                        continue;
                    }

                    int x = xCoord + coord.xCoord;
                    int y = yCoord + coord.yCoord;
                    int z = zCoord + coord.zCoord;

                    TileEntity tile = worldObj.getTileEntity(x, y, z);
                    if (tile instanceof IReagentHandler)
                    {
                        int amount = Math.min(((IReagentHandler) tile).fill(ForgeDirection.UNKNOWN, maxDrainAmount, false), amountLeft);
                        if (amount > 0)
                        {
                            amountLeft -= amount;
                            totalTransfered += amount;

                            ReagentStack stack = this.drain(ForgeDirection.UNKNOWN, new ReagentStack(entry.getKey(), amount), true);
                            ((IReagentHandler) tile).fill(ForgeDirection.UNKNOWN, stack, true);
                        }
                    }
                }
            }
        } else
        {
            if (affectedByRedstone && worldObj.isBlockIndirectlyGettingPowered(xCoord, yCoord, zCoord))
            {
                return;
            }

            renderCount++;

            if (worldObj.getWorldTime() % 100 != 0)
            {
                return;
            }

            this.sendPlayerStuffs();
        }
    }


    @SideOnly(Side.CLIENT)
    public void sendPlayerStuffs()
    {
        Minecraft mc = Minecraft.getMinecraft();
        EntityPlayer player = mc.thePlayer;
        World world = mc.theWorld;
        if (SpellHelper.canPlayerSeeAlchemy(player))
        {
            for (ColourAndCoords colourSet : this.destinationList)
            {
                if (!(worldObj.getTileEntity(xCoord + colourSet.xCoord, yCoord + colourSet.yCoord, zCoord + colourSet.zCoord) instanceof IReagentHandler))
                {
                    continue;
                }
                EntityParticleBeam beam = new EntityParticleBeam(worldObj, xCoord + 0.5, yCoord + 0.5, zCoord + 0.5);
                double velocity = Math.sqrt(Math.pow(colourSet.xCoord, 2) + Math.pow(colourSet.yCoord, 2) + Math.pow(colourSet.zCoord, 2));
                double wantedVel = 0.3d;
                beam.setVelocity(wantedVel * colourSet.xCoord / velocity, wantedVel * colourSet.yCoord / velocity, wantedVel * colourSet.zCoord / velocity);
                beam.setColour(colourSet.colourRed / 255f, colourSet.colourGreen / 255f, colourSet.colourBlue / 255f);
                beam.setDestination(xCoord + colourSet.xCoord, yCoord + colourSet.yCoord, zCoord + colourSet.zCoord);
                worldObj.spawnEntityInWorld(beam);
            }
        }
    }

    public void updateColourList()
    {
        if (worldObj.isRemote)
        {
            return;
        }

        List<ColourAndCoords> newList = this.compileListForReagentTargets(this.reagentTargetList);

        if (newList != null && !newList.equals(destinationList))
        {
            this.destinationList = newList;
            worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
        }
    }

    public List<ColourAndCoords> compileListForReagentTargets(Map<Reagent, List<Int3>> map)
    {
        List<ColourAndCoords> list = new LinkedList();

        for (Entry<Reagent, List<Int3>> entry : map.entrySet())
        {
            if (entry.getValue() != null)
            {
                Reagent reagent = entry.getKey();
                if (reagent == null)
                {
                    continue;
                }
                List<Int3> coords = entry.getValue();
                for (Int3 coord : coords)
                {
                    if (coord == null)
                    {
                        continue;
                    }
                    list.add(new ColourAndCoords(reagent.getColourRed(), reagent.getColourGreen(), reagent.getColourBlue(), reagent.getColourIntensity(), coord.xCoord, coord.yCoord, coord.zCoord));
                }
            }
        }

        return list;
    }

    public boolean addDestinationViaOffset(int red, int green, int blue, int intensity, int xOffset, int yOffset, int zOffset)
    {
        if (xOffset == 0 && yOffset == 0 && zOffset == 0)
        {
            return false;
        }

        this.destinationList.add(new ColourAndCoords(red, green, blue, intensity, xOffset, yOffset, zOffset));

        return true;
    }

    public boolean addDestinationViaActual(int red, int green, int blue, int intensity, int x, int y, int z)
    {
        return this.addDestinationViaOffset(red, green, blue, intensity, x - this.xCoord, y - this.yCoord, z - this.zCoord);
    }

    @Override
    public Packet getDescriptionPacket()
    {
        NBTTagCompound nbttagcompound = new NBTTagCompound();
        writeClientNBT(nbttagcompound);
        return new S35PacketUpdateTileEntity(xCoord, yCoord, zCoord, 90210, nbttagcompound);
    }

    @Override
    public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity packet)
    {
        super.onDataPacket(net, packet);
        readClientNBT(packet.func_148857_g());
    }

    public boolean addReagentDestinationViaOffset(Reagent reagent, int xOffset, int yOffset, int zOffset)
    {
        int totalConnections = 0;

        for (Entry<Reagent, List<Int3>> entry : this.reagentTargetList.entrySet())
        {
            if (entry.getValue() != null)
            {
                totalConnections += entry.getValue().size();
            }
        }

        if (totalConnections >= this.maxConnextions)
        {
            //Send message that it cannot be done? Maybe add a Player instance
            return false;
        }

        if (xOffset == 0 && yOffset == 0 && zOffset == 0)
        {
            return false;
        }

        Int3 newCoord = new Int3(xOffset, yOffset, zOffset);

        if (this.reagentTargetList.containsKey(reagent))
        {
            List<Int3> coordList = this.reagentTargetList.get(reagent);
            if (coordList == null)
            {
                List<Int3> newCoordList = new LinkedList();
                newCoordList.add(newCoord);
                this.reagentTargetList.put(reagent, newCoordList);
            } else
            {
                coordList.add(newCoord);
            }

            return true;
        } else
        {
            List<Int3> newCoordList = new LinkedList();
            newCoordList.add(newCoord);
            this.reagentTargetList.put(reagent, newCoordList);

            return true;
        }
    }

    public boolean addReagentDestinationViaActual(Reagent reagent, int x, int y, int z)
    {
        return (this.addReagentDestinationViaOffset(reagent, x - xCoord, y - yCoord, z - zCoord));
    }

    public boolean removeReagentDestinationViaOffset(Reagent reagent, int xOffset, int yOffset, int zOffset)
    {
        if (this.reagentTargetList.containsKey(reagent))
        {
            List<Int3> coords = this.reagentTargetList.get(reagent);
            if (coords != null)
            {
                Int3 reference = new Int3(xOffset, yOffset, zOffset);

                return coords.remove(reference);
            }
        }
        return false;
    }

    public boolean removeReagentDestinationViaActual(Reagent reagent, int x, int y, int z)
    {
        return this.removeReagentDestinationViaOffset(reagent, x - xCoord, y - yCoord, z - zCoord);
    }

    @Override
    public int fill(ForgeDirection from, ReagentStack resource, boolean doFill)
    {
        if (doFill && !worldObj.isRemote)
        {
            worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
            hasChanged = 2;
        }

        return super.fill(from, resource, doFill);
    }

    @Override
    public ReagentStack drain(ForgeDirection from, ReagentStack resource, boolean doDrain)
    {
        if (doDrain && !worldObj.isRemote)
        {
            worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
            hasChanged = 2;
        }

        return super.drain(from, resource, doDrain);
    }
}