using UnityEngine;
using System.Collections;

public enum LevelType{
	Floor 		= 0,
	Wall  		= 1,
	Abyss  		= 2,//No floor - fire and throw only
	ClosedDoor  = 3, 
	OpenedDoor  = 4
}

public class Level : MonoBehaviour {
	
	public static Level instance;
	
	public const float TURN_TIME = 0.15f;
	public static float turnDelta = 0;
	public static float delta16 = 0;
	public static float delta8 = 0;
	
	struct TileRect {
		public int minX;
		public int maxX;
		public int minY;
		public int maxY;
	}
	
	public struct LevelCell {
		public int u;
		public int v;
		public LevelType type;
		public Sprite tile;
		public Unit unit;
		public int pathCost;
		public Item[] items;
		public int parameter;
	}
	
	const int LEVEL_X_SIZE = 40;
	const int LEVEL_Y_SIZE = 40;
	TileRect visibleRect;
	TileRect activeRect;
	TileRect clearSpriteRect;
	
	LevelCell[,] cell;
	
	public Vector3 needShift;
	Vector3 shift;
	
	public Unit AddUnit(int x, int y, Unit unit){
		if (InLevelRect(x, y)){
			if (cell[x, y].type == LevelType.Floor)	{
				cell[x, y].unit = unit;
				unit.MovedTo(x, y);
				unit.EndTurn();
				return unit;
			}
		}
		Destroy(unit);
		return null;
	}
	
	public bool isFloor(int x, int y){
		if (!InLevelRect(x, y)) {return false;}
		return cell[x, y].type == LevelType.Floor;
	}
	
	public void AddItems(int x, int y, Item[] items){
		if (InLevelRect(x, y)) {
			cell[x, y].items = items;
			if (items != null && cell[x, y].type == LevelType.Floor){
				if (items.Length > 0){
					if (items[0] != null && cell[x, y].tile != null) {
						items[0].ShowIcon(cell[x, y].tile.transform, new Vector3(0, 0, -0.5f));
					}
				}
			}
		}
	}
	
	public void RefreshItems(){
		for(int i = activeRect.minX; i < activeRect.maxX; i++){
			for(int j = activeRect.minY; j < activeRect.maxY; j++){
				if (cell[i, j].items != null && cell[i, j].tile != null) {
					if (cell[i, j].items.Length > 0){
						if (cell[i, j].items[0] != null) {
							cell[i, j].items[0].ShowIcon(cell[i, j].tile.transform, new Vector3(0, 0, -0.5f));
						}
					}
				}
			}
		}
	}
	
	public Item[] GetItemsOnCell(int x, int y){
		if (InLevelRect(x, y)) {
			return cell[x, y].items;
		}
		return null;
	}
	public void SetItemsOnCell(int x, int y, Item[] items){
		if (InLevelRect(x, y)) {
			cell[x, y].items = items;
		}
	}
	
	void CreateLevel() {
		//cell = LevelGenerator.CreateDefaultLevel(LEVEL_X_SIZE, LEVEL_Y_SIZE, 12);
		cell = LevelGenerator.GenerateLevel(LEVEL_X_SIZE, LEVEL_Y_SIZE, Random.Range(0, 200));
	}
	
	Direction[] path;
	int pathLength;
	
	public Direction[] FindPath(Coordinate fromPoint, Coordinate toPoint) {
		if (fromPoint.x == toPoint.x && fromPoint.y == toPoint.y)
			return null;
		if (InLevelRect(fromPoint.x, fromPoint.y) && InLevelRect(toPoint.x, toPoint.y)) {
			FindActiveRect();
			ClearPathMarkers();
			MarkPath(toPoint.x, toPoint.y, fromPoint, toPoint, 0);
			if (cell[fromPoint.x, fromPoint.y].pathCost != 999) {
				path = new Direction[cell[fromPoint.x, fromPoint.y].pathCost];
				pathLength = cell[fromPoint.x, fromPoint.y].pathCost - 1;
				AddNearestNode(fromPoint.x, fromPoint.y, cell[fromPoint.x, fromPoint.y].pathCost);
				//for(int i = 0; i < path.Length; i++)
				//	Debug.Log(path[i].ToString());
				return path;
			}
		}
		return null;
	}
	
	bool AddNearestNode(int x, int y, int cost){
		if (!InLevelRect(x, y))
			return false;
		if (cell[x, y].pathCost == cost){
			if (cost == 0){
				return true;
			}
			if (AddNearestNode(x, y + 1, cost - 1)){
				path[pathLength] = Direction.up;
				pathLength --;
				return true;
			} else 
			if (AddNearestNode(x, y - 1, cost - 1)){
				path[pathLength] = Direction.down;
				pathLength --;
				return true;
			} else 
			if (AddNearestNode(x + 1, y, cost - 1)){
				path[pathLength] = Direction.right;
				pathLength --;
				return true;
			} else 
			if (AddNearestNode(x - 1, y, cost - 1)){
				path[pathLength] = Direction.left;
				pathLength --;
				return true;
			} else	
				return false;
		} else
			return false;
	}
	
	void MarkPath(int x, int y, Coordinate startPoint, Coordinate endPoint, int cost) {
		if (x >= activeRect.minX && x < activeRect.maxX &&
		    y >= activeRect.minY && y < activeRect.maxY) {
			if (cost < cell[x, y].pathCost 
				&& (cell[x, y].type == LevelType.Floor || (cell[x, y].type == LevelType.ClosedDoor))
				&& (cell[x, y].unit == null || (x == startPoint.x && y == startPoint.y) || (x == endPoint.x && y == endPoint.y))) {
				cell[x, y].pathCost = cost;
				if (!(x == startPoint.x && y == startPoint.y) && cost < 40) {
					MarkPath(x, y + 1, startPoint, endPoint, cost + 1);
					MarkPath(x, y - 1, startPoint, endPoint, cost + 1);
					MarkPath(x + 1, y, startPoint, endPoint, cost + 1);
					MarkPath(x - 1, y, startPoint, endPoint, cost + 1);
				}
			}
		}
	}
	
	void ClearPathMarkers() {
		for(int i = activeRect.minX; i < activeRect.maxX; i++){
			for(int j = activeRect.minY; j < activeRect.maxY; j++){
				cell[i, j].pathCost = 999;
			}
		}
	}
	
	Sprite CreateTile(int x, int y, int u, int v) {
		return Tile.CreateTile(transform, new Coordinate(u, v), new Vector3(x, y, 2f) * Tile.TILE_SIZE);
	}
	
	void Awake () {
		instance = this;
		TextEffect.Init(transform, 12);
		needShift = new Vector2(0, 0);
		
		CreateLevel();
	}
	
	public Coordinate GetCursorPosition(Vector3 mouse) {
		Vector3 floatCoord = (mouse - (Vector3)transform.localPosition)
			     * 1 / Tile.TILE_SIZE + Vector3.one * 0.5f;
		return new Coordinate((int)Mathf.Floor(floatCoord.x), (int)Mathf.Floor(floatCoord.y));
	}
	
	void Update () {
		shift = shift * 0.95f + needShift * 0.05f;
		Vector3 truncated = shift;
		truncated.x = Mathf.Floor(truncated.x / 0.1f) * 0.1f;
		truncated.y = Mathf.Floor(truncated.y / 0.1f) * 0.1f;
		truncated.z = Mathf.Floor(truncated.z / 0.1f) * 0.1f;
		transform.localPosition = -truncated;
		FindVisibleRect();
		CountDelta();
		//TODO
		if (Control.activeTouchCount > 0){
			Vector2 floatCoord = (Control.activeTouches[0].position - (Vector2)transform.localPosition)
			           * 1 / Tile.TILE_SIZE + Vector2.one * 0.5f;
			Coordinate coord = new Coordinate((int)Mathf.Floor(floatCoord.x), (int)Mathf.Floor(floatCoord.y));
			if (InLevelRect(coord.x, coord.y)) {
				Trace.Set(coord.x.ToString() + " " + coord.y.ToString() + " " + cell[coord.x, coord.y].pathCost.ToString());
			}
		}
			/*Trace.Set(
			          (
			           (Control.activeTouches[0].position - (Vector2)transform.localPosition)
			           * 1 / Level.TILE_SIZE - Vector2.one
			           ).ToString());*/
	}
	
	void CountDelta() {
		if (turnDelta > 0) {
			turnDelta -= Time.deltaTime;
			delta16 = Mathf.Round(Level.turnDelta * 16 / Level.TURN_TIME) / 16f;
			delta8 = Mathf.Round(Level.turnDelta * 8 / Level.TURN_TIME) / 8f;
			if (turnDelta <= 0) {
				turnDelta = 0;
				EndTurn();
				delta16 = 1;
				delta8 = 1;
			}
		}
	}
	
	public void MakeTurn(){
		if (turnDelta == 0){
			FindActiveRect();
			for(int i = activeRect.minX; i < activeRect.maxX; i++){
				for(int j = activeRect.minY; j < activeRect.maxY; j++){
					if (cell[i, j].unit != null) {
						cell[i, j].unit.StartTurn();
					}
				}
			}
			for(int i = activeRect.minX; i < activeRect.maxX; i++){
				for(int j = activeRect.minY; j < activeRect.maxY; j++){
					if (cell[i, j].unit != null) {
						TryMoveUnit(i, j);
					}
				}
			}
			turnDelta = TURN_TIME;
			Level.instance.RefreshItems();
		}
	}
	
	void EndTurn(){
		for(int i = activeRect.minX; i < activeRect.maxX; i++){
			for(int j = activeRect.minY; j < activeRect.maxY; j++){
				if (cell[i, j].unit != null) {
					cell[i, j].unit.EndTurn();
				}
			}
		}
	}
	
	void MoveUnit(int fromX, int fromY, int toX, int toY){
		if (InLevelRect(toX, toY)) {
			if (cell[toX, toY].unit == null &&
			    cell[toX, toY].type == LevelType.Floor)
			{
				cell[toX, toY].unit = cell[fromX, fromY].unit;
				cell[fromX, fromY].unit = null;
				cell[toX, toY].unit.MovedTo(toX, toY);
				cell[toX, toY].unit.action = Action.none;
			} else {
				if (cell[toX, toY].unit != null &&
			   		cell[toX, toY].type == LevelType.Floor) {
					cell[fromX, fromY].unit.action = Action.none;
					cell[fromX, fromY].unit.AttackUnit(cell[toX, toY].unit);
				}				
				if (cell[toX, toY].type == LevelType.ClosedDoor) {
					if (Hero.instance == cell[fromX, fromY].unit) {
						if (Hero.instance.HaveKey(cell[fromX, fromY].parameter)){
							Debug.Log("door");
							cell[toX, toY].type = LevelType.Floor;
						}
					}
				}			
			}
		}
	}
	
	void TryMoveUnit(int x, int y) {
		if (InLevelRect(x, y)) {
			if (cell[x, y].unit.action == Action.move) {
				switch(cell[x, y].unit.direction){
				case Direction.up:
					MoveUnit(x, y, x, y + 1);
					break;
				case Direction.down:
					MoveUnit(x, y, x, y - 1);
					break;
				case Direction.left:
					MoveUnit(x, y, x - 1, y);
					break;
				case Direction.right:
					MoveUnit(x, y, x + 1, y);
					break;
				}
			}
		}
	}
	
	void FindActiveRect() {
		activeRect.minX = (int)((needShift.x - 30) / Tile.TILE_SIZE);
		activeRect.minY = (int)((needShift.y - 30) / Tile.TILE_SIZE);
		activeRect.maxX = (int)((needShift.x + 30) / Tile.TILE_SIZE);
		activeRect.maxY = (int)((needShift.y + 30) / Tile.TILE_SIZE);	
		activeRect = Crop (activeRect);
	}
		
	void FindVisibleRect() {
		visibleRect.minX = (int)((needShift.x - 16 - 20) / Tile.TILE_SIZE);
		visibleRect.minY = (int)((needShift.y - 12 - 20) / Tile.TILE_SIZE);
		visibleRect.maxX = (int)((needShift.x + 18 + 20) / Tile.TILE_SIZE);
		visibleRect.maxY = (int)((needShift.y + 14 + 20) / Tile.TILE_SIZE);
		visibleRect = Crop(visibleRect);	
		for(int i = visibleRect.minX; i < visibleRect.maxX; i++){
			for(int j = visibleRect.minY; j < visibleRect.maxY; j++){
				if (cell[i, j].tile == null)
					cell[i, j].tile = CreateTile(i, j, cell[i, j].u, cell[i, j].v);
			}
		}
		clearSpriteRect.minX = visibleRect.minX - 4;
		clearSpriteRect.minY = visibleRect.minY - 4;
		clearSpriteRect.maxX = visibleRect.maxX + 4;
		clearSpriteRect.maxY = visibleRect.maxY + 4;
		clearSpriteRect = Crop(clearSpriteRect);
		for(int i = clearSpriteRect.minX; i < clearSpriteRect.maxX; i++){
			for(int j = clearSpriteRect.minY; j < clearSpriteRect.maxY; j++){
				if (!(i >= visibleRect.minX && i < visibleRect.maxX && j >= visibleRect.minY && j < visibleRect.maxY))
					if (cell[i, j].tile != null)
						Destroy(cell[i, j].tile.gameObject);
			}
		}
	}
	
	public bool InLevelRect(int x, int y){
		return (x >= 0 && y >= 0 && x < LEVEL_X_SIZE && y < LEVEL_Y_SIZE);
	}
	
	TileRect Crop(TileRect rect) {
		if (rect.minX < 0) rect.minX = 0;
		if (rect.minY < 0) rect.minY = 0;
		if (rect.maxX > LEVEL_X_SIZE) rect.maxX = LEVEL_X_SIZE;
		if (rect.maxY > LEVEL_Y_SIZE) rect.maxY = LEVEL_Y_SIZE;	
		return rect;
	}
}
