package thebetweenlands.client.handler;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.client.event.EntityViewRenderEvent.FogColors;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.event.entity.player.ArrowLooseEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import thebetweenlands.api.event.ArmSwingSpeedEvent;
import thebetweenlands.client.render.particle.BLParticles;
import thebetweenlands.client.render.particle.ParticleFactory;
import thebetweenlands.common.herblore.elixir.ElixirEffectRegistry;

public class ElixirClientHandler {

    public static final ElixirClientHandler INSTANCE = new ElixirClientHandler();

    public ElixirClientHandler() {}

    private static class EntityTrail {
        private static final int MAX_CACHE_SIZE = 80;
        private Entity entity;
        private List<Vec3d> cachedPositions = new ArrayList<>();

        private EntityTrail(Entity entity) {
            this.entity = entity;
        }

        private void update(int strength) {
            if(this.entity != null && !this.entity.isDead) {
                Vec3d newPos = new Vec3d(this.entity.posX, this.entity.posY, this.entity.posZ);
                if(this.cachedPositions.size() > 0) {
                    Vec3d lastPos = this.cachedPositions.get(this.cachedPositions.size() - 1);
                    if(lastPos.distanceTo(newPos) > 0.5D) {
                        this.cachedPositions.add(newPos);
                    }
                    if(this.cachedPositions.size() > MAX_CACHE_SIZE + MAX_CACHE_SIZE / 4 * (strength + 1)) {
                        this.cachedPositions.remove(0);
                    }
                } else {
                    this.cachedPositions.add(newPos);
                }
            }
        }
    }
    private Map<Entity, EntityTrail> entityTrails = new HashMap<Entity, EntityTrail>();
    public EntityTrail getTrail(Entity entity) {
        EntityTrail trail = this.entityTrails.get(entity);
        if(trail == null) {
            trail = new EntityTrail(entity);
            this.entityTrails.put(entity, trail);
        }
        return trail;
    }
    private static class TrailPos {
        private final Vec3d pos;
        private Vec3d nextPos;
        private final Entity entity;
        private final int index;
        private TrailPos(Vec3d pos, int index, Entity entity) {
            this.pos = pos;
            this.index = index;
            this.entity = entity;
        }
    }
    private Vec3d playerPos;
    private final Comparator<TrailPos> dstSorter = new Comparator<TrailPos>() {
        @Override
        public int compare(TrailPos v1, TrailPos v2) {
            double d1 = v1.pos.distanceTo(playerPos);
            double d2 = v2.pos.distanceTo(playerPos);
            if (d1 < d2)
                return -1;
            else if (d1 > d2)
                return 1;
            else
                return 0;
        }
    };
    
    @SubscribeEvent
    public void onArmSwingSpeed(ArmSwingSpeedEvent event) {
    	EntityLivingBase living = event.getEntityLiving();
    	if(ElixirEffectRegistry.EFFECT_SLUGARM.isActive(living)) {
            int strength = ElixirEffectRegistry.EFFECT_SLUGARM.getStrength(living);
            event.setSpeed(event.getSpeed() / (float)(2 << strength));
        }
    }

    @SubscribeEvent
    public void onClientTick(TickEvent.ClientTickEvent event) {
        EntityPlayer player = Minecraft.getMinecraft().player;
        
        if(event.phase == TickEvent.Phase.END) {
            if(player != null && player.world != null && player.world.isRemote && player == Minecraft.getMinecraft().player) {
                if(ElixirEffectRegistry.EFFECT_HUNTERSSENSE.isActive(player)) {
                    int strength = ElixirEffectRegistry.EFFECT_HUNTERSSENSE.getStrength(player);
                    World world = player.world;
                    List<Entity> entityList = world.getEntitiesWithinAABB(Entity.class, player.getEntityBoundingBox().grow(50, 50, 50));
                    List<TrailPos> availablePositions = new ArrayList<TrailPos>();
                    for(Entity e : entityList) {
                        if(e == player) continue;
                        EntityTrail trail = this.getTrail(e);
                        trail.update(strength);
                        TrailPos lastPos = null;
                        for(int i = 0; i < trail.cachedPositions.size(); i++) {
                            Vec3d pos = trail.cachedPositions.get(i);
                            if(lastPos != null) lastPos.nextPos = pos;
                            TrailPos tp = new TrailPos(pos, i, e);
                            availablePositions.add(tp);
                            lastPos = tp;
                        }
                    }
                    this.playerPos = new Vec3d(player.posX, player.posY, player.posZ);
                    availablePositions.sort(dstSorter);
                    int maxPointCount = 200;
                    int pointCount = Math.min(maxPointCount, availablePositions.size());
                    int crawlTicks = MathHelper.floor(20.0F + 120.0F - 120.0F / 4.0F * strength);
                    for(int i = 0; i < pointCount; i++) {
                        TrailPos tp = availablePositions.get(i);
                        if((player.ticksExisted - MathHelper.floor((float)crawlTicks / (float)EntityTrail.MAX_CACHE_SIZE * (float)tp.index)) % crawlTicks == 0) {
                            Vec3d pos = tp.pos;
                            if(tp.nextPos != null) {
                                int subSegments = 10;
                                Vec3d nextPos = tp.nextPos;
                                for(int s = 0; s <= subSegments; s++) {
                                    if((player.ticksExisted - MathHelper.floor((float)crawlTicks / (float)EntityTrail.MAX_CACHE_SIZE * (float)(tp.index + s / (float)subSegments))) % crawlTicks == 0) {
                                        double tpx = pos.x + 0.5F;
                                        double tpy = pos.y + 0.05F;
                                        double tpz = pos.z + 0.5F;
                                        double tpx2 = nextPos.x + 0.5F;
                                        double tpy2 = nextPos.y;
                                        double tpz2 = nextPos.z + 0.5F;
                                        double tpxi = tpx + (tpx2 - tpx) / (double)subSegments * s;
                                        double tpyi = tpy + (tpy2 - tpy) / (double)subSegments * s;
                                        double tpzi = tpz + (tpz2 - tpz) / (double)subSegments * s;
                                        BLParticles.PORTAL.spawn(world, tpxi, tpyi, tpzi, ParticleFactory.ParticleArgs.get().withScale(0.3F));
                                    }
                                }
                            } else {
                                double tpx = pos.x + 0.5F;
                                double tpy = pos.y + 0.05F;
                                double tpz = pos.z + 0.5F;
                                BLParticles.PORTAL.spawn(world, tpx, tpy, tpz, ParticleFactory.ParticleArgs.get().withScale(0.3F));
                            }
                        }
                    }
                    Iterator<Map.Entry<Entity, EntityTrail>> it = this.entityTrails.entrySet().iterator();
                    Map.Entry<Entity, EntityTrail> entry;
                    while(it.hasNext() && (entry = it.next()) != null) {
                        EntityTrail trail = entry.getValue();
                        if(trail.entity == null || trail.entity.isDead || !entityList.contains(entry.getKey())) {
                            it.remove();
                        }
                    }
                } else {
                    this.entityTrails.clear();
                }

                if(ElixirEffectRegistry.EFFECT_SWIFTARM.isActive(player)) {
                    if(Minecraft.getMinecraft().gameSettings.keyBindAttack.isKeyDown() && !player.isActiveItemStackBlocking()) {
                        try {
                            RayTraceResult target = Minecraft.getMinecraft().objectMouseOver;
                            if(target == null || target.entityHit != null || target.typeOfHit == RayTraceResult.Type.MISS) {
                                Minecraft.getMinecraft().clickMouse();
                            } else if(target != null) {
                                if(!player.isSwingInProgress) {
                                    Minecraft.getMinecraft().sendClickBlockToController(true);
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                this.entityTrails.clear();
            }
            
			if(player != null) {
				updatePlayerRootboundTicks(player);
			}
        }
    }
    
    //Ewww... but only way to set player rotation before rendering?...
    @SideOnly(Side.CLIENT)
    @SubscribeEvent
    public void onFogColors(FogColors event) {
    	EntityPlayer player = Minecraft.getMinecraft().player;
    	if(player != null) {
    		NBTTagCompound nbt = player.getEntityData();
    		
	    	if(ElixirEffectRegistry.EFFECT_BASILISK.isActive(player) || ElixirEffectRegistry.EFFECT_PETRIFY.isActive(player)) {
	    		player.prevRotationPitch = player.rotationPitch = nbt.getFloat("thebetweenlands.petrify.pitch");
	    		player.prevRotationYaw = player.rotationYaw = nbt.getFloat("thebetweenlands.petrify.yaw");
	    		player.prevRotationYawHead = player.rotationYawHead = nbt.getFloat("thebetweenlands.petrify.yawHead");
			}
    	}
    }

    @SubscribeEvent
    public void onShootArrow(ArrowLooseEvent event) {
        if(event.getEntityPlayer() == Minecraft.getMinecraft().player) {
            ArrowPredictionRenderer.setRandomYawPitch();
        }
    }

    @SideOnly(Side.CLIENT)
    @SubscribeEvent
    public void onRenderWorld(RenderWorldLastEvent event) {
        EntityPlayer player = Minecraft.getMinecraft().player;
        if(player != null && player.world != null) {
            if(ElixirEffectRegistry.EFFECT_SAGITTARIUS.isActive(player)) {
                ArrowPredictionRenderer.render(Math.min((ElixirEffectRegistry.EFFECT_SAGITTARIUS.getStrength(player) + 1) / 3.0F, 1.0F));
            }
        }
    }

	@SideOnly(Side.CLIENT)
	private void updatePlayerRootboundTicks(EntityLivingBase entity) {
		NBTTagCompound nbt = entity.getEntityData();
		if(entity.getActivePotionEffect(ElixirEffectRegistry.EFFECT_SHOCKED.getPotionEffect()) != null || entity.getActivePotionEffect(ElixirEffectRegistry.ROOT_BOUND) != null || ElixirEffectRegistry.EFFECT_BASILISK.isActive(entity) || ElixirEffectRegistry.EFFECT_PETRIFY.isActive(entity)) {
			nbt.setInteger("thebetweenlands.stuckTicks", 5);
		} else {
			int rootBoundTicks = nbt.getInteger("thebetweenlands.stuckTicks");
			if(rootBoundTicks > 1) {
				nbt.setInteger("thebetweenlands.stuckTicks", rootBoundTicks - 1);
			} else {
				nbt.removeTag("thebetweenlands.stuckTicks");
			}
		}
	}
	

	@SideOnly(Side.CLIENT)
	@SubscribeEvent
	public void onFovUpdate(FOVUpdateEvent event) {
		EntityPlayer entity = event.getEntity();
		NBTTagCompound nbt = entity.getEntityData();
		
		//NBT is necessary so that FOV doesn't flicker when potion wears off .-.
		if(entity.getActivePotionEffect(ElixirEffectRegistry.EFFECT_SHOCKED.getPotionEffect()) != null || entity.getActivePotionEffect(ElixirEffectRegistry.ROOT_BOUND) != null || ElixirEffectRegistry.EFFECT_BASILISK.isActive(entity) || ElixirEffectRegistry.EFFECT_PETRIFY.isActive(entity) || nbt.hasKey("thebetweenlands.stuckTicks")) {
			event.setNewfov(1);
		}
	}
}
