using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace TBTK{
	
	[System.Serializable]
	public class Wall{
		public Transform objT;
		
		public int nIdx1_x;	public int nIdx2_x;
		public int nIdx1_z;	public int nIdx2_z;
		
		[System.NonSerialized] public Node node1;
		[System.NonSerialized] public Node node2;
		
		public float angle1;	//from n1 to n2
		public float angle2;	//from n2 to n1
		
		public Wall(Transform prefab, Node n1, Node n2){
			node1=n1; node2=n2;	
			nIdx1_x=node1.idxX;	nIdx2_x=node2.idxX;
			nIdx1_z=node1.idxZ;	nIdx2_z=node2.idxZ;
			angle1=GridManager.GetAngle(n1.GetPos(), n2.GetPos(), true);
			angle2=GridManager.GetAngle(n1.GetPos(), n2.GetPos(), true);
			
			Vector3 pos=(n1.GetPos()+n2.GetPos())/2;
			Quaternion rot=Quaternion.LookRotation(n1.GetPos()-n2.GetPos());
			
			#if UNITY_EDITOR
				objT=(Transform)PrefabUtility.InstantiatePrefab(prefab);
				objT.position=pos;	objT.rotation=rot;
			#else
				objT=(Transform)MonoBehaviour.Instantiate(prefab, pos, rot);
			#endif
			
			objT.transform.localScale*=GridManager.GetNodeSize();
			//objT.transform.parent=node1.objT;
			objT.transform.parent=GridGenerator.GetGridObjParent();
			objT.transform.name=node1.GetObjT().name+"_"+objT.transform.name;
			
			if(GridManager.IsHexGrid()){
				objT.transform.localScale=new Vector3(objT.transform.localScale.x*0.5f, objT.transform.localScale.y, objT.transform.localScale.x*0.5f);
			}
		}
		
		public void SetupNode(){
			if(node1==null) node1=GridManager.GetNode(nIdx1_x, nIdx1_z);
			if(node2==null) node2=GridManager.GetNode(nIdx2_x, nIdx2_z);
		}
		
		public float GetAngle1(Node node){
			if(node==node1) return angle1;
			else if(node==node2) return angle2;
			return 0;
		}
		
		public bool CheckContainNode(Node node){
			return node1==node | node2==node;
		}
		
		public void Remove(){
			node1.wallList.Remove(this);
			node2.wallList.Remove(this);
			if(objT!=null) MonoBehaviour.DestroyImmediate(objT.gameObject);
			else Debug.Log("wall has no obj? error?");
		}
	}
	
	[System.Serializable]
	public class Node{
		public int idx;		public int idxX;	public int idxZ;
		public int x;		public int y;		public int z;		//for hex node, to calculate distance
		public Transform objT;
		public Renderer rend;
		
		public Transform GetObjT(){ return objT; }
		
		public bool walkable=true;
		
		public Vector3 pos;
		public Vector3 GetPos(){ return objT!=null ? objT.position : pos ; }
		
		public Transform obstacleT;
		
		public Unit unit;
		
		public Collectible collectible;
		
		[HideInInspector] public Node abLineParent;		//for line type ability, refering to the final node in the line
		
		public bool IsEmpty(){	//for checking before placing any item 
			return unit==null && collectible==null && !HasObstacle();
		}
		
		//~ public int floorID=-1;
		//~ public int wallID=-1;
		
		//~ public FloorPrefab floorPrefab;
		//~ public void InitiateFloorPrefab(){ if(walkable && objT!=null) floorPrefab=objT.GetComponent<FloorPrefab>(); }
		//~ public bool CanPlaceHostile(){ return floorPrefab!=null ? floorPrefab.enableHostile : false ; }
		//~ public bool CanPlaceCollectible(){ return floorPrefab!=null ? floorPrefab.enableCollectible : false ; }
		//~ public bool CanPlaceDTerrain(){ return floorPrefab!=null ? floorPrefab.enableDTerrain : false ; }
		
		
		public Node(int i, int iX, int iZ, Vector3 p){
			idx=i; idxX=iX; idxZ=iZ; 
			pos=p;
		}
		
		public void DeleteObject(){
			if(objT==null) return;
			MonoBehaviour.DestroyImmediate(objT.gameObject);
		}
		
		//public bool IsBlocked(Node node, bool ignoreUnit=false, bool ignoreObs=false){ 
		public bool IsBlocked(Node node, int ignoreUnit=-1, bool ignoreObs=false){ 
			if(!walkable) return true;
			
			if(!ignoreObs && obstacleT!=null) return true;
			//if(!ignoreUnit && unit!=null) return true;
			
			if(AStar.CantBypassUnit(ignoreUnit) && unit!=null) return true;
			if(AStar.BypassFriendlyUnit(ignoreUnit) && unit!=null && unit.facID!=ignoreUnit) return true;
			
			//check for wall 
			if(!ignoreObs){
				for (int i=0; i<wallList.Count; i++){
					if(wallList[i].CheckContainNode(node)) return true; 
				}
			}
			
			if(GridManager.IsSquareGrid() && GridManager.EnableDiagonalNeighbour()){
				if(GetPos().x!=node.GetPos().x && GetPos().z!=node.GetPos().z){
					List<Node> tgtNeighbours=node.GetNeighbourList();
					List<Node> adjNeighbours=new List<Node>();
					for(int i=0; i<tgtNeighbours.Count; i++){
						if(neighbours.Contains(tgtNeighbours[i])) adjNeighbours.Add(tgtNeighbours[i]);
					}
					
					//Debug.Log("IsBlocked  "+adjNeighbours.Count);
					
					int blockedCount=0;
					for(int i=0; i<adjNeighbours.Count; i++){
						if(adjNeighbours[i].IsBlocked(this, ignoreUnit, ignoreObs)) blockedCount+=1;
					}
					return blockedCount==adjNeighbours.Count;
				}
			}
			
			return false;
		}
		
		
		
		
		
		public bool HasObstacle(){ return obstacleT!=null; }
		public void RemoveObstacle(){
			MonoBehaviour.DestroyImmediate(obstacleT.gameObject);
		}
		public void AddObstacle(Transform prefab){
			if(obstacleT!=null) RemoveObstacle();
			
			#if UNITY_EDITOR
				obstacleT=(Transform)PrefabUtility.InstantiatePrefab(prefab);
				obstacleT.position=GetPos();
			#else
				obstacleT=(Transform)MonoBehaviour.Instantiate(prefab, GetPos(), Quaternion.identity);
			#endif
			
			obstacleT.transform.localScale*=GridManager.GetNodeSize();
			//obstacleT.transform.parent=objT;
			obstacleT.transform.parent=GridGenerator.GetGridObjParent();
			obstacleT.transform.name=objT.name+"_"+obstacleT.transform.name;
		}
		
		
		#region wall
		public List<Wall> wallList=new List<Wall>();
		
		public void SetupWall(){
			for(int i=0; i<wallList.Count; i++) wallList[i].SetupNode();
		}
		
		public int CheckWall(Vector3 point){
			Node neighbour=GetNeighbourFromPos(point);
			if(neighbour==null) return -100;
			if(neighbour.HasObstacle()) return -100;
			
			for (int i=0; i<wallList.Count; i++){
				if(wallList[i].CheckContainNode(neighbour)) return i; 
				//if(wallList[i].GetAngle(this)==angle) return false; 
			}
			
			return -1;
		}
		public void AddWall(Vector3 point, Transform prefab){
			if(CheckWall(point)==-1){
				Node neighbour=GetNeighbourFromPos(point);
				Wall wall=new Wall(prefab, this, neighbour);
				wallList.Add(wall);	neighbour.wallList.Add(wall);
			}
		}
		public void RemoveWall(Vector3 point){
			int idx=CheckWall(point);
			if(idx>=0) wallList[idx].Remove();
		}
		
		public bool HasWall(){ return wallList.Count>0; }
		#endregion
		
		
		#region LOS/Fog-of-War
		private bool visible=true;
		private int scanned=0;
		
		private MeshRenderer objRend;
		
		public bool IsVisible(bool debug=false){ 
			if(debug) Debug.Log(" ----------------------  "+visible+"   "+scanned+"   "+objT.gameObject.activeInHierarchy);
			return !GameControl.EnableFogOfWar() | visible | scanned>0 ; }
		
		public void SetVisible(bool flag){
			visible=flag;
			
			//if(rend==null) rend=objT.gameObject.GetComponent<Renderer>();
			//rend.enabled=visible;
			
			UpdateVisibility();
		}
		public void UpdateVisibility(){
			if(objT==null) Debug.Log("objT is null   "+idxX+"   "+idxZ);
			
			if(GridManager.UseIndividualCollider()){
				if(objRend==null) objRend=objT.GetComponent<MeshRenderer>();
				objRend.enabled=IsVisible();
			}
			else{
				if(objT!=null) objT.gameObject.SetActive(IsVisible());
			}
			
			if(unit!=null){
				Utility.SetLayerRecursively(unit.transform, IsVisible() ? TBTK.GetLayerUnit() : TBTK.GetLayerInvisible());
			}
			if(collectible!=null){
				Utility.SetLayerRecursively(collectible.transform, IsVisible() ? 0 : TBTK.GetLayerInvisible());
			}
		}
		
		
		public void RevealFogOfWar(int duration){
			GridManager.AddScannedNode(this);
			scanned=Mathf.Max(scanned, duration);
			if(!visible) UpdateVisibility();
		}
		public bool IterateFogOfWarCD(){
			scanned-=1;
			if(scanned<=0) UpdateVisibility();
			return scanned<=0;
		}
		#endregion
		
		
		#region cover
		public List<int> coverList=new List<int>();
		
		public void InitCover(){
			coverList=new List<int>{ 0, 0, 0, 0 };
			if(GridManager.IsHexGrid()){ coverList.Add(0);	coverList.Add(0); }
			
			Vector3 offset=new Vector3(0, 0.1f, 0);
			
			int count=GridManager.IsHexGrid() ? 6 : 4 ;
			int angleStart=GridManager.IsHexGrid() ? 30 : 0 ;
			int angleStep=GridManager.IsHexGrid() ? 60 : 90 ;
			
			for(int i=0; i<count; i++){
				int angle=angleStart+(i*angleStep);
				Node neighbour=GetNeighbourOfAngle(angle);
				
				if(neighbour==null) continue;
				if(!neighbour.IsBlocked(this)) continue;
				
				Vector3 dir=(neighbour.GetPos()-GetPos()).normalized;
				//float dist=GridManager.GetNodeSize();//*GridManager.GetGridToTileSizeRatio()*.75f;
				LayerMask mask=1<<TBTK.GetLayerObsFullCover() | 1<<TBTK.GetLayerObsHalfCover();	RaycastHit hit;
				
				bool flag=Physics.Raycast(GetPos()+offset, dir, out hit, GridManager.GetNodeSize(), mask);
				
				if(flag) coverList[i]=hit.transform.gameObject.layer==TBTK.GetLayerObsFullCover() ? 2 : 1 ;
			}
		}
		
		public int HasCover(int count=0){
			for(int i=0; i<coverList.Count; i++){ if(coverList[i]>0) count+=1; }
			return count;
		}
		
		public const float effectiveCoverAngle=90;
		public int GetCover(float angle){
			int cover=0;
			if(GridManager.IsHexGrid()){
				if(angle>0 && angle<60)				cover=coverList[0];
				else if(angle==60)						cover=Mathf.Max(coverList[0], coverList[1]);
				else if(angle>60 && angle<120)	cover=coverList[1];
				else if(angle==120)					cover=Mathf.Max(coverList[1], coverList[2]);
				else if(angle>120 && angle<180)	cover=coverList[2];
				else if(angle==180)					cover=Mathf.Max(coverList[2], coverList[3]);
				else if(angle>180 && angle<240)	cover=coverList[3];
				else if(angle==240)					cover=Mathf.Max(coverList[3], coverList[4]);
				else if(angle>240 && angle<300)	cover=coverList[4];
				else if(angle==300)					cover=Mathf.Max(coverList[4], coverList[5]);
				else if(angle>300 && angle<360)	cover=coverList[5];
				else if(angle==360 || angle==0)	cover=Mathf.Max(coverList[5], coverList[0]);
				
				//~ if(angle<30) 									cover=Mathf.Max(coverList[5], coverList[0]);
				//~ else if(angle==30)							cover=coverList[0];
				//~ else if(angle>30 && angle<90)			cover=Mathf.Max(coverList[0], coverList[1]);
				//~ else if(angle==90)							cover=coverList[1];
				//~ else if(angle>90 && angle<150)		cover=Mathf.Max(coverList[1], coverList[2]);
				//~ else if(angle==150)						cover=coverList[2];
				//~ else if(angle>150 && angle<210)		cover=Mathf.Max(coverList[2], coverList[3]);
				//~ else if(angle==210)						cover=coverList[3];
				//~ else if(angle>210 && angle<270)		cover=Mathf.Max(coverList[3], coverList[4]);
				//~ else if(angle==270)						cover=coverList[4];
				//~ else if(angle>270 && angle<330)		cover=Mathf.Max(coverList[4], coverList[5]);
				//~ else if(angle==330)						cover=coverList[5];
				//~ else if(angle>330)							cover=Mathf.Max(coverList[5], coverList[0]);
			}
			else if(GridManager.IsSquareGrid()){
				if(angle>0 && angle<90) 					cover=Mathf.Max(coverList[0], coverList[1]);
				else if(angle==90)							cover=coverList[1];
				else if(angle>90 && angle<180)		cover=Mathf.Max(coverList[1], coverList[2]);
				else if(angle==180)						cover=coverList[2];
				else if(angle>180 && angle<270)		cover=Mathf.Max(coverList[2], coverList[3]);
				else if(angle==270)						cover=coverList[3];
				else if(angle>270 && angle<360)		cover=Mathf.Max(coverList[3], coverList[0]);
				else if(angle==0 || angle==360)		cover=coverList[0];
			}
			return cover;
		}
		#endregion
		
		
		#region deploy and spawn-group
		public int deployFacID=-1;
		public int spawnGroupFacID=-1;
		public int spawnGroupID=-1;
		
		public void SetDeploymentFac(int facID){ deployFacID=facID; }
		public void ClearDeploymentFac(){ SetDeploymentFac(-1); }
		
		public void SetSpawnGroup(int facID, int groupID){
			spawnGroupFacID=facID;
			spawnGroupID=groupID;
		}
		public void ClearSpawnGroup(){ SetSpawnGroup(-1, -1); }
		#endregion
		
		
		#region A* and neighbour
		public float scoreG;
		public float scoreF;
		public float tempScoreG=0;
		public Node parent;
		
		[System.NonSerialized] private List<Node> neighbours=new List<Node>();
		private List<float> neighboursAngle=new List<float>();
		
		public enum _ListState{Unassigned, Open, Close};
		public _ListState listState=_ListState.Unassigned;
		
		public void ResetListState(){ listState=_ListState.Unassigned; }
		
		//public List<Node> GetNeighbourList(bool walkableOnly=false, bool allowUnit=false, bool allowObj=false){ 
		public List<Node> GetNeighbourList(bool walkableOnly=false, int allowUnit=-1, bool allowObs=false){ 
			if(neighbours==null) SetupNeighbour();
			List<Node> newList=new List<Node>();
			if(walkableOnly){
				for(int i=0; i<neighbours.Count; i++){
					if(!neighbours[i].IsBlocked(this, allowUnit, allowObs)) newList.Add(neighbours[i]);
				}
			}
			else{
				for(int i=0; i<neighbours.Count; i++) newList.Add(neighbours[i]);
				//for(int i=0; i<disconnectedNeighbourList.Count; i++) newList.Add(disconnectedNeighbourList[i]);
			}
			return newList;
		}
		
		public Node GetNeighbourFromPos(Vector3 pos){
			return GetNeighbourOfAngle((int)GridManager.GetAngle(GetPos(), pos, true));
		}
		public Node GetNeighbourOfAngle(int angle){
			for(int i=0; i<neighboursAngle.Count; i++){
				if(neighboursAngle[i]==angle) return neighbours[i];
			}
			return null;
		}
		
		public void SetupNeighbour(){
			neighboursAngle=new List<float>();
			neighbours=GetVerticalNeighbour(GetHorizontalNeighbour(new List<Node>()));
			for(int i=0; i<neighbours.Count; i++){
				if(neighbours[i]!=null){
					neighboursAngle.Add(GridManager.GetAngle(this, neighbours[i], true));
					continue;
				}
				neighbours.RemoveAt(i);	i-=1;
			}
			
			if(GridManager.IsSquareGrid() && GridManager.EnableDiagonalNeighbour()){
				AddDiagonalNeighbour(1, 1);
				AddDiagonalNeighbour(1, -1);
				AddDiagonalNeighbour(-1, -1);
				AddDiagonalNeighbour(-1, 1);
			}
		}
		public void AddDiagonalNeighbour(int offsetX, int offsetZ){
			Node nn=GridManager.GetNode(idxX+offsetX, idxZ+offsetZ);
			if(nn!=null){
				neighbours.Add(nn);
				neighboursAngle.Add(GridManager.GetAngle(this, nn, true));
			}
		}
		public List<Node> GetHorizontalNeighbour(List<Node> list){
			if(GridManager.GetGridType()==_GridType.HexGrid){
				if(idxX>0){
					list.Add(GridManager.GetNode(idxX-1, idxZ));
					if(idxX%2==0) 	list.Add(GridManager.GetNode(idxX-1, idxZ+1));
					else					list.Add(GridManager.GetNode(idxX-1, idxZ-1));
				}
				if(idxX<GridManager.DimensionX()-1){
					list.Add(GridManager.GetNode(idxX+1, idxZ));
					if(idxX%2==0) 	list.Add(GridManager.GetNode(idxX+1, idxZ+1));
					else					list.Add(GridManager.GetNode(idxX+1, idxZ-1));
				}
			}
			else{
				if(idxX>0) 										list.Add(GridManager.GetNode(idxX-1, idxZ));
				if(idxX<GridManager.DimensionX()-1) 	list.Add(GridManager.GetNode(idxX+1, idxZ));
			}
			return list;
		}
		public List<Node> GetVerticalNeighbour(List<Node> list){
			if(idxZ>0) 										list.Add(GridManager.GetNode(idxX, idxZ-1));
			if(idxZ<GridManager.DimensionZ()-1) 	list.Add(GridManager.GetNode(idxX, idxZ+1));
			return list;
		}
		
		
		public List<Node> ProcessNeighbour(Vector3 pos, int ignoreUnit, bool ignoreObs){
			//List<Node> neighbours=null;			//List<Node> neighbours=SetupNeighbour();
			
			for(int i=0; i<neighbours.Count; i++){
				if(neighbours[i].IsBlocked(this, ignoreUnit, ignoreObs)) continue;
				
				//if the neightbour state is clean (never evaluated so far in the search)
				if(neighbours[i].listState==_ListState.Unassigned){
					//check the score of G and H and update F, also assign the parent to currentNode
					neighbours[i].scoreG=scoreG+GridManager.GetNodeSize();	//neighbourCost[i];
					//neighbours[i].scoreH=LevelGenerator.GetNodeSize();	//Vector3.Distance(neighbours[i].pos, pos);
					neighbours[i].UpdateScoreF();		neighbours[i].parent=this;
				}
				//if the neighbour state is open (it has been evaluated and added to the open list)
				else if(neighbours[i].listState==_ListState.Open){
					//calculate if the path if using this neighbour node through current node would be shorter compare to previous assigned parent node
					tempScoreG=scoreG+GridManager.GetNodeSize();	//neighbourCost[i];
					if(neighbours[i].scoreG>tempScoreG){
						//if so, update the corresponding score and and reassigned parent
						neighbours[i].scoreG=tempScoreG;
						neighbours[i].UpdateScoreF();	neighbours[i].parent=this;
					}
				}
			}
			
			return neighbours;
		}
		
		void UpdateScoreF(){ scoreF=scoreG+GridManager.GetNodeSize(); }//scoreH; }
		#endregion
	}
	
}
