using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using System;
using System.IO;

public class MapManager : MonoBehaviour {

	private static MapManager _instance = null;
	public static MapManager Instance { get { return _instance; } }

	public static readonly Color DefaultTileColor = Color.white;
	public static readonly Color ObstacleTileColor = Color.yellow;
	public static readonly Color StartTileColor = Color.red;
	public static readonly Color EndTileColor = Color.red;
	public static readonly Color WifiSpawnTileColor = Color.black;
	public static readonly Color WifiPlaceTileColor = Color.blue;
	public static readonly Color PathTileColor = Color.red;

	private GameObject _mapParent;
	private GameObject _obstaclesParent;
	private GameObject _bordersParent;
	public Map MapInstance;

    public GameObject Player;
	public GameObject Zombie;
    public GameObject Portal;
	public GameObject Antena;

	public List<GameObject> ObstaclesPrefabs = new List<GameObject> ();

	void Awake()
	{
		if (_instance == null || _instance != this) {
			_instance = this;
		}
	}

	void Start()
	{

	}


	public void GenerateMap(int borderSize, int obstaclesAmount)
	{
//		for (int i = -borderSize/2; i < borderSize/2; i++) {
//			for (int j = -borderSize/2; j < borderSize/2; j++) {
//				GameObject mapTile = GameObject.CreatePrimitive (PrimitiveType.Cube);
//				mapTile.transform.position = new Vector3 (i, 0.0f, j);
//				mapTile.name = String.Format ("{0}.{1}", i, j);
//				mapTile.transform.SetParent (MapParent.transform);
//
//			}
//		}

		if (MapInstance != null) {
			MapInstance = null;
		}

		MapInstance = new Map();
		MapInstance.borderSize = borderSize;
		MapInstance.mapGrid = new Map.Tile[borderSize * borderSize];
		int index = 0;
		for (int i = 0; i < borderSize; i++) {
			for (int j = 0; j < borderSize; j++) {
				MapInstance.mapGrid [index] = new Map.Tile ();
				MapInstance.mapGrid [index].X = i;
				MapInstance.mapGrid [index].Y = j;
				index++;
			}
		}

		SpawnMap (MapInstance);
		//SetMapOverviewCamera (MapInstance);
		GenerateObstacles (obstaclesAmount, MapInstance.Tiles);


		MapTile startPoint;
		MapTile endPoint;
		MapTile wifiSpawnPoint;
		MapTile wifiPlacePoint;

		if (RandomStartEndPoints (MapInstance.Tiles, out startPoint, out endPoint, out wifiSpawnPoint, out wifiPlacePoint)) {
			MapInstance.StartPoint = startPoint.Coordinates;
			MapInstance.EndPoint = endPoint.Coordinates;
			MapInstance.WifiSpawnPoint = wifiSpawnPoint.Coordinates;
			MapInstance.WifiPlacePoint = wifiPlacePoint.Coordinates;
			SetTileColors (MapInstance, new List<MapTile> ());
			PlaceObstacles (MapInstance, ObstaclesPrefabs);
			PlaceBorders ();

			List<MapTile> startToWifi = Pathfinder.FindPathAStar (MapInstance.Tiles, MapInstance.StartPoint, MapInstance.WifiSpawnPoint);
			List<MapTile> wifiPath = Pathfinder.FindPathAStar (MapInstance.Tiles, MapInstance.WifiSpawnPoint, MapInstance.WifiPlacePoint);
			List<MapTile> wifiToEnd = Pathfinder.FindPathAStar (MapInstance.Tiles, MapInstance.WifiPlacePoint, MapInstance.EndPoint);

			if ((startToWifi == null || wifiPath == null || wifiToEnd == null) || (wifiPath != null && wifiPath.Count < borderSize)) {

				GenerateMap (borderSize, obstaclesAmount);
				return;
			}

			var navmesh = _mapParent.GetComponent<NavMeshSurface> ();
			if (!navmesh) {
				navmesh = _mapParent.AddComponent<NavMeshSurface> ();
			}

			navmesh.collectObjects = CollectObjects.Children;
			navmesh.BuildNavMesh ();

//			PlaceGameplayObjects ();

		} else {
			GenerateMap (borderSize, obstaclesAmount);
			return;
		}
	}
		

	public void SetTileColors(Map map, List<MapTile> path)
	{
		for (int i = 0; i < map.Tiles.GetLength(0); i++) {
			for (int j = 0; j < map.Tiles.GetLength(1); j++) {
				map.Tiles [i, j].Renderer.material.color = new Color (0.0f, UnityEngine.Random.Range (0.6f, 0.8f), 0.0f);
			}
		}

//		for (int i = 0; i < map.Tiles.GetLength(0); i++) {
//			for (int j = 0; j < map.Tiles.GetLength(1); j++) {
//				map.Tiles[i, j].Renderer.material.color = map.Tiles[i, j].IsObstacle ? ObstacleTileColor : DefaultTileColor;
//			}
//		}
//
//		if (path != null) {
//			for (int i = 0; i < path.Count; i++) {
//				path [i].Renderer.material.color = PathTileColor;
//			}
//		}

		map.Tiles [map.StartPoint.X, map.StartPoint.Y].Renderer.material.color = StartTileColor;
		map.Tiles [map.EndPoint.X, map.EndPoint.Y].Renderer.material.color = EndTileColor;
		map.Tiles [map.WifiSpawnPoint.X, map.WifiSpawnPoint.Y].Renderer.material.color = WifiSpawnTileColor;
		map.Tiles [map.WifiPlacePoint.X, map.WifiPlacePoint.Y].Renderer.material.color = WifiPlaceTileColor;
	}

	public static MapTile GetTileSafe(MapTile[,] map, int x, int y)
	{
		if (x >= 0 && x < map.GetLength (0) && y >= 0 && y < map.GetLength (1)) {
			return map [x, y];
		}
		return null;
	}

	public static List<MapTile> GetNeighbours(MapTile[,] map, Map.Vector2Int tileCoords)
	{
		List<MapTile> neighbours = new List<MapTile> ();

		Vector2[] neighboursCoords = new Vector2[] { 
			new Vector2 (tileCoords.X + 1, tileCoords.Y ),
			new Vector2 (tileCoords.X - 1, tileCoords.Y ),
			new Vector2 (tileCoords.X, tileCoords.Y  + 1),
			new Vector2 (tileCoords.X, tileCoords.Y - 1)
		};

		for (int i = 0; i < neighboursCoords.Length; i++) {
			MapTile tile = GetTileSafe (map, (int)neighboursCoords [i].x, (int)neighboursCoords [i].y);
			if (tile != null) {
				neighbours.Add (tile);
			}
		}
		return neighbours;
	}

	private void SpawnMap(Map map)
	{
		if (_mapParent == null) {
			_mapParent = new GameObject ();
			_mapParent.name = "Map";
		}

		for (int i = _mapParent.transform.childCount - 1; i >= 0; i--) {
			var child = _mapParent.transform.GetChild (i).transform;
			#if UNITY_EDITOR
			DestroyImmediate (child.gameObject);
			#else
			Destroy (child.gameObject);
			#endif
		}

		map.Tiles = new MapTile[map.borderSize, map.borderSize];
		int x = 0;
		int y = 0;
		for (int index = 0; index < map.Tiles.Length; index++) {
			x = map.mapGrid [index].X;
			y = map.mapGrid [index].Y;
			GameObject tileGO = GameObject.CreatePrimitive (PrimitiveType.Cube);
			tileGO.transform.SetParent (_mapParent.transform);
			tileGO.name = string.Format ("X{0}Y{1}", x, y);
			tileGO.AddComponent<BoxCollider> ();
			MapTile tile = tileGO.AddComponent<MapTile> ();
			tile.Init (x, y);
			tile.IsObstacle = map.mapGrid [index].IsObstacle;
			tileGO.transform.position = new Vector3 (tile.Coordinates.X, 0.0f, tile.Coordinates.Y);
			map.Tiles[x, y] = tile;
		}
	}

	private List<MapTile> GenerateObstacles(int amount, MapTile[,] map)
	{
		List<MapTile> obstacles = new List<MapTile> ();

//		List<MapTile> case1 = new List<MapTile> ();
//		List<MapTile> case2 = new List<MapTile> ();
//		List<MapTile> case3 = new List<MapTile> ();


		if (amount > 0 && map != null) {

			List<MapTile> notDrawn = new List<MapTile> ();

			for (int i = 0; i < map.GetLength(0); i++) {
				for (int j = 0; j < map.GetLength(1); j++) {
					notDrawn.Add (map [i, j]);
				}
			}

			while (amount > 0 && notDrawn.Count > 2) {
				MapTile current = notDrawn [UnityEngine.Random.Range (0, notDrawn.Count)];
				notDrawn.Remove (current);

				List<MapTile> neighbours = GetNeighbours (map, current.Coordinates);
				neighbours.RemoveAll (x => x.IsObstacle);

				current.IsObstacle = true;

				int drawCase = UnityEngine.Random.Range (0, neighbours.Count);

				switch (drawCase) {
				case 0:
					break;
				case 1:
					neighbours [0].IsObstacle = true;
					notDrawn.Remove (neighbours [0]);
					obstacles.Add (neighbours [0]);
					//case1.Add (neighbours [0]);
					break;
				default:
					int randomIndex = UnityEngine.Random.Range (0, 1);
					neighbours [randomIndex].IsObstacle = true;
					notDrawn.Remove (neighbours [randomIndex]);
					obstacles.Add (neighbours [randomIndex]);
					//case2.Add (neighbours [randomIndex]);
					break;
//				default:
//					for (int i = 0; i < neighbours.Count; i++) {
//						List<MapTile> neighOfNeigh = GetNeighbours (map, neighbours [i].Coordinates);
//						List<MapTile> common = neighOfNeigh.FindAll (x => /*GetNeighbours(map, x.Coordinates).Contains(x) &&*/ neighbours.Contains () && !x.IsObstacle);
//
//						//Debug.Log ("Common count: " + common.Count);
//						if (common.Count > 0) {
//							for (int j = 0; j < common.Count; j++) {
//								common[j].IsObstacle = true;
//								notDrawn.Remove (common[j]);
//								obstacles.Add (common[j]);
//								//case3.Add (common[j]);
//							}
//						} else
//							goto case 2;
//					}
//					break;
				}
				amount--;
			}
		}
		//Debug.Log (String.Format ("Case1: {0} case2: {1} case3: {2}", case1.Count, case2.Count, case3.Count));
		return obstacles;
	}

	private bool RandomStartEndPoints(MapTile[,] map, out MapTile startPoint, out MapTile endPoint, out MapTile wifiSpawnPoint, out MapTile wifiPlacePoint)
	{
		startPoint = null;
		endPoint = null;
		wifiSpawnPoint = null;
		wifiPlacePoint = null;

		HashSet<Map.Vector2Int> drawn = new HashSet<Map.Vector2Int> ();

		if (map != null && map.Length > 1) {
			do {
				int indexX = UnityEngine.Random.Range (0, map.GetLength(0));
				int indexY = UnityEngine.Random.Range (0, map.GetLength(1));

				if(!drawn.Contains(new Map.Vector2Int(indexX, indexY)))
				{
					drawn.Add(new Map.Vector2Int(indexX, indexY));

					if (!map[indexX, indexY].IsObstacle && map [indexX, indexY] != startPoint && map [indexX, indexY] != endPoint && map [indexX, indexY] != wifiSpawnPoint && map [indexX, indexY] != wifiPlacePoint) {

						if(startPoint == null)
						{
							startPoint = map [indexX, indexY];
						} else if(endPoint == null)
						{
							endPoint = map [indexX, indexY];
						}
						else if (wifiSpawnPoint == null)
						{
							wifiSpawnPoint = map [indexX, indexY];
						}
						else if (wifiPlacePoint == null)
						{
							wifiPlacePoint = map [indexX, indexY];
						}
					}
				}

			} while (startPoint == null || endPoint == null || wifiSpawnPoint == null || wifiPlacePoint == null || drawn.Count == map.Length);

			return (startPoint != null && endPoint != null && wifiSpawnPoint != null && wifiPlacePoint != null);
		}
		return false;
	}

	private void PlaceObstacles(Map map, List<GameObject> obstaclesPrefabs)
	{
		if (ObstaclesPrefabs.Count == 0) {
			Debug.LogError("Better fill up ObstaclesPrefabs before placing them.");
			return;
		}

		if (_obstaclesParent == null) {
			_obstaclesParent = new GameObject ();
			_obstaclesParent.name = "Obstacles";
		}

		for (int i = 0; i < _obstaclesParent.transform.childCount; i++) {
			Destroy (_obstaclesParent.transform.GetChild (i).gameObject);
		}

		for (int i = 0; i < map.Tiles.GetLength(0); i++) {
			for (int j = 0; j < map.Tiles.GetLength(1); j++) {
				if (map.Tiles [i, j].IsObstacle) {
					GameObject randomObstacle = obstaclesPrefabs[UnityEngine.Random.Range (0, obstaclesPrefabs.Count)];
					GameObject placedObstacle = Instantiate (randomObstacle, new Vector3 (map.Tiles [i, j].Coordinates.X, 0.5f, map.Tiles [i, j].Coordinates.Y), Quaternion.identity);
					placedObstacle.transform.SetParent (_obstaclesParent.transform);
					GetTileSafe(MapInstance.Tiles, i, j).gameObject.AddComponent<NavMeshObstacle> ();
				}
			}
		}
	}

	private void PlaceBorders()
	{
		if (_bordersParent == null) {
			_bordersParent = new GameObject ();
			_bordersParent.name = "Borders";
		}

		for (int i = 0; i < _bordersParent.transform.childCount; i++) {
			Destroy (_bordersParent.transform.GetChild (i).gameObject);
		}

		GameObject border1 = GameObject.CreatePrimitive (PrimitiveType.Cube);
		border1.transform.SetParent (_bordersParent.transform);
		border1.name = "Border1";
		border1.transform.localScale = new Vector3 (MapInstance.borderSize + 1.0f, 10.0f, 1.0f);
		border1.transform.position = new Vector3 (MapInstance.borderSize / 2.0f - 1, -5.0f, -1.0f);

		GameObject border2 = GameObject.CreatePrimitive (PrimitiveType.Cube);
		border2.transform.SetParent (_bordersParent.transform);
		border2.name = "Border2";
		border2.transform.localScale = new Vector3 (MapInstance.borderSize + 1.0f, 10.0f, 1.0f);
		border2.transform.position = new Vector3 (MapInstance.borderSize / 2.0f, -5.0f, MapInstance.borderSize);

		GameObject border3 = GameObject.CreatePrimitive (PrimitiveType.Cube);
		border3.transform.SetParent (_bordersParent.transform);
		border3.name = "Border3";
		border3.transform.localScale = new Vector3 (1.0f, 10.0f, MapInstance.borderSize + 1.0f);
		border3.transform.position = new Vector3 (-1.0f, -5.0f, MapInstance.borderSize / 2.0f);

		GameObject border4 = GameObject.CreatePrimitive (PrimitiveType.Cube);
		border4.transform.SetParent (_bordersParent.transform);
		border4.name = "Border4";
		border4.transform.localScale = new Vector3 (1.0f, 10.0f, MapInstance.borderSize + 1.0f);
		border4.transform.position = new Vector3 (MapInstance.borderSize, -5.0f, MapInstance.borderSize / 2.0f - 1.0f);

	}

    public void PlaceGameplayObjects(int zombieCount)
	{
        Instantiate (Player, new Vector3 (MapInstance.StartPoint.X, 0.5f, MapInstance.StartPoint.Y), Quaternion.identity);//, _mapParent.transform);

        for (int i = 0; i < zombieCount; i++)
        {
            Instantiate (Zombie, new Vector3 (MapInstance.WifiSpawnPoint.X, 0.5f, MapInstance.WifiSpawnPoint.Y), Quaternion.identity, _mapParent.transform);
        }

        Instantiate (Antena, new Vector3 (MapInstance.WifiSpawnPoint.X, 0.5f, MapInstance.WifiSpawnPoint.Y), Quaternion.identity);//, _mapParent.transform);
        Instantiate (Portal, new Vector3 (MapInstance.EndPoint.X, 0.5f, MapInstance.EndPoint.Y), Quaternion.identity);//, _mapParent.transform);
		
        //MapInstance.StartPoint
		//MapInstance.EndPoint
		//MapInstance.WifiSpawnPoint
		//MapInstance.WifiPlacePoint

	}
}
