using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.LuisPedroFonseca.ProCamera2D;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class TileManager : MonoBehaviour {

    public static TileManager Instance;

	[SerializeField]
    public Tile[,] tiles;
	protected List<Cloud> clouds;

	public uint width;
	public uint height;
    public float xOffset;
    public float yOffset;

    public Tile waterPrefab;
    public Tile earthPrefab;
    public Tile sourcePrefab;
    public Tile sinkPrefab;
	public Tile housePrefab;
	public Tile treePrefab;

	public Cloud cloudPrefab;
	public Cloud stormPrefab;

	public bool generateAtRuntime;
	public Texture2D map;

	private float timer;
	private uint cloudTimer;
	public float simulationSpeed;
	public uint simulationStep;
	public uint stopAt;

	public float maxWaterCurrent;
	public float maxWaterLevel;

	public float sourceModifier;
	public float sinkModifier;

	const float CURRENT_COEF_DIFF = 0.1f;
	const float CURRENT_COEF_NEXT = 0.1f;

    void Awake()
    {
        Instance = this;
    }

	public void Start () {

		clouds = new List<Cloud>();

		cloudTimer = 0;

		simulationStep = 0;
		if(generateAtRuntime){
			GenerateTiles();
		}
		else {
			tiles = new Tile[width,height];
			for(int i = 0; i < transform.childCount;++i){
				Tile t = transform.GetChild(i).GetComponent<Tile>();
				tiles[t.tileX,t.tileY] = t;
			}
		}

        ProCamera2DNumericBoundaries numericBoundaries = ProCamera2D.Instance.GetComponent<ProCamera2DNumericBoundaries>();
        numericBoundaries.BottomBoundary = -1f;
        numericBoundaries.LeftBoundary = -1f;
        numericBoundaries.RightBoundary = width * 0.1f + 1f;
        numericBoundaries.TopBoundary = height * 0.1f + 1f;
	}

	public void FloodStart(){
		cloudTimer = 10000;
	}

	public void spawnCloud(){
		Cloud c;
		float rand_y = Random.value;
		if(GameController.Instance.floodState != GameController.FloodState.STARTED){
            c = Instantiate(cloudPrefab) as Cloud;
			c.transform.position = new Vector3(c.transform.position.x,
				rand_y * yOffset * (height - 0.5f) + yOffset*0.5f,
				c.transform.position.z + rand_y);
		}
		else {
			float rangeY = 0.75f;
			c = Instantiate(stormPrefab) as Cloud;
			c.transform.position = new Vector3(c.transform.position.x,
				rand_y * yOffset * (height * rangeY - 0.5f) + yOffset*(height * (1.0f-rangeY) + 0.5f),
				c.transform.position.z + rand_y);
		}

		c.speed *= 0.60f + Random.value * 0.8f;
		clouds.Add(c);
		c.transform.SetParent(this.transform);
	}

	public void ActivateBarriers(){
		for(uint x = 0; x < width;++x){
			for(uint y = 0; y < height;++y){
				BarrierTile barrier = tiles[x,y] as BarrierTile;
				if(barrier != null){
					barrier.isBarrier = true;
					barrier.eligibleActions.Clear();
                    barrier.OnActivated();
				}
			}
		}
	}

	public void GenerateTiles(){
		if(map != null){
			width = (uint)map.width;
			height = (uint)map.height;
			tiles = new Tile[width,height];
			for(uint x = 0; x < width; ++x){
				for(uint y = 0; y < height;++y){
					Tile newTile;
					Color c = map.GetPixel((int)x,(int)y);
					if(c.b > 0.5f){
						if(c.g > 0.5f){
                            newTile = InstantiatePrefab(sourcePrefab) as Tile;
						}
						else if(c.r > 0.5f){
                            newTile = InstantiatePrefab(sinkPrefab) as Tile;
						}
						else {
                            newTile = InstantiatePrefab(waterPrefab) as Tile;
						}
					}
					else if(c.r > 0.5f){
						if(c.g > 0.5f){
                            newTile = InstantiatePrefab(treePrefab) as Tile;
						}
						else {
                            newTile = InstantiatePrefab(housePrefab) as Tile;
						}
					}
					else {
                        newTile = InstantiatePrefab(earthPrefab) as Tile;
					}
					newTile.gameObject.SetActive(true);
					tiles[x,y] = newTile.GetComponent<Tile>();
					tiles[x,y].tileX = x;
					tiles[x,y].tileY = y;
					newTile.transform.parent = this.transform;
                    newTile.transform.localPosition = new Vector3(x * xOffset,y * yOffset, y * yOffset);
				}
			}
		}
	}

	private void DoWaterPass(uint x,uint y){
		

		if(x < (width - 1) && tiles[x+1,y].isLikeWater){
			tiles[x,y].newWaterCurrentX += tiles[x,y].waterLevel - tiles[x+1,y].waterLevel;
			tiles[x,y].newWaterCurrentX += tiles[x+1,y].waterCurrentX * CURRENT_COEF_NEXT;
			if(tiles[x,y].waterCurrentX > 0.0f) {
				tiles[x,y].waterDiff -= tiles[x,y].waterCurrentX;
				tiles[x+1,y].waterDiff += tiles[x,y].waterCurrentX;
			}
		}
		if(x > 0 && tiles[x-1,y].isLikeWater){
			tiles[x,y].newWaterCurrentX -= tiles[x,y].waterLevel - tiles[x-1,y].waterLevel;
			tiles[x,y].newWaterCurrentX += tiles[x-1,y].waterCurrentX * CURRENT_COEF_NEXT;
			if(tiles[x,y].waterCurrentX < 0.0f){
				tiles[x,y].waterDiff += tiles[x,y].waterCurrentX;
				tiles[x-1,y].waterDiff -= tiles[x,y].waterCurrentX;
			}
		}
		if(y < (height - 1) && tiles[x,y+1].isLikeWater){
			tiles[x,y].newWaterCurrentY += tiles[x,y].waterLevel - tiles[x,y+1].waterLevel;
			tiles[x,y].newWaterCurrentY += tiles[x,y+1].waterCurrentY * CURRENT_COEF_NEXT;
			if(tiles[x,y].waterCurrentY > 0.0f) {
				tiles[x,y].waterDiff -= tiles[x,y].waterCurrentY;
				tiles[x,y+1].waterDiff += tiles[x,y].waterCurrentY;
			}
		}
		if(y > 0 && tiles[x,y-1].isLikeWater){
			tiles[x,y].newWaterCurrentY -= tiles[x,y].waterLevel - tiles[x,y-1].waterLevel;
			tiles[x,y].newWaterCurrentY += tiles[x,y-1].waterCurrentY * CURRENT_COEF_NEXT;
			if(tiles[x,y].waterCurrentY < 0.0f) {
				tiles[x,y].waterDiff += tiles[x,y].waterCurrentY;
				tiles[x,y-1].waterDiff -= tiles[x,y].waterCurrentY;
			}
		}
	}


	private float UpdateTilePrepare(Tile source,Tile sink){
		float result = 0.0f;
		if(source.waterLevel > sink.waterLevel){
			result = source.waterLevel - sink.waterLevel;
		}
		if(sink.isAbsorbing && sink.isLikeWater) result += (sink.waterAbsorption - sink.waterLevel);

		if(source.isTree){
			if(source.tileState == Tile.TileState.NORMAL){
				sink.waterDiff -= 2f;
			}
			else if(source.tileState == Tile.TileState.FLOODED) {
				sink.waterDiff -= 1f;
			}
		}

		return result;
	}

	private void DoPass(uint x,uint y){
		float l=0.0f,r=0.0f,u=0.0f,d=0.0f;
		float dl=0.0f,dr=0.0f,du=0.0f,dd=0.0f;

		if(tiles[x,y].isTree){
			if(tiles[x,y].tileState == Tile.TileState.NORMAL){
				tiles[x,y].waterDiff -= 5f;
			}
			else if(tiles[x,y].tileState == Tile.TileState.FLOODED){
				tiles[x,y].waterDiff -= 2f;
			}
		}

		if(!tiles[x,y].isAbsorbing){
			// Precompute
			if(x > 0){
				// LEFT Tile
				l = UpdateTilePrepare(tiles[x,y],tiles[x-1,y]);
				if(l > 0.0f){
					dl = l;
					//dl = tiles[x,y].waterLevel - tiles[x-1,y].waterLevel;
					//if(tiles[x-1,y].isAbsorbing && tiles[x-1,y].isLikeWater) dl += (tiles[x-1,y].waterAbsorption - tiles[x-1,y].waterLevel);
				}
			}
			if(y > 0){
				// UP Tile
				u = UpdateTilePrepare(tiles[x,y],tiles[x,y-1]);
				if(u > 0.0f){
					du = u;
					//du = tiles[x,y].waterLevel - tiles[x,y-1].waterLevel;
					//if(tiles[x,y-1].isAbsorbing && tiles[x,y-1].isLikeWater) du += (tiles[x,y-1].waterAbsorption - tiles[x,y-1].waterLevel);
				}
			}
			if(x < (width-1)){
				// RIGHT Tile
				r = UpdateTilePrepare(tiles[x,y],tiles[x+1,y]);
				if(r > 0.0f){
					dr = r;
					//dr = tiles[x,y].waterLevel - tiles[x+1,y].waterLevel;
					//if(tiles[x+1,y].isAbsorbing && tiles[x+1,y].isLikeWater) dr += (tiles[x+1,y].waterAbsorption - tiles[x+1,y].waterLevel);
				}
			}
			if(y < (height-1)){
				// DOWN Tile
				d = UpdateTilePrepare(tiles[x,y],tiles[x,y+1]);
				if(d > 0.0f){
					dd = d;
					//dd = tiles[x,y].waterLevel - tiles[x,y+1].waterLevel;
					//if(tiles[x,y+1].isAbsorbing && tiles[x,y+1].isLikeWater) dd += (tiles[x,y+1].waterAbsorption - tiles[x,y+1].waterLevel);
				}
			}
			float total = l + u + r + d;
			float above = tiles[x,y].waterLevel - tiles[x,y].waterCapacity;

			// Distribute proportionally
			if(l > 0.0f){
				float modifier = (l / total) * 0.5f;
				float dw = Mathf.Min(modifier * dl, modifier * above);
				//float dw = modifier * dl;
				tiles[x,y].waterDiff -= dw;
				tiles[x-1,y].waterDiff += dw;
			}
			if(u > 0.0f){
				float modifier = (u / total) * 0.5f;
				float dw = Mathf.Min(modifier * du, modifier * above);
				//float dw = modifier * du;
				tiles[x,y].waterDiff -= dw;
				tiles[x,y-1].waterDiff += dw;
			}
			if(r > 0.0f){
				float modifier = (r / total) * 0.5f;
				float dw = Mathf.Min(modifier * dr, modifier * above);
				//float dw = modifier * dr;
				tiles[x,y].waterDiff -= dw;
				tiles[x+1,y].waterDiff += dw;
			}
			if(d > 0.0f){
				float modifier = (d / total) * 0.5f;
				float dw = Mathf.Min(modifier * dd, modifier * above);
				//float dw = modifier * dd;
				tiles[x,y].waterDiff -= dw;
				tiles[x,y+1].waterDiff += dw;
			}
		}
	}

	// Update is called once per frame
	void Update () {
		timer += Time.deltaTime;
		for (int i = clouds.Count - 1; i >= 0; i--){
			Cloud cloud = clouds[i];
			cloud.transform.position = new Vector3(cloud.transform.position.x + cloud.speed * Time.deltaTime * simulationSpeed,
				cloud.transform.position.y,cloud.transform.position.z);

			if(cloud.transform.position.x > xOffset * (width + 5)){
				clouds.RemoveAt(i);
				GameObject.Destroy(cloud.gameObject);
			}
		}

		if(timer > (1.0f / simulationSpeed)){

			for(uint x = 0; x < width;++x){
				for(uint y = 0; y < height;++y){
					tiles[x,y].waterDiff = 0.0f;
					tiles[x,y].newWaterCurrentX = 0.0f;
					tiles[x,y].newWaterCurrentY = 0.0f;
					tiles[x,y].lastColorUpdate += 0.1f;
				}
			}

			foreach(Cloud cloud in clouds){
				if(cloud.isRaining){
					Collider2D[] colliders = Physics2D.OverlapPointAll(cloud.shadow.transform.position);
					foreach(Collider2D c in colliders){
						Tile t = c.gameObject.GetComponent<Tile>();
						if(t){
							t.waterDiff += 800.0f * cloud.speed;
						}
					}
				}
			}

			for(uint x = 0; x < width;++x){
				for(uint y = 0; y < height;++y){
					if(tiles[x,y].isLikeWater){
						DoWaterPass(x,y);
					}
					else {
						tiles[x,y].waterCurrentX = 0f;
						tiles[x,y].waterCurrentY = 0f;
					}
					if(tiles[x,y].waterAddition > 0.0f){
						tiles[x,y].waterDiff += tiles[x,y].waterAddition * sourceModifier;
					}
					else if(tiles[x,y].waterLevel > 20.0f) {
						float sub = Mathf.Min(-tiles[x,y].waterAddition * sinkModifier, tiles[x,y].waterLevel - 2.0f);
						tiles[x,y].waterDiff -= sub;
					}
				}
			}

			float initDiff = (GameController.Instance.floodState == GameController.FloodState.ENDED)?-0.5f:0f;
			for(uint x = 0; x < width;++x){
				for(uint y = 0; y < height;++y){
					tiles[x,y].waterLevel += tiles[x,y].waterDiff;
					float topLimit = (tiles[x,y].waterAddition > 0.01f)?2*maxWaterLevel:maxWaterLevel;
					if(tiles[x,y].waterLevel < 0.0f){
						tiles[x,y].waterLevel = 0.0f;
					}
					else if(tiles[x,y].waterLevel > topLimit){
						tiles[x,y].waterLevel = topLimit;
					}
					if(Mathf.Abs(tiles[x,y].waterDiff) > 0.00001f){
						tiles[x,y].UpdateColor();
					}
					tiles[x,y].waterDiff = initDiff;
				}
			}

			for(uint x = 0; x < width;++x){
				for(uint y = 0; y < height;++y){
					if(tiles[x,y].isOverloaded && !tiles[x,y].isBarrier){
						DoPass(x,y);
					}
				}
			}

			float totalWater = 0.0f;
			for(uint x = 0; x < width;++x){
				for(uint y = 0; y < height;++y){

					tiles[x,y].waterLevel += tiles[x,y].waterDiff;
					totalWater+=tiles[x,y].waterLevel;
					float topLimit = (tiles[x,y].waterAddition > 0.01f)?2*maxWaterLevel:maxWaterLevel;
					if(tiles[x,y].waterLevel < 0.0f){
						tiles[x,y].waterLevel = 0.0f;
					}
					else if(tiles[x,y].waterLevel > topLimit){
						tiles[x,y].waterLevel = topLimit;
					}

					if(tiles[x,y].isLikeWater){
						tiles[x,y].waterCurrentX += tiles[x,y].newWaterCurrentX * CURRENT_COEF_DIFF;
						tiles[x,y].waterCurrentY += tiles[x,y].newWaterCurrentY * CURRENT_COEF_DIFF;

						tiles[x,y].newWaterCurrentY -= 5.0f;

						if(tiles[x,y].waterCurrentY > 0.0f) tiles[x,y].waterCurrentY = 0.0f;
						float totalCurrent = Mathf.Abs(tiles[x,y].waterCurrentX) + Mathf.Abs(tiles[x,y].waterCurrentY);
						float currentLimit = Mathf.Min(tiles[x,y].waterLevel, maxWaterCurrent);
						if(totalCurrent > currentLimit){
							float mod =currentLimit / totalCurrent;
							tiles[x,y].waterCurrentX *= mod;
							tiles[x,y].waterCurrentY *= mod;
						}
					}

					if(Mathf.Abs(tiles[x,y].waterDiff) > 0.00001f){
						tiles[x,y].UpdateColor();
					}
				}
			}

			timer -= (1.0f / simulationSpeed);
			simulationStep++;

			if(stopAt == simulationStep){
				Debug.Break();
			}

			cloudTimer++;
			if(cloudTimer > 100){
				cloudTimer = 0;
				spawnCloud();
			}
		}
	}

    public void Clear()
    {
        for (int i = transform.childCount - 1; i >= 0; --i )
        {
            var child = transform.GetChild(i).gameObject;
            DestroyImmediate( child );
        }
    }

    public Tile Build(Tile originalTile, Tile newTilePrefab)
    {
        Tile newTile = Instantiate(newTilePrefab, originalTile.transform.position, originalTile.transform.rotation, originalTile.transform.parent);
        tiles [originalTile.tileX, originalTile.tileY] = newTile;
		newTile.tileX = originalTile.tileX;
		newTile.tileY = originalTile.tileY;
        newTile.waterLevel = originalTile.waterLevel;
        Destroy(originalTile.gameObject);
        return newTile;
    }

    Object InstantiatePrefab(Object sourcePrefab)
    {
        #if UNITY_EDITOR
        return PrefabUtility.InstantiatePrefab(sourcePrefab);
        #else
        return Instantiate(sourcePrefab);
        #endif
    }
}

#if UNITY_EDITOR

[CustomEditor(typeof(TileManager))]
public class TileManagerEditor : Editor {

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector ();

        TileManager tileManager = target as TileManager;

        if (GUILayout.Button("Generate"))
			tileManager.GenerateTiles();

        if (GUILayout.Button("Clear"))
            tileManager.Clear();
    }

}

#endif
