using UnityEngine;
using System.Collections;

public class GameController : MonoBehaviour {
	private const float CAMERA_SPEED = 5.0f;
	
	private Grid grid;
	private ArrayList units;
	private ArrayList deadUnits;
	
	private Player player1;
	private Player player2;
	
	private Player currentTurn;
	private Unit selectedUnit;
	
	private CameraPanAnim cameraPanAnim;
	private UnitWalk unitWalk;
	private ArrayList battles;
	private ArrayList fogOfWar;
	private ArrayList highlight;
	
	private bool turnOver;
	private Player winner;
	
	private bool cameraLeft;
	private bool cameraRight;
	private bool cameraUp;
	private bool cameraDown;
	
	
	// Use this for initialization
	public void Start() {
		player1 = new Player(Player.Side.LIGHT, "Light", true);
		player1.setLeaderObjective(Constants.DARK_LEADER_START);
		
		player2 = new Player(Player.Side.DARK, "Darkness", true);
		player2.setLeaderObjective(Constants.LIGHT_LEADER_START);
		
		grid = new Grid();
		
		units = new ArrayList();
		deadUnits = new ArrayList();
		fogOfWar = new ArrayList();
		highlight = new ArrayList();
		
		units.Add(new Unit(player1, Unit.Type.LEADER, Constants.LIGHT_LEADER_START));
		units.Add(new Unit(player1, Unit.Type.RED, Constants.LIGHT_RED_START));
		units.Add(new Unit(player1, Unit.Type.GREEN, Constants.LIGHT_GREEN_START));
		units.Add(new Unit(player1, Unit.Type.BLUE, Constants.LIGHT_BLUE_START));
		
		units.Add(new Unit(player2, Unit.Type.LEADER, Constants.DARK_LEADER_START));
		units.Add(new Unit(player2, Unit.Type.RED, Constants.DARK_RED_START));
		units.Add(new Unit(player2, Unit.Type.GREEN, Constants.DARK_GREEN_START));
		units.Add(new Unit(player2, Unit.Type.BLUE, Constants.DARK_BLUE_START));
		
		for (int i = 0; i < units.Count; ++i) {
			grid.addUnit((Unit)units[i]);
		}
		
		currentTurn = player1;
		updateFogOfWar();
		
		panCameraToLeader();
	}
	
	private void panCameraToLeader() {
		for (int i = 0; i < units.Count; ++i) {
			Unit unit = (Unit)units[i];
			if (unit.getPlayer() == currentTurn && unit.isLeader()) {
				cameraPanAnim = new CameraPanAnim(unit);
				break;
			}
		}
	}
	
	public void FixedUpdate() {
		if (cameraPanAnim != null) {
			cameraPanAnim.step(Time.fixedDeltaTime);
			if (cameraPanAnim.isComplete()) cameraPanAnim = null;
			else return;
		}
		
		// update camera
		if (cameraLeft || cameraRight || cameraUp || cameraDown) {
			Vector3 dir = new Vector3(0, 0, 0);
			if (cameraLeft) dir += new Vector3(-1, 0, 0);
			if (cameraRight) dir += new Vector3(1, 0, 0);
			if (cameraUp) dir += new Vector3(0, 1, 0);
			if (cameraDown) dir += new Vector3(0, -1, 0);
			Camera.main.transform.position += dir * Time.fixedDeltaTime * CAMERA_SPEED;
		}
		
		if (unitWalk != null) {
			Tile currentTile = grid.getTile(unitWalk.getUnit());
			unitWalk.step(Time.fixedDeltaTime);
			if (unitWalk.isComplete()) {
				Player w = checkWinPosition(unitWalk.getUnit());
				if (w != null) {
					gameOver(w);
					return;
				}
				
				sortBattles(unitWalk.getBattles());
				unitWalk = null;
				updateTileBlockedRenderMode();
				
				if (battles != null) processBattles();
				else {
					updateFogOfWar();
					updateHighlight();
				}
			}
			else {
				if (currentTile != grid.getTile(unitWalk.getUnit())) updateFogOfWar();
			}
		}
	}
	
	private Player checkWinPosition(Unit unit) {
		if (unit.isLeader() && unit.getPosition().equals(unit.getPlayer().getLeaderObjective())) {
			return unit.getPlayer();
		}
		
		return null;
	}
	
	public void Update() {
		if (unitWalk == null && battles == null) processInput();
	}
	
	public void OnGUI() {
		if (winner != null) {
			GUI.Label(new Rect(200, 200, 200, 30), "Game Over! " + winner.getName() + " wins!");
			GUI.Label(new Rect(250, 230, 200, 30), "Press RETURN to return to main menu");
		}
		else if (turnOver) {
			GUI.Label(new Rect(200, 200, 200, 30), "Switch players and press SPACE");
		}
		else if (isOutOfMoves()) {
			GUI.Label(new Rect(200, 200, 200, 30), "You are out of moves.");
			GUI.Label(new Rect(200, 230, 200, 30), "press SPACE to end your turn");
		}
	}
	
	private bool isOutOfMoves() {
		for (int i = 0; i < units.Count; ++i) {
			Unit unit = (Unit)units[i];
			if (unit.getPlayer() == currentTurn && unit.canMoveThisTurn()) return false;
		}
		
		return true;
	}
	
	private void sortBattles(ArrayList battleList) {
		if (battleList.Count == 0) return;
		
		battles = new ArrayList();
		
		// add first all battles that the first unit wins (the one who moved)
		for (int i = 0; i < battleList.Count; ++i) {
			if (((Battle)battleList[i]).firstUnitWillKill()) battles.Add(battleList[i]);
		}
		
		// add all battles that the will draw
		for (int i = 0; i < battleList.Count; ++i) {
			if (((Battle)battleList[i]).willBattleDraw()) battles.Add(battleList[i]);
		}
		
		// add all battles that the first unit lose
		for (int i = 0; i < battleList.Count; ++i) {
			if (((Battle)battleList[i]).firstUnitWillDie()) {
				battles.Add(battleList[i]);
				
				// just need to kill it once
				break;
			}
		}
	}
	
	private void processBattles() {
		for (int i = 0; i < battles.Count; ++i) {
			Battle battle = (Battle)battles[i];
			battle.processBattle();
			Unit dead = battle.getDead();
			if (dead != null) {
				grid.removeUnit(dead);
				units.Remove(dead);
				deadUnits.Add(dead);
			}
		}
		
		battles = null;
		
		// check if a leader is dead
		for (int i = 0; i < deadUnits.Count; ++i) {
			Unit unit = (Unit)deadUnits[i];
			if (unit.isLeader()) {
				gameOver(unit.getPlayer() == player1 ? player2 : player1);
			}
		}
		
		updateFogOfWar();
	}
	
	private void endTurn() {
		turnOver = true;
		
		updateFogOfWar();
		
		if (selectedUnit != null) {
			selectedUnit.setSelected(false);
			selectedUnit = null;
			updateHighlight();
			updateTileBlockedRenderMode();
		}
	}
	
	public void nextTurn() {
		turnOver = false;
		
		if (selectedUnit != null) {
			selectedUnit.setSelected(false);
			selectedUnit = null;
			updateHighlight();
			updateTileBlockedRenderMode();
		}

		if (currentTurn == player1) currentTurn = player2;
		else currentTurn = player1;

		for (int i = 0; i < units.Count; ++i) {
			((Unit)units[i]).nextTurn();
		}

		updateFogOfWar();
		panCameraToLeader();
	}
	
	private void gameOver(Player win) {
		winner = win;
		turnOver = true;
		clearObjectList(fogOfWar);
		
		for (int i = 0; i < units.Count; ++i) {
			Unit unit = ((Unit)units[i]);
			unit.setVisible(true);
			unit.setIdentified(true);
		}
	}
	
	private void processInput() {
		processCameraInput();
		
		if (winner != null) {
			if (Input.GetKeyUp("return")) Application.LoadLevel("MainMenu");
			return;
		}
		if (turnOver) {
			if (Input.GetKeyUp("space")) nextTurn();
			return;
		}
		
		if (Input.GetMouseButtonUp(0)) {
			Vector2 location = Input.mousePosition;
			Vector3 pos = Camera.main.ScreenToWorldPoint(new Vector3(location.x, location.y, 0));
			
			if (pos.x >= 0.0f && pos.y >= 0.0f && (int)pos.x < grid.getGridWidth() && (int)pos.y < grid.getGridHeight()) {
				GridPos gridPos = new GridPos((int)pos.x, (int)pos.y);
				Tile tile = grid.getTile(gridPos);
				
				if (tile.hasUnit()) unitClicked(tile.getUnit());
				else if (selectedUnit != null && unitCanMoveTo(selectedUnit, gridPos)) {
					clearObjectList(highlight);
					moveUnit(selectedUnit, gridPos);
					if (!selectedUnit.canMoveThisTurn()) {
						selectedUnit.setSelected(false);
						selectedUnit = null;
					}
				}
			}
		}
		else if (Input.GetKeyUp("space")) endTurn();
		else if (Input.GetKeyUp("escape")) Application.Quit();
	}
	
	private void processCameraInput() {
		cameraLeft = Input.GetKey("left");
		cameraRight = Input.GetKey("right");
		cameraUp = Input.GetKey("up");
		cameraDown = Input.GetKey("down");
	}
	
	private void unitClicked(Unit unit) {
		if (unit == selectedUnit) {
			selectedUnit.setSelected(false);
			selectedUnit = null;
			updateHighlight();
			updateTileBlockedRenderMode();
		}
		else if (canBeSelected(unit)) {
			if (selectedUnit != null) selectedUnit.setSelected(false);
			selectedUnit = unit;
			selectedUnit.setSelected(true);
			updateHighlight();
			updateTileBlockedRenderMode();
		}
	}
	
	private bool canBeSelected(Unit unit) {
		return currentTurn == unit.getPlayer() && unit.canMoveThisTurn();
	}
	
	private bool unitCanMoveTo(Unit unit, GridPos target) {
		GridPos pos = unit.getPosition();
		
		bool directional = pos.getX() == target.getX() || pos.getY() == target.getY();
		
		return directional && unit.getPosition().distance(target) - 1.0e-5f <= unit.getRemainingMove();
	}
	
	private void moveUnit(Unit unit, GridPos target) {
		unitWalk = new UnitWalk(grid, unit, target);
	}
	
	private void updateHighlight() {
		clearObjectList(highlight);
		
		if (selectedUnit != null) {
			int moves = selectedUnit.getRemainingMove();
			GridPos pos = selectedUnit.getPosition();
			
			updateHighlightDir(pos, moves, -1, 0);
			updateHighlightDir(pos, moves, 1, 0);
			updateHighlightDir(pos, moves, 0, -1);
			updateHighlightDir(pos, moves, 0, 1);
		}
	}
	
	private void updateHighlightDir(GridPos pos, int moves, int dx, int dy) {
		for (int i = 1; i <= moves; ++i) {
			int x = pos.getX() + i * dx;
			int y = pos.getY() + i * dy;
			
			if (x >= 0 && x < grid.getGridWidth() && y >= 0 && y < grid.getGridHeight()) {
				GridPos p = new GridPos(x, y);
				Tile tile = grid.getTile(p);
				if (!selectedUnit.canWalkOnTile(tile)) break;
				if (tile.hasUnit()) break;
				
				createHighlight(p);
			}
		}
	}
	
	private void updateFogOfWar() {
		clearObjectList(fogOfWar);
		
		if (turnOver) {
			updateFogTurnOver();
			return;
		}
		
		bool[,] visible = new bool[grid.getGridWidth(),grid.getGridHeight()];
		bool[,] identifyRed = new bool[grid.getGridWidth(),grid.getGridHeight()];
		bool[,] identifyGreen = new bool[grid.getGridWidth(),grid.getGridHeight()];
		bool[,] identifyBlue = new bool[grid.getGridWidth(),grid.getGridHeight()];
		
		markVisible(visible, identifyRed, identifyGreen, identifyBlue);
		
		for (int x = 0; x < grid.getGridWidth(); ++x) {
			for (int y = 0; y < grid.getGridHeight(); ++y) {
				updateVisibility(visible, identifyRed, identifyGreen, identifyBlue, new GridPos(x, y));
			}
		}
	}
	
	private void clearObjectList(ArrayList objList) {
		for (int i = 0; i < objList.Count; ++i) {
			GameObject.Destroy((GameObject)objList[i]);
		}
		objList.Clear();
	}
	
	private void updateFogTurnOver() {
		for (int x = 0; x < grid.getGridWidth(); ++x) {
			for (int y = 0; y < grid.getGridHeight(); ++y) {
				createFog(new GridPos(x, y));
			}
		}
		for (int i = 0; i < units.Count; ++i) {
			((Unit)units[i]).setVisible(false);
		}
	}
	
	private void updateVisibility(bool[,] visible, bool[,] identifyRed, bool[,] identifyGreen, bool[,] identifyBlue, GridPos pos) {
		int x = pos.getX();
		int y = pos.getY();
		
		if (!visible[x, y]) createFog(pos);

		Unit unit = grid.getTile(pos).getUnit();
		if (unit != null) {
			if (unit.getPlayer() == currentTurn) {
				unit.setVisible(true);
				unit.setIdentified(true);
			}
			else {
				unit.setVisible(visible[x, y]);
				
				switch (unit.getType()) {
					case Unit.Type.RED:
						unit.setIdentified(identifyRed[x, y]);
						break;
					case Unit.Type.GREEN:
						unit.setIdentified(identifyGreen[x, y]);
						break;
					case Unit.Type.BLUE:
						unit.setIdentified(identifyBlue[x, y]);
						break;
					case Unit.Type.LEADER:
						unit.setIdentified(true);
						break;
				}
			}
		}
	}
	
	private void markVisible(bool[,] visible, bool[,] identifyRed, bool[,] identifyGreen, bool[,] identifyBlue) {
		for (int x = 0; x < grid.getGridWidth(); ++x) {
			for (int y = 0; y < grid.getGridHeight(); ++y) {
				Unit unit = grid.getTile(new GridPos(x, y)).getUnit();
				if (unit != null && unit.getPlayer() == currentTurn) {
					markVisible(visible, x, y, unit.getVision());
					
					switch (unit.getType()) {
						case Unit.Type.RED:
							markVisible(identifyRed, x, y, unit.getVision());
							break;
						case Unit.Type.GREEN:
							markVisible(identifyGreen, x, y, unit.getVision());
							break;
						case Unit.Type.BLUE:
							markVisible(identifyBlue, x, y, unit.getVision());
							break;
						case Unit.Type.LEADER:
							markVisible(identifyRed, x, y, unit.getVision());
							markVisible(identifyGreen, x, y, unit.getVision());
							markVisible(identifyBlue, x, y, unit.getVision());
							break;
					}
				}
			}
		}
	}
	
	private void markVisible(bool[,] visible, int centerX, int centerY, int radius) {
		int xBegin = centerX - radius;
		if (xBegin < 0) xBegin = 0;
		int xEnd = centerX + radius + 1;
		if (xEnd >= grid.getGridWidth()) xEnd = grid.getGridWidth();
		
		for (int x = xBegin; x < xEnd; ++x) {
			int dx = x - centerX;
			if (dx < 0) dx = -dx;
			int radY = radius - dx;
			
			int yBegin = centerY - radY;
			if (yBegin < 0) yBegin = 0;
			int yEnd = centerY + radY + 1;
			if (yEnd >= grid.getGridHeight()) yEnd = grid.getGridHeight();
			
			for (int y = yBegin; y < yEnd; ++y) {
				visible[x, y] = true;
			}
		}
	}
	
	private void createFog(GridPos pos) {
		GameObject fog = (GameObject)MonoBehaviour.Instantiate(GameAssets.get().getFog());
		fog.transform.position = pos.getWorldPosition();
		fogOfWar.Add(fog);
	}
	
	private void createHighlight(GridPos pos) {
		GameObject h = (GameObject)MonoBehaviour.Instantiate(GameAssets.get().getHighlight());
		h.transform.position = pos.getWorldPosition();
		highlight.Add(h);
	}
	
	private void updateTileBlockedRenderMode() {
		for (int x = 0; x < grid.getGridWidth(); ++x) {
			for (int y = 0; y < grid.getGridHeight(); ++y) {
				Tile tile = grid.getTile(new GridPos(x, y));
				tile.setBlockedRenderMode(selectedUnit != null ? !selectedUnit.canWalkOnTile(tile) : true);
			}
		}
	}
}
