package com.plusminus.craft;

import java.io.File;
import java.util.ArrayList;

import com.plusminus.craft.dtf.CompoundTag;
import com.plusminus.craft.dtf.DTFReader;
import com.plusminus.craft.dtf.DoubleTag;
import com.plusminus.craft.dtf.FloatTag;
import com.plusminus.craft.dtf.IntTag;
import com.plusminus.craft.dtf.ListTag;
import com.plusminus.craft.dtf.Tag;

import com.plusminus.craft.MineCraftConstants.BLOCK;

/***
 * A Minecraft level 
 * @author Vincent
 */
public class MinecraftLevel {

	//public static int LEVELDATA_SIZE = 256;
	public static int LEVELDATA_SIZE = 128;
	public static int LEVELDATA_OFFSET = Integer.MAX_VALUE/2;
	public Chunk[][] levelData;
	
	private WorldInfo world;
	
	// This var holds the index of the player position we've most recently picked
	private ArrayList<CameraPreset> playerPositions;
	private int playerPos_idx;
	private int spawnPoint_idx;
	public BLOCK[] HIGHLIGHT_ORES;
	
	public Texture minecraftTexture;
	public Texture paintingTexture;
	public Texture portalTexture;

	/***
	 * Create a minecraftLevel from the given world
	 * @param world
	 */
	public MinecraftLevel(WorldInfo world, Texture minecraftTexture, Texture paintingTexture, Texture portalTexture, BLOCK[] HIGHLIGHT_ORES) {
		this.world = world;
		this.minecraftTexture = minecraftTexture;
		this.paintingTexture = paintingTexture;
		this.portalTexture = portalTexture;
		this.HIGHLIGHT_ORES = HIGHLIGHT_ORES;
		
		this.levelData = new Chunk[LEVELDATA_SIZE][LEVELDATA_SIZE];
		
		File levelFile = world.getLevelDatFile();
		
		CompoundTag levelData = (CompoundTag) DTFReader.readDTFFile(levelFile);
		
		//	System.out.println(levelData.toString());
		
		this.playerPositions = new ArrayList<CameraPreset>();
		this.playerPos_idx = -1;
		this.spawnPoint_idx = -1;
		
		CompoundTag levelDataData = (CompoundTag) levelData.getTagWithName("Data");
		CompoundTag levelPlayerData = (CompoundTag) levelDataData.getTagWithName("Player");
		if(levelPlayerData != null) {
			
			// Figure out what dimension the player's in.  If it matches, move our camera there.
			IntTag playerDim = (IntTag) levelPlayerData.getTagWithName("Dimension");
			if ((playerDim == null && !world.isNether()) || (playerDim != null && ((playerDim.value == 0 && !world.isNether()) || (playerDim.value == -1 && world.isNether()))))
			{
				ListTag playerPos = (ListTag) levelPlayerData .getTagWithName("Pos");
				ListTag playerRotation = (ListTag) levelPlayerData .getTagWithName("Rotation");
		
				DoubleTag posX = (DoubleTag) playerPos.value.get(0);
				DoubleTag posY = (DoubleTag) playerPos.value.get(1);
				DoubleTag posZ = (DoubleTag) playerPos.value.get(2);
				
				FloatTag rotYaw = (FloatTag) playerRotation.value.get(0);
				FloatTag rotPitch = (FloatTag) playerRotation.value.get(1);

				this.playerPositions.add(new CameraPreset(0, "Singleplayer User", new Block((int) -posX.value, (int) -posY.value, (int) -posZ.value),
						rotYaw.value, rotPitch.value));
				this.playerPos_idx = 0;
			}
		}
		
		// Get a list of MP users that can provide valid camera positions for us
		CompoundTag mpuserData;
		for (String mpusername : world.mp_players.keySet())
		{
			try
			{
				mpuserData = (CompoundTag) DTFReader.readDTFFile(world.mp_players.get(mpusername));
				
				// Skip players who aren't currently in the dimension that we're using
				// (which would be weird, since SMP doesn't support Nether properly yet)
				IntTag playerDim = (IntTag) mpuserData.getTagWithName("Dimension");
				if (playerDim == null)
				{
					if (world.isNether())
					{
						continue;
					}
				}
				else
				{
					if ((playerDim.value == 0 && world.isNether()) || (playerDim.value == -1 && !world.isNether()))
					{
						continue;
					}
				}
				
				// Pull out the data
				ListTag playerPos = (ListTag) mpuserData.getTagWithName("Pos");
				ListTag playerRotation = (ListTag) mpuserData.getTagWithName("Rotation");	
				DoubleTag posX = (DoubleTag) playerPos.value.get(0);
				DoubleTag posY = (DoubleTag) playerPos.value.get(1);
				DoubleTag posZ = (DoubleTag) playerPos.value.get(2);
				FloatTag rotYaw = (FloatTag) playerRotation.value.get(0);
				FloatTag rotPitch = (FloatTag) playerRotation.value.get(1);
				this.playerPositions.add(new CameraPreset(this.playerPositions.size(),
						mpusername, new Block((int) -posX.value, (int) -posY.value-1, (int) -posZ.value),
						rotYaw.value, rotPitch.value));
			}
			catch (Exception e)
			{
				// Just report to console and continue.
				System.out.println("Error loading position information for user " + mpusername + ": " + e.toString());
			}
		}
		
		// Set the spawn point if we're not in the Nether
		this.spawnPoint_idx = this.playerPositions.size();
		if (world.isNether())
		{
			this.playerPositions.add(new CameraPreset(this.spawnPoint_idx, "Map Center", new Block(0,-66,0), 0, 0));
		}
		else
		{
			IntTag spawnX = (IntTag) levelDataData.getTagWithName("SpawnX");
			IntTag spawnY = (IntTag) levelDataData.getTagWithName("SpawnY");
			IntTag spawnZ = (IntTag) levelDataData.getTagWithName("SpawnZ");
			this.playerPositions.add(new CameraPreset(this.spawnPoint_idx, "Spawnpoint", new Block(-spawnX.value, -spawnY.value-1, -spawnZ.value), 0, 0));
		}

		// Figure out where to set the "player" position, if we have no singleplayer user
		if (this.playerPos_idx == -1)
		{
			this.playerPos_idx = this.spawnPoint_idx;
		}
	}
	
	/***
	 * returns the spawning point for this level
	 */
	public CameraPreset getPlayerPositionIdx(int idx)
	{
		return this.playerPositions.get(idx);
	}
	
	public CameraPreset getSpawnPoint() {
		return this.getPlayerPositionIdx(this.spawnPoint_idx);
	}
	
	public CameraPreset getPlayerPosition() {
		return this.getPlayerPositionIdx(this.playerPos_idx);
	}
	
	public CameraPreset getNextPlayerPosition(CameraPreset current) {
		int next_idx = (current.idx+1) % this.playerPositions.size();
		return this.getPlayerPositionIdx(next_idx);
	}

	public CameraPreset getPrevPlayerPosition(CameraPreset current) {
		int prev_idx = current.idx - 1;
		if (prev_idx < 0)
		{
			prev_idx = this.playerPositions.size()-1;
		}
		return this.getPlayerPositionIdx(prev_idx);
	}
	
	/***
	 * correctly calculate the chunk X value given a universal coordinate
	 * @param x
	 * @return
	 */
	public int getChunkX(int x) {
		if(x<0) {
			return  -(((-x)-1) / 16)-1; // otherwise -1 and +1 would return the same chunk
		} else {
			return x / 16;
		}
	}
	
	/***
	 * correctly calculate the block X value given a universal coordinate
	 * @param x
	 * @return
	 */
	public int getBlockX(int x) {
		if(x<0) {
			return 15-(((-x)-1) % 16); // compensate for different chunk calculation
		} else {
			return x % 16;
		}
	}
	
	/***
	 * correctly calculate the chunk Z value given a universal coordinate
	 * @param z
	 * @return
	 */
	public int getChunkZ(int z) {
		if(z<0) {
			return  -(((-z)-1) / 16)-1; // otherwise -1 and +1 would return the same chunk
		} else {
			return z / 16;
		}
	}
	
	/***
	 * correctly calculate the block Z value given a universal coordinate
	 * @param z
	 * @return
	 */
	public int getBlockZ(int z) {
		if(z<0) {
			return 15-(((-z)-1) % 16); // compensate for different chunk calculation
		} else {
			return z % 16;
		}
	}
	
	/***
	 * Returns a single byte representing the block data at the given universal coordinates
	 * @param x
	 * @param z
	 * @param y
	 * @return
	 */
	public byte getBlockData(int x, int z, int y) {
		int chunkX = getChunkX(x);
		int chunkZ = getChunkZ(z);
		
		int blockX = getBlockX(x);
		int blockZ = getBlockZ(z);
		
		Chunk chunk = this.getChunk(chunkX, chunkZ);
		if(chunk == null) { // no chunk for the given coordinate
			return 0;
		}
		int blockOffset = y + (blockZ * 128) + (blockX * 128 * 16);
		
		try {
			return chunk.getMapData().value[blockOffset];
		} catch(Exception e) {
			// dirty, but there was an error with out of range blockvalues O_o
			System.out.println(blockOffset);
			System.out.println("" + x + ", " + y + ", " + z);
			System.out.println("" + blockX + ", " + blockZ );
			System.exit(0);
			return 0;
		}
	}
	

	public void invalidateSelected() {
		this.invalidateSelected(false);
	}

	public void invalidateSelected(boolean main_dirty) {
		for (Chunk[] chunkrow : this.levelData)
		{
			for (Chunk chunk : chunkrow)
			{
				if (chunk != null)
				{
					chunk.isSelectedDirty = true;
					if (main_dirty)
					{
						chunk.isDirty = true;
					}
				}
			}
		}
	}
	
	public void markChunkAsDirty(int x, int z) {
		Chunk c = this.getChunk(x, z);
		if (c != null)
		{
			c.isDirty = true;
		}
	}
	
	public Tag loadChunk(int x, int z) {
		File chunkFile = MineCraftEnvironment.getChunkFile(world, x,z);
		if(!chunkFile.exists()) {
			return null;
		}
		Tag t = DTFReader.readDTFFile(chunkFile);

		if (t != null)
		{
			levelData[(x+LEVELDATA_OFFSET)%LEVELDATA_SIZE][(z+LEVELDATA_OFFSET)%LEVELDATA_SIZE] = new Chunk(this, t);
		}
		
		return t;
	}
	
	/**
	 * Gets the specified Chunk object
	 *
	 * @param chunkX
	 * @param chunkZ
	 * @return
	 */
	public Chunk getChunk(int chunkX, int chunkZ) {
		return this.levelData[(chunkX+LEVELDATA_OFFSET)%LEVELDATA_SIZE][(chunkZ+LEVELDATA_OFFSET)%LEVELDATA_SIZE];
	}

	/**
	 * Sets a chunk to null
	 * 
	 * @param chunkX
	 * @param chunkZ
	 */
	public void clearChunk(int chunkX, int chunkZ)
	{
		this.levelData[(chunkX+LEVELDATA_OFFSET)%LEVELDATA_SIZE][(chunkZ+LEVELDATA_OFFSET)%LEVELDATA_SIZE] = null;
	}
	
	/**
	 * Sets all chunks in the given X row to be no longer on the minimap
	 * 
	 * @param chunkX
	 */
	public ArrayList<Chunk> removeChunkRowXFromMinimap(int chunkX)
	{
		ArrayList<Chunk> chunks = new ArrayList<Chunk>();
		int xval = (chunkX+LEVELDATA_OFFSET)%LEVELDATA_SIZE;
		for (int i=0; i<LEVELDATA_SIZE; i++)
		{
			if (this.levelData[xval][i] != null && this.levelData[xval][i].isOnMinimap)
			{
				//System.out.println("(" + chunkX + ") Removing from minimap on " + xval + ", " + i);
				this.levelData[xval][i].isOnMinimap = false;
				chunks.add(this.levelData[xval][i]);
			}
		}
		return chunks;
	}
	
	/**
	 * Sets all chunks in the given Z row to be no longer on the minimap
	 * 
	 * @param chunkZ
	 */
	public ArrayList<Chunk> removeChunkRowZFromMinimap(int chunkZ)
	{
		ArrayList<Chunk> chunks = new ArrayList<Chunk>();
		int zval = (chunkZ+LEVELDATA_OFFSET)%LEVELDATA_SIZE;
		for (int i=0; i<LEVELDATA_SIZE; i++)
		{
			if (this.levelData[i][zval] != null && this.levelData[i][zval].isOnMinimap)
			{
				//System.out.println("Removing from minimap on " + i + ", " + zval);
				this.levelData[i][zval].isOnMinimap = false;
				chunks.add(this.levelData[i][zval]);
			}
		}
		return chunks;
	}
	
	/**
	 * Sets all chunks to be no longer on the minimap
	 * @return
	 */
	public ArrayList<Chunk> removeAllChunksFromMinimap()
	{
		ArrayList<Chunk> chunks = new ArrayList<Chunk>();	
		for (int z=0; z<LEVELDATA_SIZE; z++)
		{
			for (int x=0; x<LEVELDATA_SIZE; x++)
			{
				if (this.levelData[x][z] != null && this.levelData[x][z].isOnMinimap)
				{
					//System.out.println("(" + chunkX + ") Removing from minimap on " + xval + ", " + i);
					this.levelData[x][z].isOnMinimap = false;
					chunks.add(this.levelData[x][z]);
				}
			}
		}
		return chunks;
	}
	
	/***
	 * gets the data for a given chunk (coordinates are CHUNK coordinates, not world coordinates!)
	 * @param chunkX
	 * @param chunkZ
	 * @return
	 */
	public byte[] getChunkData(int chunkX, int chunkZ) {
		Chunk c = this.getChunk(chunkX, chunkZ);
		if(c == null) {
			return new byte[32768];
		} else {
			return c.getMapData().value;
		}
	}
		
	
	public Tag getFullChunk(int chunkX, int chunkZ) {
		Chunk c = this.getChunk(chunkX, chunkZ);
		if(c == null) {
			return null;
		}
		return c.getChunkData();
	}
}
