package ei.game.scene;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;

import ei.engine.math.Vector2f;
import ei.engine.math.Vector2i;
import ei.engine.math.Vector3f;
import ei.engine.scene.Entity;
import ei.engine.scene.Node;
import ei.engine.scene.Sprite;
import ei.engine.util.MultiPrintStream;
import ei.game.algo.PathFinderCallback;
import ei.game.algo.TileMap;
import ei.game.algo.TileSet;
import ei.game.player.GaiaPlayer;
import ei.game.player.Player;
import ei.game.player.PlayerHandler;
import ei.game.scene.map.Factory;
import ei.game.scene.map.Prison;
import ei.game.scene.map.Stone;
import ei.game.scene.map.Water;

public class Map implements TileMap, TileSet, PathFinderCallback{
	public static final int MAP_GRASS = 0;
	public static final int MAP_SAND = 1;
	public static final int MAP_REDMUD = 2;
	public static final int MAP_ASFALT = 3;
	public static final int MAP_PARKING = 4;
	public static final int MAP_ASFALT_HORIZONTAL = 5;
	public static final int MAP_ASFALT_VERTICAL = 6;
	
	public static final int OBJ_STONE = 1;
	public static final int OBJ_FACTORY = 2;
	public static final int OBJ_PRISON = 3;
	public static final int OBJ_WATER = 4;
	
	public static final int POS_SIZE = 50;
	private int width;
	private int hight;
	private GameEntity[][] map;
	private Node mapNode;
	private String[] parking = {
			"data/map/parking/parking_01.png",
			"data/map/parking/parking_04.png",
			"data/map/parking/parking_07.png",
			"data/map/parking/parking_02.png",
			"data/map/parking/parking_05.png",
			"data/map/parking/parking_08.png",
			"data/map/parking/parking_03.png",
			"data/map/parking/parking_06.png",
			"data/map/parking/parking_09.png"
	};
	private int parkingIndex;
	
	public Map(int w, int h){
		this.width = w;
		this.hight = h;
		//init();
	}

	/**
	 * Creates a default map
	 *
	 */
	public void init(){
		map = new GameEntity[width][hight];
		
		// init textures
		mapNode = new Node("MapNode");
		for(int i=0; i<width ;i++){
			for(int j=0; j<hight ;j++){
				Sprite s = new Sprite("MapPos("+i+","+j+")","data/map/sand.jpg");
				s.setLocation(new Vector3f(i*POS_SIZE,j*POS_SIZE,0));
				s.setSize(new Vector2f(POS_SIZE,POS_SIZE));
				mapNode.add(s);
			}
		}
		
		Player gaia = new GaiaPlayer();
		PlayerHandler.getInstance().addPlayer(gaia);
		for(int i=0; i<5 ;i++){
			gaia.addUnit(new Stone(new Vector2i((int)(Math.random()*width),(int)(Math.random()*hight)),gaia));
		}
	}
	
	/**
	 * Loads the map from a file
	 * 
	 * @param file The map file name without extension
	 */
	public void init(String file){
		int[][] mapData;
		int[][] objData;
		try {
			mapData = getMapData(file+".map");
			objData = getMapData(file+".obj");
		} catch (IOException e) {
			MultiPrintStream.out.println("Error Loading Map: "+e);
			e.printStackTrace();
			return;
		}
		width = mapData.length;
		hight = mapData[0].length;
		map = new GameEntity[width][hight];
		
		// init textures
		mapNode = new Node("MapNode");
		for(int i=0; i<width ;i++){
			for(int j=0; j<hight ;j++){
				switch(mapData[i][j]){
				case MAP_GRASS:
					mapNode.add(getPosSprite(i,j,"data/map/grass.png", 7));
					break;
				case MAP_SAND:
					mapNode.add(getPosSprite(i,j,"data/map/sand.png", 6));
					break;
				case MAP_REDMUD:
					mapNode.add(getPosSprite(i,j,"data/map/redmud.png", 6));
					break;
				case MAP_ASFALT:
					mapNode.add(getPosSprite(i,j,"data/map/asfalt.png", 6));
					break;
				case MAP_ASFALT_HORIZONTAL:
					mapNode.add(getPosSprite(i,j,"data/map/asfalt_horizontal.png", 6));
					break;
				case MAP_ASFALT_VERTICAL:
					mapNode.add(getPosSprite(i,j,"data/map/asfalt_vertical.png", 6));
					break;
				case MAP_PARKING:
					mapNode.add(getPosSprite(i,j,parking[parkingIndex], 0));
					parkingIndex++;
					if(parkingIndex >= parking.length){
						parkingIndex = 0;
					}
					break;
				}
			}
		}
		
		// init the gaia player and stones etc...
		Player gaia = new GaiaPlayer();
		PlayerHandler.getInstance().setGaiaPlayer(gaia);
		for(int i=0; i<width ;i++){
			for(int j=0; j<hight ;j++){
				switch(objData[i][j]){
				case OBJ_STONE:
					gaia.addUnit(new Stone(new Vector2i(i,j),gaia));
					break;
				case OBJ_FACTORY:
					gaia.addUnit(new Factory(new Vector2i(i,j),gaia));
					break;
				case OBJ_PRISON:
					gaia.addUnit(new Prison(new Vector2i(i,j),gaia));
					break;
				case OBJ_WATER:
					gaia.addUnit(new Water(new Vector2i(i,j),gaia));
					break;
				}
			}
		}
	}
	
	private Entity getPosSprite(int x, int y, String file, int overlap){
		Sprite s = new Sprite("MapPos("+x+","+y+")", file);
		s.setLocation(new Vector3f(x*POS_SIZE,y*POS_SIZE,0));
		s.setSize(new Vector2f((overlap*2)+POS_SIZE, (overlap*2)+POS_SIZE));
		return s;
	}
	
	/**
	 * Loads a map file
	 * 
	 * @param file The file to load
	 * @return The data
	 * @throws IOException
	 */
	private int[][] getMapData(String file) throws IOException{
		LinkedList<String> tempData = new LinkedList<String>();

		MultiPrintStream.out.println("Loading Map file: "+file);
		InputStreamReader isr = new InputStreamReader(
				getClass().getClassLoader().getResourceAsStream(file));
		BufferedReader infil = new BufferedReader(isr);
		
		String rad="";
		while(rad!=null){
			rad = infil.readLine();
			if(rad != null)
				tempData.addLast(rad);
		}

		// calculate the size of the map
		int xSize = 0;
		int ySize = tempData.size();
		for(int i=0; i<tempData.size() ;i++){
			if(xSize < tempData.get(i).length()){
				xSize = tempData.get(i).length();
			}
		}

		// geting the data for every pos
		int[][] data = new int[xSize][ySize];
		for(int i=0; i<ySize ;i++){
			String temp = tempData.get(i);
			for(int j=0; j<temp.length() ;j++){
				data[j][i] = Integer.parseInt(""+temp.charAt(j));
			}
		}
		return data;
	}
	
	/**
	 * Returns the size of the map
	 * 
	 * @return The size of the map
	 */
	public Vector2i getSize(){
		return new Vector2i(width,hight);
	}
	
	/**
	 * Returns the pos of the map by the pixel
	 * @param x x pixels
	 * @param y y pixels
	 * @return The pos thats in the coordinates
	 */
	public static Vector2i getPosByPixel(float x, float y){
		x = x/POS_SIZE;
		y = y/POS_SIZE;
		return new Vector2i((int)x, (int)y);
	}
	
	/**
	 * The coordinate of the map pos
	 * @param x x pos
	 * @param y y pos
	 * @return The coordinate of the pos
	 */
	public static Vector2f getPixelByPos(int x, int y){
		float xf = (POS_SIZE*x);
		float yf = (POS_SIZE*y);
		return new Vector2f(xf,yf);
	}
	
	public static Vector2f getPixelByPos(int x, int y, int size){
		if(size%2!=0) {
			size = (size/2)+1;
			float xf = (POS_SIZE)*(x+size);
			float yf = (POS_SIZE)*(y+size);
			return new Vector2f(xf,yf);
		}
		size = (size/2)-1;
		float xDiff = Math.abs((getPixelByPos(x+size, 0).getX())-(getPixelByPos(size+x+1, 0).getX()))/2;
		float yDiff = Math.abs((getPixelByPos(0, size+y).getY())-(getPixelByPos(0, size+y+1).getY()))/2;
	
		return new Vector2f(getPixelByPos(x+size, 0).getX()+xDiff, getPixelByPos(0, y+size).getY()+yDiff);
		
		
	}
	
	/**
	 * Returns if the pos inthe map is empty
	 * 
	 * @param x The x pos
	 * @param y The y pos
	 * @return True if empty else false
	 */
	public boolean isPosEmpty(int x, int y){
		if(posExist(x,y) && map[x][y] == null){
			return true;
		}
		return false;
	}
	/**
	 * Returns true if the pos in the map is empty
	 * @param x pos
	 * @param y pos
	 * @param size, size to check
	 * @return true if pos is empty, else false
	 */
	public boolean isPosEmpty(int x, int y, int size) {
		for(int i=x; i<size+x; i++) {
			for(int j=y; j<size+y; j++) {
				if(posExist(i,j) && map[i][j] == null){
					return true;
				}
				return false;
			}
		}
		return false;
	}
	
	/**
	 * Returns true if the given pos exists on the map
	 * @param x The x pos
	 * @param y The y pos
	 * @return True if the given pos exists on the map
	 */
	public boolean posExist(int x, int y){
		if(x < 0 || y < 0 || x >= width || y >= hight)
			return false;
		return true;
	}
	
	/**
	 * Returns the object at the pos
	 * 
	 * @param x The x pos
	 * @param y The y pos
	 * @return The object in that pos
	 */
	public GameEntity getPos(int x, int y){
		return map[x][y];
	}
	
	/**
	 * Sets an object at the pos
	 * 
	 * @param e The object
	 * @param x The x pos
	 * @param y The y pos
	 */
	public void setPos(GameEntity e, int x, int y){
		map[x][y] = e;
	}
	/**
	 * Set an object in a rectangle of size*size, starting at x,y
	 * @param e object
	 * @param x pos
	 * @param y pos
	 * @param size 
	 */
	public void setBuildPos(GameEntity e, int x, int y, int size) {
		if(isPosEmpty(x, y, size)) {
			for(int i=x; i<size+x; i++) {
				for(int j=y; j<size+y; j++) {
					map[i][j] = e;
				}
			}
		}
	}
	/**
	 * Remove an object from this position
	 * @param e, the object
	 * @param x pos of object
	 * @param y pos of object
	 * @param size of object
	 */
	public void removeBuildPos(int x, int y, int size) {
		for(int i=x; i<size+x; i++) {
			for(int j=y; j<size+y; j++) {
				map[i][j] = null;
			}
		}
	}
	/**
	 * Removes a object from that pos
	 * 
	 * @param x The x pos
	 * @param y The y pos
	 */
	public void removePos(int x, int y){
		map[x][y] = null;
	}
	
	/**
	 * Returns the map node
	 * 
	 * @return The map node
	 */
	public Node getMapNode(){
		return mapNode;
	}
	
	public Vector2i getPosIndex(GameEntity u){
		for(int i=0; i<width ;i++){
			for(int j=0; j<hight ;j++){
				if(map[i][j] == u){
					return new Vector2i(i,j);
				}
			}
		}
		return null;
	}
	
	/**
	 * Prints all the units on the map
	 *
	 */
	public void printAllUnits(){
		for(int i=0; i<width ;i++){
			for(int j=0; j<hight ;j++){
				if(map[i][j] != null){
					System.out.println("Unit("+map[i][j].getNode().getName()+"): "+i+" "+j);
				}
			}
		}
	}

	
	//*************************************************************
	//*************************************************************
	//Methods for the path finding algo
	
	public int getMapHeight() {
		return hight;
	}

	public int getMapWidth() {
		return width;
	}

	public int getTileAt(int x, int y, int layer) {
		if(map[x][y] == null){
			return 0;
		}
		return 1;
	}

	public boolean isBlocked(int x, int y) {
		if(x < 0 || x >= width || y < 0 || y >= hight){
			return true;
		}
		return false;
	}

	public boolean blocksMovement(int tile) {
		return tile != 0;
	}

	public void fireNode(int x, int y) {
		//System.out.println(x+" "+y+" "+getTileAt(x,y,0)+" "+blocksMovement(getTileAt(x,y,0)));
	}
}
