package ncrashed.warp;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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

import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.packet.Packet51MapChunk;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MathHelper;
import net.minecraft.world.ChunkPosition;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;

public class ChunkCopier 
{
	public static int NOTIFY_DISTANCE = 24; // in chunks
	
	public static AxisAlignedBB findEmptyPlace(World world,  int x1, int y1, int z1, int x2, int y2, int z2, int dx, int dy, int dz)
	{
		int temp;
		if(x2 < x1)
		{
			temp = x2;
			x2 = x1; x1 = temp;
		}
		if(y2 < y1)
		{
			temp = y2;
			y2 = y1; y1 = temp;
		}
		if(z2 < z1)
		{
			temp = z2;
			z2 = z1; z1 = temp;
		}
		
		if(Math.abs(dx) <= x2-x1 && Math.abs(dy) <= y2 - y1 && Math.abs(dz) <= z2-z1) return null;
		if(y2 + dy >= 256 || y1 + dy < 0) return null;
		
		AxisAlignedBB box = AxisAlignedBB.getBoundingBox(x1+dx, y1+dy, z1+dz, x2+dx, y2+dy, z2+dz);
		System.out.println("Init place box " + box);
		do 
		{
			if(isDistEmpty(world, (int)box.minX, (int)box.minY, (int)box.minZ, (int)box.maxX, (int)box.maxY, (int)box.maxZ))
			{
				return box;
			}
			
			if(dx > 0)
				dx -= 1;
			else if(dx < 0)
				dx += 1;
			
			if(dy > 0)
				dy -= 1;
			else if(dy < 0)
				dy += 1;
			
			if(dz > 0)
				dz -= 1;
			else if(dz < 0)
				dz += 1;
			
			System.out.println("Box is not empty decreasing vector "+dx+" "+dy+" "+dz);
			box.setBounds(x1+dx, y1+dy, z1+dz, x2+dx, y2+dy, z2+dz);
			
		} while(!(Math.abs(dx) <= x2-x1 && Math.abs(dy) <= y2-y1 && Math.abs(dz) <= z2-z1));
		
		return null;
	}
	
	public static boolean isDistEmpty(World world, int x1, int y1, int z1, int x2, int y2, int z2)
	{
		int temp;
		if(x2 < x1)
		{
			temp = x2;
			x2 = x1; x1 = temp;
		}
		if(y2 < y1)
		{
			temp = y2;
			y2 = y1; y1 = temp;
		}
		if(z2 < z1)
		{
			temp = z2;
			z2 = z1; z1 = temp;
		}
		
		if(y2 > 255 || y1 < 0) return false;
		
		for(int ix = x1; ix <= x2; ++ix)
		{
			for(int iy = y1; iy <= y2; ++iy)
			{
				for(int iz = z1; iz <= z2; ++iz)
				{
					if(world.getBlockId(ix, iy, iz) != 0) return false;
				}
			}
		}
		return true;
	}
	
	public static boolean copyVolume(World world, int x1, int y1, int z1, int x2, int y2, int z2, int dx, int dy, int dz)
	{
		int temp;
		if(x2 < x1)
		{
			temp = x2;
			x2 = x1; x1 = temp;
		}
		if(y2 < y1)
		{
			temp = y2;
			y2 = y1; y1 = temp;
		}
		if(z2 < z1)
		{
			temp = z2;
			z2 = z1; z1 = temp;
		}
		
		if(Math.abs(dx) <= x2-x1 && Math.abs(dy) <= y2 - y1 && Math.abs(dz) <= z2-z1) return false;
		if(y2 + dy >= 256 || y1 + dy < 0) return false;
		
		ChunkCache sourceCache = new ChunkCache(world, x1 >> 4, z1 >> 4, x2 >> 4, z2 >> 4);
		ChunkCache distCache = new ChunkCache(world, (x1 + dx) >> 4, (z1 + dz) >> 4, (x2 + dx) >> 4, (z2 + dz) >> 4);
		
		int sourceY1 = y1;
		int sourceY2 = y2;
		int distY1 = y1 + dy;
		
		int xcopied = 0;
		int sourceX1 = x1 & 15;
		int sourceX2 = Math.min((x2 - x1) + sourceX1, 15);
		int distX1 = (x1 + dx) & 15;
		
		for(int sourceChunkX = 0; sourceChunkX < sourceCache.xsize; sourceChunkX++)
		{
			int zcopied = 0;
			int sourceZ1 = z1 & 15;
			int sourceZ2 = Math.min((z2 - z1) + sourceZ1, 15);
			int distZ1 = (z1 + dz) & 15;
			int tempXOffset = 0;
			
			for(int sourceChunkZ = 0; sourceChunkZ < sourceCache.zsize; sourceChunkZ++)
			{
				Chunk sourceChunk = sourceCache.get(sourceChunkX, sourceChunkZ);
				int distChunkX = (((x1 + dx) & 15) + xcopied) >> 4;
			    int distChunkZ = (((z1 + dz) & 15) + zcopied) >> 4;
				TempTuple tuple = distCache.copyFrom(sourceChunk, 
						sourceX1, sourceY1, sourceZ1, 
						sourceX2, sourceY2, sourceZ2, 
						distChunkX, distChunkZ, 
						distX1, distY1, distZ1);
				
				zcopied += sourceZ2-sourceZ1+1;
				distZ1 = (distZ1 + sourceZ2-sourceZ1+1) & 15; 
				sourceZ1 = 0;
				sourceZ2 = Math.min((z2 - z1) + sourceZ1 - zcopied, 15);
				tempXOffset = tuple.offsetX;
				
				//sourceChunk.generateSkylightMap();
			}
			
			xcopied += sourceX2-sourceX1+1;
			distX1 = (distX1 + sourceX2-sourceX1+1) & 15; 
			sourceX1 = 0;
			sourceX2 = Math.min((x2 - x1) + sourceX1 - xcopied, 15);
		}
		
		// teleport entities
		AxisAlignedBB box = AxisAlignedBB.getBoundingBox( (double)(x1-2), (double)(y1-2), (double)(z1-2), (double)(x2+2), (double)(y2+2), (double)(z2+2));
        List entitiesList = world.getEntitiesWithinAABB(Entity.class, box);
        if (entitiesList != null) 
        {
        	for (int i = 0; i < entitiesList.size(); i++) 
        	{
        		Entity entity = (Entity)entitiesList.get(i);
        		if (entity != null) 
        		{
        			double entOldX = entity.posX;
        			double entOldY = entity.posY; 
        			double entOldZ = entity.posZ;
        			
        			if (!(entity instanceof EntityPlayer)) 
        			{
        				Chunk entityChunk;
			            if (entity.addedToChunk) 
			            {
			            	entityChunk = world.getChunkFromChunkCoords(entity.chunkCoordX << 4, entity.chunkCoordZ << 4);
			             	if (entityChunk != null) 
			             	{
			             		entity.addedToChunk = false; entityChunk.removeEntityAtIndex(entity, entity.chunkCoordY);
			             	}
			             }
			             
			             entity.setPosition(entOldX + (double)(dx), entOldY + (double)(dy)+0.1D, entOldZ + (double)(dz));
			             entityChunk = world.getChunkFromChunkCoords(MathHelper.floor_double(entity.posX / 16.0D), MathHelper.floor_double(entity.posZ / 16.0D));
			             
			             if (entityChunk != null) 
			             {
			            	 entityChunk.addEntity(entity); entity.addedToChunk = true;
			             } else entity.addedToChunk = false;
        			} else
        			{
        				EntityPlayerMP playermp = (EntityPlayerMP)entity;
        				playermp.playerNetServerHandler.setPlayerLocation(playermp.posX + dx, playermp.posY + dy + 0.6d, playermp.posZ + dz, playermp.rotationYaw, playermp.rotationPitch);
        			}
        		}
        	}
        }
        
		return true;
	}
	
	protected static class TempTuple
	{
		int offsetX, offsetZ;
		
		public TempTuple(int x, int z)
		{
			offsetX = x;
			offsetZ = z;
		}
	}
	
	protected static class ChunkCache
	{
		private World world;
		private Chunk[][] chunks;
		//public int xoffset, zoffset;
		public int xsize, zsize;
		
		public ChunkCache(World world, int x1, int z1, int x2, int z2) // chunk coords
		{
			chunks = new Chunk[x2-x1+1][z2-z1+1];
			this.world = world;
			
			for(int i = x1; i <= x2; i++)
			{	
				for(int j = z1; j <= z2; j++)
				{
					chunks[i-x1][j-z1] = world.getChunkFromChunkCoords(i, j);
				}
			}
			
			xsize = x2-x1+1;
			zsize = z2-z1+1;
		}
		
		public Chunk get(int x, int z)
		{
			if(x > xsize || z > zsize) return null;
			
			return chunks[x][z];
		}
		
		public TempTuple copyFrom(Chunk sourceChunk, int x1, int y1, int z1, int x2, int y2, int z2, 
				int chunkX, int chunkZ, int xoffset, int yoffset, int zoffset)
		{
			int temp;
			if(x2 < x1)
			{
				temp = x2;
				x2 = x1; x1 = temp;
			}
			if(y2 < y1)
			{
				temp = y2;
				y2 = y1; y1 = temp;
			}
			if(z2 < z1)
			{
				temp = z2;
				z2 = z1; z1 = temp;
			}
			
			if(xoffset + x2-x1 < 16 && zoffset + z2-z1 < 16) // case 1 : 1 chunk
			{
				Chunk distChunk = get(chunkX, chunkZ);
				if(distChunk == null) return new TempTuple(0, 0);
				
				copyWithinChunk(sourceChunk, distChunk, x1, y1, z1, x2, y2, z2, xoffset, yoffset, zoffset);
				
				return new TempTuple(0, 0);
			} else if(xoffset + x2-x1 >= 16 && zoffset + z2-z1 < 16) // case 2 : 2 chunks
			{
				Chunk distChunk1 = get(chunkX, chunkZ);
				if(distChunk1 == null) return new TempTuple(xoffset + x2-x1 - 15, 0);
				Chunk distChunk2 = get(chunkX+1, chunkZ);
				if(distChunk2 == null) return new TempTuple(xoffset + x2-x1 - 15, 0);
				
				int xslice = x1 + (16 - xoffset)-1;
				copyWithinChunk(sourceChunk, distChunk1, x1, y1, z1, xslice, y2, z2, xoffset, yoffset, zoffset);
				copyWithinChunk(sourceChunk, distChunk2, xslice+1, y1, z1, x2, y2, z2, 0, yoffset, zoffset);
				
				return new TempTuple(xoffset + x2-x1 - 15, 0);
			} else if(xoffset + x2-x1 < 16 && zoffset + z2-z1 >= 16) // case 3 : 2 chunks
			{
				Chunk distChunk1 = get(chunkX, chunkZ);
				if(distChunk1 == null) return new TempTuple(0, zoffset + z2-z1 - 15);
				Chunk distChunk2 = get(chunkX, chunkZ+1);
				if(distChunk2 == null) return new TempTuple(0, zoffset + z2-z1 - 15);
				
				int zslize = z1 + (16 - zoffset)-1;
				copyWithinChunk(sourceChunk, distChunk1, x1, y1, z1, x2, y2, zslize, xoffset, yoffset, zoffset);
				copyWithinChunk(sourceChunk, distChunk2, x1, y1, zslize+1, x2, y2, z2, xoffset, yoffset, 0);
				
				return new TempTuple(0, zoffset + z2-z1 - 15);
			} else // case 4 : 4 chunks
			{
				Chunk distChunk = get(chunkX, chunkZ);
				if(distChunk == null) return new TempTuple(xoffset + x2-x1 - 15, zoffset + z2-z1 - 15);
				Chunk distChunkX = get(chunkX+1, chunkZ);
				if(distChunkX == null) return new TempTuple(xoffset + x2-x1 - 15, zoffset + z2-z1 - 15);
				Chunk distChunkZ = get(chunkX, chunkZ+1);
				if(distChunkZ == null) return new TempTuple(xoffset + x2-x1 - 15, zoffset + z2-z1 - 15);
				Chunk distChunkXZ = get(chunkX+1, chunkZ+1);
				
				int xslice = x1 + (16 - xoffset)-1;
				int zslize = z1 + (16 - zoffset)-1;
				copyWithinChunk(sourceChunk, distChunk,   x1, y1, z1, xslice, y2, zslize, xoffset, yoffset, zoffset);
				copyWithinChunk(sourceChunk, distChunkX,  xslice+1, y1, z1, x2, y2, zslize, 0, yoffset, zoffset);
				copyWithinChunk(sourceChunk, distChunkZ,  x1, y1, zslize+1, xslice, y2, z2, xoffset, yoffset, 0);
				copyWithinChunk(sourceChunk, distChunkXZ, xslice+1, y1, zslize+1, x2, y2, z2, 0, yoffset, 0);
				
				return new TempTuple(xoffset + x2-x1 - 15, zoffset + z2-z1 - 15);
			}
		}
		
		public void copyWithinChunk(Chunk sourceChunk, Chunk distChunk, int x1, int y1, int z1, int x2, int y2, int z2, int xoffset, int yoffset, int zoffset)
		{
			boolean needUpdateSky = false;
			
			int chunkSourceUpdateFlag = 0;
			int chunkDistUpdateFlag = 0;
			
			int ydistFull = yoffset;
			for(int yindex = y1 >> 4; yindex <= y2 >> 4; ++yindex)
			{
				chunkSourceUpdateFlag |= 1 << yindex;
				ExtendedBlockStorage sourceStorage = sourceChunk.getBlockStorageArray()[yindex];
				if(sourceStorage == null)
				{
					sourceStorage = sourceChunk.getBlockStorageArray()[yindex] = new ExtendedBlockStorage(yindex << 4, !world.provider.hasNoSky);
				}
				
				int ystart = ((yindex == (y1 >>4)) ? (y1 & 15) : 0);
				int yend = ((yindex == (y2 >> 4)) ? (y2 & 15) : 15);
				
				
				for(int ysource = ystart; ysource <= yend; ++ysource)
				{
					int ysourceFull = (yindex << 4) + ysource;
					
					int ydistIndex = ydistFull >> 4;
					int ydist = ydistFull & 15;
					ExtendedBlockStorage distStorage = distChunk.getBlockStorageArray()[ydistIndex];
					boolean newStorage = false;
					
					chunkDistUpdateFlag |= 1 << ydistIndex;
					
					if(distStorage == null)
					{
						distStorage = distChunk.getBlockStorageArray()[ydistIndex] = new ExtendedBlockStorage(ydistIndex << 4, !world.provider.hasNoSky);
						newStorage = true;
					}
					
					for(int x = x1; x <= x2; x++)
					{
						for(int z = z1; z <= z2; z++)
						{
							int oldHeightMap = distChunk.heightMap[z << 4 | x];
							needUpdateSky = needUpdateSky || newStorage && (ydist <= oldHeightMap);
							int xdist = xoffset + x-x1;
							int zdist = zoffset + z-z1;
							int blockID = sourceStorage.getExtBlockID(x, ysource, z);
							int metadata = sourceStorage.getExtBlockMetadata(x, ysource, z);
							
							if(blockID == 0) continue;
							
							distStorage.setExtBlockID(xdist, ydist, zdist, blockID);
							
							if(distStorage.getExtBlockID(xdist, ydist, zdist) == blockID)
							{
								distStorage.setExtBlockMetadata(xdist, ydist, zdist, metadata);
								sourceStorage.setExtBlockID(x, ysource, z, 0);
								sourceStorage.setExtBlockMetadata(x, ysource, z, -1);
								
								// light update
				                if(!needUpdateSky)
				                {
				                	if(x == x1)
				                	{
				                		sourceChunk.relightBlock(x, ysource, z);
				                	} else if(x == x2)
				                	{
				                		sourceChunk.relightBlock(x, ysource, z);
				                	}
				                	
				                	if(z == z1)
				                	{
				                		sourceChunk.relightBlock(x, ysource, z);
				                	} else if(z == z2)
				                	{
				                		sourceChunk.relightBlock(x, ysource, z);
				                	}
				                	
				                    if (distChunk.getBlockLightOpacity(xdist, ydist, zdist) > 0)
				                    {
				                        if (ydistFull >= oldHeightMap)
				                        {
				                        	distChunk.relightBlock(xdist, ydistFull+1, zdist);
				                        }
				                    }
				                    else if (ydistFull == oldHeightMap - 1)
				                    {
				                    	distChunk.relightBlock(xdist, ydistFull, zdist);
				                    }

				                    distChunk.propagateSkylightOcclusion(xdist, zdist);
				                }
				                
				                // update tile entities and clear old block
			                    if (Block.blocksList[blockID] != null && Block.blocksList[blockID].hasTileEntity(metadata))
			                    {
			                    	TileEntity tileEnt = removeChunkBlockTileEntity(sourceChunk, x, ysourceFull, z);
			                    	
			                        if (tileEnt == null)
			                        {
			                            tileEnt = Block.blocksList[blockID].createTileEntity(world, metadata);
			                            world.setBlockTileEntity(xdist, ydistFull, zdist, tileEnt);
			                        }

			                        if (tileEnt != null)
			                        {
			                        	setChunkBlockTileEntity(distChunk, xdist, ydistFull, zdist, tileEnt, blockID, metadata);
			                        }
			                    }
			                    
							}
						}
					}
					
					++ydistFull;
				}
				
			}
			
            // notify clients that chunk changed
            List notifyList = world.playerEntities;
            Iterator notifyIter = notifyList.iterator();
            while(notifyIter.hasNext())
            {
            	EntityPlayerMP playermp = (EntityPlayerMP)notifyIter.next();
            	if(playermp == null) continue;

            	if( Math.abs((distChunk.xPosition << 4) - playermp.posX) < (NOTIFY_DISTANCE << 4) && 
            			Math.abs((distChunk.zPosition << 4) - playermp.posZ) < (NOTIFY_DISTANCE << 4))
            				playermp.playerNetServerHandler.sendPacketToPlayer(new Packet51MapChunk(distChunk, false, chunkDistUpdateFlag));
            	
            	if( Math.abs((sourceChunk.xPosition << 4) - playermp.posX) < (NOTIFY_DISTANCE << 4) && 
            			Math.abs((sourceChunk.zPosition << 4) - playermp.posZ) < (NOTIFY_DISTANCE << 4))            	
            				playermp.playerNetServerHandler.sendPacketToPlayer(new Packet51MapChunk(sourceChunk, false, chunkSourceUpdateFlag));
            }
            
            if (needUpdateSky)
            {
                distChunk.generateSkylightMap();
            }
		}
		
	    protected void setChunkBlockTileEntity(Chunk chunk, int x, int y, int z, TileEntity tileEntity, int blockID, int metadata) // x, y, z is chunk coords
	    {
	        ChunkPosition chunkPos = new ChunkPosition(x, y, z);
	        tileEntity.setWorldObj(world);
	        tileEntity.xCoord = chunk.xPosition * 16 + x;
	        tileEntity.yCoord = y;
	        tileEntity.zCoord = chunk.zPosition * 16 + z;

	        Block block = Block.blocksList[blockID];
	        /*if (block != null && block.hasTileEntity(metadata))
	          {
	            TileEntity old = (TileEntity)chunk.chunkTileEntityMap.get(chunkPos);
	            if (old != null)
	            {
	                old.invalidate();
	            }
	            tileEntity.validate();*/
	            chunk.chunkTileEntityMap.put(chunkPos, tileEntity);
	            tileEntity.worldObj.markBlockForUpdate(tileEntity.xCoord, tileEntity.yCoord, tileEntity.zCoord);
	            ModHelper.rp2UpdateLighting(tileEntity);
	       // }
	    }
	    
	    protected TileEntity removeChunkBlockTileEntity(Chunk chunk, int x, int y, int z) // x, y, z is chunk coords
	    {
	        ChunkPosition chunkPos = new ChunkPosition(x, y, z);

	        //if (chunk.isChunkLoaded)
	        //{
	        	return (TileEntity)chunk.chunkTileEntityMap.remove(chunkPos);
	        //}
	    }
	}
}
