package ncrashed.warp.cores;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

import ncrashed.warp.CoreRegistry;
import ncrashed.warp.IntVec3;
import ncrashed.warp.ModHelper;
import ncrashed.warp.WarpMod;
import ncrashed.warp.api.IBigStructure;
import ncrashed.warp.api.ICoreAction;
import ncrashed.warp.api.ICoreStructure;
import ncrashed.warp.api.IWarpCore;
import ncrashed.warp.api.volume.IVolume;
import ncrashed.warp.api.volume.SimpleVolume;
import ncrashed.warp.cores.actions.BeaconSummon;
import ncrashed.warp.cores.actions.BeaconWarp;
import ncrashed.warp.cores.actions.CrewSummon;
import ncrashed.warp.cores.actions.GetCoreX;
import ncrashed.warp.cores.actions.GetCoreY;
import ncrashed.warp.cores.actions.GetCoreZ;
import ncrashed.warp.cores.actions.GetMFSUEnergy;
import ncrashed.warp.cores.actions.LongWarp;
import ncrashed.warp.cores.actions.ShortWarp;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeDirection;

public class TileLegacyCore extends TileEntity implements IWarpCore
{
	public final static int SUNKING_READY = 1;
	public final static int MARKER_CHECK_DELAY = 300;
	public final static int CONSTRUCTION_CHECK_DELAY = 200;
	
	public IVolume coreVolume;
	public ICoreStructure coreStructure;
	public List<ICoreAction> coreActions;
	public List<String> playersCrew;
	
	private int id = -1;
	private boolean registered = false;
	private int markerTicker = MARKER_CHECK_DELAY;
	private int constructionTicker = 0;
	private int cooldownTicker = 0;
	
	private boolean volumed = false;
	private boolean constructed = false;
	private boolean ready = false;
	private boolean wasUnloaded = false;
	private boolean lastActionSuccess = false;
	
	public TileLegacyCore()
	{
		this.coreVolume = new SimpleVolume(WarpMod.mConfig.mMarkerBlockID);
		this.coreStructure = new LegacyCoreStructure();
		this.coreActions = new ArrayList<ICoreAction>();
		this.playersCrew = new CopyOnWriteArrayList<String>();
		
		registerActions();
	}
	
	public TileLegacyCore(IVolume volumeProvider, ICoreStructure structure, int meta)
	{
		this.coreVolume = volumeProvider;
		this.coreStructure = structure;
		this.coreActions = new ArrayList<ICoreAction>();
		this.playersCrew = new CopyOnWriteArrayList<String>();
		
		registerActions();
	}
	
	protected void registerActions()
	{
		registerAction(new ShortWarp());
		registerAction(new LongWarp());
		registerAction(new CrewSummon());
		registerAction(new GetCoreX());
		registerAction(new GetCoreY());
		registerAction(new GetCoreZ());
		registerAction(new GetMFSUEnergy());
		registerAction(new BeaconWarp());
		registerAction(new BeaconSummon());
	}
	
	@Override
	public void updateEntity() 
	{
		Side side = FMLCommonHandler.instance().getEffectiveSide();
		if(side == Side.SERVER)
		{
			if(id == -1)
			{
				WarpMod.mRegister.registerNewCore(this);
				registered = true;
			} else if(!registered)
			{
				WarpMod.mRegister.registerOldCore(this);
				registered = true;
			}

			if(++markerTicker > MARKER_CHECK_DELAY)
			{
				checkMarkers();
				markerTicker = 0;
			}
			
			if(++constructionTicker > CONSTRUCTION_CHECK_DELAY)
			{
				checkConstruction();
				
				constructionTicker = 0;
			}
			
			if(volumed && constructed)
			{
				ModHelper.ic2SunkEnergy(worldObj, 
						xCoord + coreStructure.getStorageX() - coreStructure.getCoreX(), 
						yCoord + coreStructure.getStorageY() - coreStructure.getCoreY(), 
						zCoord + coreStructure.getStorageZ() - coreStructure.getCoreZ(), 
						SUNKING_READY);
			}
			
			if(cooldownTicker > 0)
			{
				cooldownTicker -= 1;
				ready = false;
				coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 1, 14, false);
			} else
			{
				ready = volumed && constructed;
				coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 1, 14, ready);
			}
		}
	}
	
	public void checkConstruction()
	{
		//System.out.println("Check Construction");
		
		if(coreStructure.checkStructure(worldObj, xCoord, yCoord, zCoord, coreStructure.getCoreX(), coreStructure.getCoreY(), coreStructure.getCoreZ()))
		{
			if(!constructed)
			{
				constructed = true;
				sendConstructedEvent();
			}
			//System.out.println("Structure valid!");
			coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 0, 14, true);
			checkMarkers();
			
		} else
		{
			if(constructed)
			{
				constructed = false;
				sendConstructedEvent();
			}
			//System.out.println("Structure invalid!");
			coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 0, 14, false);
		}	
		
		if(coreVolume.isValidVolume(worldObj, xCoord, yCoord, zCoord, IVolume.MAX_VOLUME_SIZE, 6, 6, 6) &&
				coreStructure.checkCounters(worldObj, xCoord, yCoord, zCoord))
		{
			//System.out.println("Volume valid!");
			volumed = true;
			coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 0, 15, true);
		} else
		{
			//System.out.println("Volume invalid!");
			volumed = false;
			coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 0, 15, false);
		}
	}
	
	public void checkMarkers()
	{
		//System.out.println("Check markers");
		boolean flag = false;
		List<IntVec3> markers = coreVolume.getMarkers();

		ListIterator<IntVec3> iter = markers.listIterator();
		while(iter.hasNext())
		{
			IntVec3 vec = iter.next();
			TileMarker ent = (TileMarker)worldObj.getBlockTileEntity(vec.x+xCoord, vec.y+yCoord, vec.z+zCoord);
			if(ent == null || ent.isInvalid())
			{
				//System.out.println("Removing marker at "+vec.x + " " + vec.y + " " + vec.z);
				coreVolume.removeMarker(worldObj, vec.x, vec.y, vec.z, 0, 0, 0);
				flag = true;
			}
		}

		if(flag)
		{
			checkConstruction();
		}
	}
	
	/*@Override
	@SideOnly(Side.CLIENT)
	public void receiveClientEvent(int eventID, int eventPar) 
	{
		if(eventID == CONSTRUCTED_EVENT)
		{
			constructed = eventPar == 1;
		}
	}*/
	
	private void sendConstructedEvent()
	{
		//worldObj.addBlockEvent(xCoord, yCoord, zCoord, WarpMod.mLegacyCoreBlock.blockID, CONSTRUCTED_EVENT, constructed ? 1 : 0);
	}
	
    /**
     * Reads a tile entity from NBT.
     */
	@Override
    public void readFromNBT(NBTTagCompound tagCompound)
    {
        super.readFromNBT(tagCompound);
        Side side = FMLCommonHandler.instance().getEffectiveSide();
        if(side == Side.SERVER)
        {
	        NBTTagList tagList = tagCompound.getTagList("Markers");
	        
	        List<IntVec3> list = new ArrayList<IntVec3>();
	        for(int i = 0; i < tagList.tagCount(); ++i)
	        {
	        	NBTTagCompound tag = (NBTTagCompound)tagList.tagAt(i);
	        	
	        	int[] coords = tag.getIntArray("Marker");
	        	if(coords == null || coords.length != 3) continue;
	        	
	        	list.add(new IntVec3(coords[0], coords[1], coords[2]));
	        }
	        
	        coreVolume.setupVolume(list);
	        
	        id = tagCompound.getInteger("CoreID");
	        cooldownTicker = tagCompound.getInteger("Cooldown");
	        
	        tagList = tagCompound.getTagList("Crew");
	        playersCrew.clear();
	        for(int i = 0; i < tagList.tagCount(); ++i)
	        {
	        	NBTTagCompound tag = (NBTTagCompound)tagList.tagAt(i);
	        	playersCrew.add(tag.getString("Name"));
	        }
        }
    }

    /**
     * Writes a tile entity to NBT.
     */
	@Override
    public void writeToNBT(NBTTagCompound tagCompound)
    {
        super.writeToNBT(tagCompound);
        
        Side side = FMLCommonHandler.instance().getEffectiveSide();
        if(side == Side.SERVER)
        {
	        NBTTagList tagList = new NBTTagList();
	        List<IntVec3> list = coreVolume.getMarkers();
	        ListIterator<IntVec3> iter = list.listIterator();
	        while(iter.hasNext())
	        {
	        	IntVec3 markerVec = iter.next();
	        	NBTTagCompound tag = new NBTTagCompound();
	        	tag.setIntArray("Marker", new int[]{markerVec.x, markerVec.y, markerVec.z});
	        	tagList.appendTag(tag);
	        }
	        tagCompound.setTag("Markers", tagList);
	        
	        tagCompound.setInteger("CoreID", id);
	        tagCompound.setInteger("Cooldown", cooldownTicker);
	        
	        ListIterator<String> nameIter = playersCrew.listIterator();
	        tagList = new NBTTagList();
	        while(nameIter.hasNext())
	        {
	        	String playerName = nameIter.next();
	        	NBTTagCompound tag = new NBTTagCompound();
	        	tag.setString("Name", playerName);
	        	tagList.appendTag(tag);
	        }
	        tagCompound.setTag("Crew", tagList);
        }
    }

	@Override
	public int getCoreID()
	{
		return id;
	}
	
	@Override
	public void setCoreID(int id)
	{
		this.id = id;
	}
	
	@Override
	public boolean isConstructed() 
	{
		return constructed;
	}

	@Override
	public boolean isReady()
	{
		return ready;
	}

	@Override
	public boolean isVolumed()
	{
		return volumed;
	}
	
	@Override
	public int getCooldownTicks()
	{
		return cooldownTicker;
	}
	
	@Override
	public int getCooldownSecs()
	{
		return cooldownTicker / ICoreAction.TICKS_PER_SECOND;
	}
	
	@Override
	public ICoreStructure getCoreStructure() 
	{
		return coreStructure;
	}

	@Override
	public IVolume getCoreVolume() 
	{
		return coreVolume;
	}
	
	@Override
	public boolean activate()
	{
		if(!ready) return false;
		
		if(!constructed || !volumed) 
		{
			checkConstruction();
			if(!constructed || !volumed) return false; 
		}
		
		TileEntity multCounter = coreStructure.getMultCounter(worldObj, xCoord, yCoord, zCoord);
		TileEntity northCounter = coreStructure.getSidedCounter(worldObj, xCoord, yCoord, zCoord, ForgeDirection.NORTH);
		TileEntity southCounter = coreStructure.getSidedCounter(worldObj, xCoord, yCoord, zCoord, ForgeDirection.SOUTH);
		TileEntity westCounter = coreStructure.getSidedCounter(worldObj, xCoord, yCoord, zCoord, ForgeDirection.WEST);
		TileEntity eastCounter = coreStructure.getSidedCounter(worldObj, xCoord, yCoord, zCoord, ForgeDirection.EAST);
		TileEntity upCounter = coreStructure.getSidedCounter(worldObj, xCoord, yCoord, zCoord, ForgeDirection.UP);
		TileEntity downCounter = coreStructure.getSidedCounter(worldObj, xCoord, yCoord, zCoord, ForgeDirection.DOWN);
		
		int mult = ModHelper.rp2GetCounterValue(multCounter);
		int north = Math.min(ModHelper.rp2GetCounterValue(northCounter), 63);
		int south = Math.min(ModHelper.rp2GetCounterValue(southCounter), 63);
		int west = Math.min(ModHelper.rp2GetCounterValue(westCounter), 63);
		int east = Math.min(ModHelper.rp2GetCounterValue(eastCounter), 63);
		int up = Math.min(ModHelper.rp2GetCounterValue(upCounter), 63);
		int down = Math.min(ModHelper.rp2GetCounterValue(downCounter), 63);
		
		if(mult == -1) return false;
		
		coreStructure.zeroAllExpanders(worldObj, xCoord, yCoord, zCoord);
		
		Iterator<ICoreAction> iter = coreActions.iterator();
		while(iter.hasNext())
		{
			ICoreAction nextAction = iter.next();
			if(nextAction.getLowMultCounter() <= mult && nextAction.getHighMultCounter() >= mult)
			{
				int energy = nextAction.getRequiredEnergy(this, mult, north, south, west, east, up, down);
				
				if(energy <= ModHelper.ic2GetEnergy(worldObj, 
								xCoord + coreStructure.getStorageX() - coreStructure.getCoreX(), 
								yCoord + coreStructure.getStorageY() - coreStructure.getCoreY(), 
								zCoord + coreStructure.getStorageZ() - coreStructure.getCoreZ()
								))
				{
					ModHelper.ic2SunkEnergy(worldObj,								
							xCoord + coreStructure.getStorageX() - coreStructure.getCoreX(), 
							yCoord + coreStructure.getStorageY() - coreStructure.getCoreY(), 
							zCoord + coreStructure.getStorageZ() - coreStructure.getCoreZ(), 
							energy);
					
					lastActionSuccess = nextAction.performAction(this, mult, north, south, west, east, up, down);
					
					if(lastActionSuccess)
					{
						cooldownTicker = nextAction.getSuccessCooldown(this, mult, north, south, west, east, up, down)*ICoreAction.TICKS_PER_SECOND;
					} else
					{
						cooldownTicker = nextAction.getFailCooldown(this, mult, north, south, west, east, up, down)*ICoreAction.TICKS_PER_SECOND;
					}
					
					int minimumTicks = nextAction.getMinimumCooldown(this, mult, north, south, west, east, up, down)*ICoreAction.TICKS_PER_SECOND;
					if(cooldownTicker < minimumTicks)
					{
						cooldownTicker = minimumTicks;
					}
					
					System.out.println("Cooldown ticks: "+cooldownTicker);
					ready = false;
					if(nextAction.printActionResult())
					{
						coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, this.getErrorExpanderID(), this.getResultBit(), !lastActionSuccess);
					}
					
					List<IWarpCore> cores = coreVolume.getAnotherCores(worldObj, xCoord, yCoord, zCoord);
					for(int i = 0; i<cores.size(); i++)
					{
						cores.get(i).addCooldownTicks(cooldownTicker/2);
					}
					
					return lastActionSuccess;
				} else
				{
					ModHelper.ic2SunkEnergy(worldObj,								
							xCoord + coreStructure.getStorageX() - coreStructure.getCoreX(), 
							yCoord + coreStructure.getStorageY() - coreStructure.getCoreY(), 
							zCoord + coreStructure.getStorageZ() - coreStructure.getCoreZ(), 
							energy);
					
					cooldownTicker = nextAction.getFailCooldown(this, mult, north, south, west, east, up, down)*ICoreAction.TICKS_PER_SECOND;
					ready = false;
					lastActionSuccess = false;
					if(nextAction.printActionResult())
					{
						coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, this.getErrorExpanderID(), this.getNotEnoughEnergyBitError(), true);
						coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, this.getErrorExpanderID(), this.getResultBit(), true);
					}
					return lastActionSuccess;
				}
			}
		}
		lastActionSuccess = false;
		return false;
	}
	
	@Override
	public void registerAction(ICoreAction action)
	{
		Iterator<ICoreAction> iter = coreActions.iterator();
		while(iter.hasNext())
		{
			ICoreAction nextAction = iter.next();
			if(!(nextAction.getLowMultCounter() > action.getHighMultCounter() || nextAction.getHighMultCounter() < action.getLowMultCounter()) )
			{
				throw new Error("Actions intervals intersecs: [" +action.getLowMultCounter() + "," + action.getHighMultCounter() + "] and ["+ +nextAction.getLowMultCounter() + "," + nextAction.getHighMultCounter() + "]");
			}
		}
		coreActions.add(action);
	}
	
	@Override
	public World getCoreWorld()
	{
		return worldObj;
	}

	@Override
	public int getCoreX() 
	{
		return xCoord;
	}

	@Override
	public int getCoreY() 
	{
		return yCoord;
	}

	@Override
	public int getCoreZ() 
	{
		return zCoord;
	}
	
	@Override
	public void onChunkUnload()
	{
		wasUnloaded = true;
		volumed = false;
		constructed = false;
	}

	@Override
	public boolean addPlayerCrew(EntityPlayerMP player) 
	{
		if(player == null || player.username.isEmpty()) return false;
		
		ListIterator<String> iter = playersCrew.listIterator();
		while(iter.hasNext())
		{
			if(player.username.equals(iter.next()))
			{
				return false;
			}
		}
		
		playersCrew.add(player.username);
		return true;
	}

	@Override
	public boolean isPlayerCrew(EntityPlayerMP player) 
	{
		if(player == null || player.username.isEmpty()) return false;
		
		ListIterator<String> iter = playersCrew.listIterator();
		while(iter.hasNext())
		{
			if(player.username.equals(iter.next()))
			{
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean exludePlayerCrew(EntityPlayerMP player) 
	{
		if(player == null || player.username.isEmpty()) return false;
		
		ListIterator<String> iter = playersCrew.listIterator();
		while(iter.hasNext())
		{
			if(player.username.equals(iter.next()))
			{
				iter.remove();
				return true;
			}
		}
		return false;
	}

	@Override
	public int getDamageOnCrewSuccess() 
	{
		return 4;
	}

	@Override
	public int getDamageOnCrewFail() 
	{
		return 1;
	}

	@Override
	public List<String> getCrew() 
	{
		return playersCrew;
	}

	@Override
	public List<EntityPlayerMP> getCrewInGame() 
	{
		if(FMLCommonHandler.instance().getEffectiveSide() == Side.CLIENT)
			throw new Error("getCrewInGame only server side method!");
		
		List<EntityPlayerMP> returnList = new ArrayList<EntityPlayerMP>();
		ListIterator<String> iter = playersCrew.listIterator();
		while(iter.hasNext())
		{
			EntityPlayerMP player = (EntityPlayerMP) worldObj.getPlayerEntityByName(iter.next());
			if(player != null)
			{
				returnList.add(player);
			}
		}
		
		return returnList;
	}

	@Override
	public List<EntityPlayerMP> getCrewOnBoard() 
	{
		if(FMLCommonHandler.instance().getEffectiveSide() == Side.CLIENT)
			throw new Error("getCrewOnBoard only server side method!");
		
		List<EntityPlayerMP> returnList = new ArrayList<EntityPlayerMP>();
		ListIterator<String> iter = playersCrew.listIterator();
		while(iter.hasNext())
		{
			EntityPlayerMP player = (EntityPlayerMP) worldObj.getPlayerEntityByName(iter.next());
			
			if(player != null && coreVolume.isEntityInside(worldObj, xCoord, yCoord, zCoord, player))
			{
				returnList.add(player);
			}
		}
		
		return returnList;
	}
	
	@Override
	public List<EntityPlayerMP> getCrewNotOnBoard() 
	{
		if(FMLCommonHandler.instance().getEffectiveSide() == Side.CLIENT)
			throw new Error("getCrewNotOnBoard only server side method!");
		
		List<EntityPlayerMP> returnList = new ArrayList<EntityPlayerMP>();
		ListIterator<String> iter = playersCrew.listIterator();
		while(iter.hasNext())
		{
			EntityPlayerMP player = (EntityPlayerMP) worldObj.getPlayerEntityByName(iter.next());
			
			if(player != null && !coreVolume.isEntityInside(worldObj, xCoord, yCoord, zCoord, player))
			{
				returnList.add(player);
			}
		}
		
		return returnList;
	}
	
	@Override
	public void restartCheckingTickers()
	{
		markerTicker = 0;
		constructionTicker = 0;
	}

	@Override
	public boolean isLastActionFailed() 
	{
		return !lastActionSuccess;
	}

	@Override
	public int getErrorExpanderID() 
	{
		return 1;
	}

	@Override
	public int getNotEnoughEnergyBitError() 
	{
		return 0;
	}

	@Override
	public int getResultBit() 
	{
		return 13;
	}

	@Override
	public void addCooldownTicks(int ticks) 
	{
		cooldownTicker += ticks;
		
		if(cooldownTicker > 0)
		{
			ready = false;
			coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 1, 14, false);
		} else
		{
			ready = true;
			coreStructure.setExpander(worldObj, xCoord, yCoord, zCoord, 1, 14, volumed && constructed);
		}
	}
}
