package ei.game.scene.units;


import java.util.LinkedList;
import ei.engine.math.Vector2f;
import ei.engine.math.Vector2i;
import ei.engine.math.Vector3f;
import ei.engine.scene.Node;
import ei.engine.sound.Sound;
import ei.game.algo.AStarPathFinder;
import ei.game.algo.Path;
import ei.game.algo.PathFinder;
import ei.game.gamestate.InGameState;
import ei.game.player.Player;
import ei.game.scene.GameEntity;
import ei.game.scene.Map;
import ei.game.scene.SelectBox;
import ei.game.scene.weapons.Weapon;
import ei.game.scene.weapons.WeaponHandler;

/**
 * The Unit class, handles the units in the game.
 * @author Jesper Lundin
 * @author Ziver koc
 *
 */
public abstract class Unit extends GameEntity{
	// The texture
	private Node unitNode;
	// Som temp pos for moving the unit
	private Vector2i oldPos;
	private Vector2i oldVect;
	// Order variables
	private Vector2f moveTo;
	private GameEntity attack;
	private int buildTime;
	private int price;
	// The wepon reload timer
	private int weponTimer;
	private int autoAttackTimer;
	// The path to travel
	private LinkedList<Vector2i> path;
	/** The finder searching the map */
	private static PathFinder pathfinder;
	private Vector2i target;
	private boolean autoAttack;

	/**
	 * Creates a empty unit
	 * 
	 * @param l The max life of the unit
	 */
	public Unit(int l, Vector2i pos, Player p) {
		super(l, p);
		unitNode = new Node("UnitNode");
		unitNode.setLocation(Map.getPixelByPos(pos.getX(), pos.getY()));
		setPos(pos.getX(), pos.getY());
		autoAttack = true;
		if(pathfinder == null){
			pathfinder = new AStarPathFinder(InGameState.getMap(),InGameState.getMap(),InGameState.getMap());
		}
		init();
		initGraphics();
	}
	
	/**
	 * Creates a empty unit
	 * 
	 * @param l The max life of the unit
	 */
	public Unit(int l, Player p) {
		super(l, p);
		unitNode = new Node("UnitNode");
		autoAttack = true;
		init();
		initGraphics();
	}
	
	public void setSelected(Player p,boolean b,boolean playSound) {
		if(b) {
			if(playSound)getSelectSound().play();
			SelectBox box = getSelection();
			if(getPlayer() != p){
				box.setColor(SelectBox.COLOR_RED);
				unitNode.add(box.getSelectNode());
			}
			else{
				box.setColor(SelectBox.COLOR_GREEN);
				unitNode.add(box.getSelectNode());	
			}
		}
		else{
			unitNode.remove(getSelection().getSelectNode());
		}
	}
	
	public void setMouseOver(Player p,boolean b) {
		if(b) {
			SelectBox box = getSelection();
			if(getPlayer() != p){
				box.setColor(SelectBox.COLOR_RED);
				unitNode.add(box.getMouseOverNode());
			}
			else{
				box.setColor(SelectBox.COLOR_GREEN);
				unitNode.add(box.getMouseOverNode());	
			}
		}
		else{
			unitNode.remove(getSelection().getMouseOverNode());
		}
	}
	
	/**
	 * Returns the sprite for the unit
	 * 
	 * @return The sprite for the unit
	 */
	public Node getNode(){
		return unitNode;
	}
	/**
	 * Returns the current pos of the unit
	 */
	public Vector2i getPos() {
		return oldPos;
	}
	/**
	 * Changes the pos of the unit in the map
	 * 
	 * @param x The x pos to move to
	 * @param y The y pos to move to
	 */
	public void setPos(int x, int y) {
		if(oldPos!=null) {
			InGameState.getMap().removePos(oldPos.getX(), oldPos.getY());
		}
		oldPos = new Vector2i(x, y);
		InGameState.getMap().setPos(this, x, y);
	}
	
	public void forcePos(int x, int y){
		if(oldPos!=null) {
			InGameState.getMap().removePos(oldPos.getX(), oldPos.getY());
		}
		oldPos = new Vector2i(x, y);
		InGameState.getMap().setPos(this, x, y);
		unitNode.setLocation(Map.getPixelByPos(x, y));
	}
	
	/**
	 * Moving a unit to the given pos
	 * @param play if play, play sound
	 * @param x The x pos to move to
	 * @param y The y pos to move to
	 */
	public void move(boolean play, int x, int y){
		if(play){
			getMoveSound().play();
		}
		move(x,y,true);
	}
	/**
	 * Specifies a building time for this unit
	 * @param time, time to build.
	 */
	public void setBuildTime(int time) {
		this.buildTime = time;
	}
	/**
	 * Specifies a price for this unit.
	 * @param price, the cost.
	 */
	public void setPrice(int price) {
		this.price = price;
	}
	public boolean isAttacking() {
		if(attack==null) {
			return false;
		}
		return true;
	}
	public boolean isMoving() {
		if(path==null || path.isEmpty()) {
			return false;
		}
		return true;
	}
	/**
	 * Moving a unit to the given pos
	 * 
	 * @param x The x pos to move to
	 * @param y The y pos to move to
	 * @param b True to clear old orders else false
	 */
	protected void move(int x, int y, boolean b) {
		if(b){
			attack = null;
		}
		
		System.out.println(oldPos.getX()+" "+oldPos.getY()+"-"+x+" "+y);
		Path p = pathfinder.findPath(oldPos.getX(),oldPos.getY(),x,y,600);
		pathfinder.reset();
		if(p != null) path = p.getList();
		else path = null;

		if(path != null && !path.isEmpty() && moveTo == null){
			Vector2i temp = path.poll();
			oldVect = new Vector2i((int)unitNode.getLocation().getX(), (int)unitNode.getLocation().getY());
			moveTo = Map.getPixelByPos(temp.getX(), temp.getY());
			setPos(temp.getX(), temp.getY());
		}
	}
	
	/**
	 * Lets a unit attack another unit or object in the world;
	 */
	public void attack(Vector2i target, boolean play) {
		if(play){ 
			getAttackSound().play();
		}
		this.target = target;
		attack = InGameState.getMap().getPos(target.getX(),target.getY());
	}
	
	public void autoAttack(boolean auto) {
		autoAttack = auto;
	}
	
	public abstract Weapon getWeapon(Vector2f startPos);
	
	public abstract float getVelocity();
	
	public abstract Sound getGunSound();
	
	public abstract Sound getAttackSound();
	
	public abstract Sound getSelectSound();
	
	public abstract Sound getMoveSound();
	
	public abstract void init();
	
	public void remove(){
		unitNode.remove(getSprite());
		getPlayer().removeUnit(this);
		InGameState.getMap().removePos(oldPos.getX(), oldPos.getY());
	}
	
	/**
	 * Updating the unit
	 */
	public void update() {
		weponTimer++;
		if(getLife()<=0) {
			destroyed();
			remove();
		}
		else if(moveTo != null) {
			Vector2i moveVect = new Vector2i((int)moveTo.getX(),(int)moveTo.getY());
			
			int moveXminus = moveVect.getX()-oldVect.getX();
			int moveYminus = moveVect.getY()-oldVect.getY();

			float divideY = (moveVect.getY()+2)/(oldVect.getY()+2);
			float divideX = (moveVect.getX()+2)/(oldVect.getX()+2);

			//The rotation animation is done here.
			if(moveVect.getX() < oldVect.getX() && divideY==1) {
				getSprite().setRotation(new Vector3f(0, 0, 90));
			}

			if(moveVect.getX() > oldVect.getX() && divideY==1) {
				getSprite().setRotation(new Vector3f(0, 0, -90));
			}
			
			if(moveVect.getY() < oldVect.getY() && divideX==1) {
				getSprite().setRotation(new Vector3f(0, 0, 180));
			}
			
			if(moveVect.getY() > oldVect.getY() && divideX==1) {
				getSprite().setRotation(new Vector3f(0, 0, 0));
			}				

			//Diagonally.
			if(moveVect.getX() > oldVect.getX() && moveVect.getY() > oldVect.getY()
					&& moveXminus>=2 && moveYminus>=2) {
				getSprite().setRotation(new Vector3f(0, 0, -45));
			}

			if(moveVect.getX() < oldVect.getX() && moveVect.getY() < oldVect.getY()
					&& moveXminus<=-2 && moveYminus<=-2) {
				getSprite().setRotation(new Vector3f(0, 0, 135));
			}

			if(moveVect.getX() > oldVect.getX() && moveVect.getY() < oldVect.getY()
					&& moveXminus>=2 && moveYminus<=-2) {
				getSprite().setRotation(new Vector3f(0, 0, -135));
			}

			if(moveVect.getX() < oldVect.getX() && moveVect.getY() > oldVect.getY()
					&& moveXminus<=-2 && moveYminus>=2) {
				getSprite().setRotation(new Vector3f(0, 0, 45));
			}

			//System.out.println(getSprite().getRotation());


			//The moving is done here.
			if(moveTo.getX() > unitNode.getLocation().getX()) {
				unitNode.getLocation().add(getVelocity(), 0f, 0f);
			}
			if(moveTo.getX() < unitNode.getLocation().getX()) {
				unitNode.getLocation().add(-getVelocity(), 0f, 0f);
			}
			if(moveTo.getY() > unitNode.getLocation().getY()) {
				unitNode.getLocation().add(0f, getVelocity(), 0f);
			}
			if(moveTo.getY() < unitNode.getLocation().getY()) {
				unitNode.getLocation().add(0f, -getVelocity(), 0f);
			}
			
			else if(Math.abs(moveTo.getX() - unitNode.getLocation().getX()) < getVelocity()+1
					&& Math.abs(moveTo.getY() - unitNode.getLocation().getY())< getVelocity()+1 ){
				
				if(attack != null && getWeapon(new Vector2f(unitNode.getLocation().getX(), unitNode.getLocation().getY()))
						.onRange(new Vector2f(attack.getNode().getLocation().getX(), attack.getNode().getLocation().getY()))){
					moveTo = null;
				}
				else if(path != null && !path.isEmpty()){
					Vector2i temp = path.poll();
					if(InGameState.getMap().isPosEmpty(temp.getX(), temp.getY())){
						oldVect = new Vector2i((int)moveTo.getX(), (int)moveTo.getY());
						moveTo = Map.getPixelByPos(temp.getX(), temp.getY());
						setPos(temp.getX(), temp.getY());
					}
					else if(!path.isEmpty()){
						//removed because its making problem whit the bomber unit
						//move(false, path.getLast().getX(),	path.getLast().getY());
					}
				}
				else{
					moveTo = null;
				}
			}
		}
		else if(attack != null){
			Weapon wepon = getWeapon(new Vector2f(unitNode.getLocation().getX(), unitNode.getLocation().getY()));
			Vector2i enamyPos;
			if(InGameState.getMap().getPos(target.getX(), target.getY())!=attack) {
				enamyPos = InGameState.getMap().getPosIndex(attack);
			}
			else{
				enamyPos = target.getCopy();
			}


			if(attack.getLife() <= 0 || enamyPos == null){
				attack = null;
			}
			else{
				if(wepon.onRange(new Vector2f(attack.getNode().getLocation().getX(), attack.getNode().getLocation().getY()))){
					if(weponTimer >= wepon.getReload()){
						wepon.launch(enamyPos);
						getGunSound().play();
						WeaponHandler.getInstance().addWeapon(wepon);
						weponTimer = 0;
					}
					
				}
				else{
					move(enamyPos.getX(),enamyPos.getY(), false);
				}
			}	
		}
		else if(autoAttack && (autoAttackTimer%50==0)) {
			Weapon wep = getWeapon(Map.getPixelByPos(oldPos.getX(), oldPos.getY()));
			for(int i=oldPos.getX()-(wep.getRange()/50); i<=oldPos.getX()+(wep.getRange()/50); i++ ) {
				for(int j=oldPos.getY()-(wep.getRange()/50); j<=oldPos.getY()+(wep.getRange()/50); j++) {
					//System.out.println("i :"+i+" j: "+j);
					
					if(InGameState.getMap().posExist(i, j)&& !InGameState.getMap().isPosEmpty(i, j) 
							&& getPlayer()!=InGameState.getMap().getPos(i, j).getPlayer() &&  wep.onRange(Map.getPixelByPos(i, j))
							&& InGameState.getMap().getPos(i, j).isTerrain()==false) {
						//System.out.println("ziver is shit");
						attack(new Vector2i(i, j), false);
						i = oldPos.getX()+(wep.getRange()/50)+1;
						break;
					}
				}
			}
		}
		autoAttackTimer++;
		
	}
	public boolean isTerrain() {
		return false;
	}
	
	public int getBuildTime(){
		return buildTime;
	}
	
	public int getPrice(){
		return price;
	}
}
