using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;
using Random = UnityEngine.Random;

public class Tile : MonoBehaviour {

    public World world
    {
        get
        {
            return gameObject.GetComponentInParent<World>();
        }
    }
    public bool isPlayerTile { get { return _isPlayerTile; }
        set {
            if (value && becamePlayerTile != null) becamePlayerTile();
            _isPlayerTile = value;
            }
    }
    bool _isPlayerTile;
    static Transform playerTransform
    {
        get
        {
            if (_playerTransform == null)
                if (Game.player != null)
                    _playerTransform = Game.player.transform;
            return _playerTransform;
        }
    }
    static Transform _playerTransform;

    Tile[] neighbors = new Tile[0];
    public delegate void voidDelegate();
    public event voidDelegate becamePlayerTile;

    [Header("Child objects")]
    public GameObject groundObj;
    public GameObject lifeObj;

    [Header("Env Config")]
    public float temperatureChangePerSec;
    public float coolingPerSecPerHeight;

    [Header("Favorable conditions for life")]
    public Vector2 temperatureRange;
    public Vector2 humidityRange;

    [Header("Life Config")]
    [Range(0, 1)]
    public float abundanceGrowthPerSec;
    [Range(0, 1)]
    public float abundanceShrinkPerSec;
    [Range(0, 1)]
    public float minAbundanceForMigration;
    [Range(0, 1)]
    public float migratedAbundancePerSec;

    [Header("Names")]
    public string[] prefixes;
    public string[] middles;
    public string[] suffixes;

    [Header("Geography")]
    public float height;

    [Header("Environment")]
    public bool temperatureConstant;
    [Range(-1, 1)]
    public float temperature;
    [Range(0, 3)]
    public int humidity;
    public bool toxic;

    [Header("Life")]
    [Range(0, 1)]
    public float abundance;
    public uint ferocity;
    public enum Intelligence {Primordial, Feral, Primitive, Advanced};
    public Intelligence intelligence;

    public enum Infrastructure {None, Farm, City }
    [Header("Infrastructure")]
    public Infrastructure infrastructure;

    static float Lerp(float current, float target, float maxStepPerSec)
    {
        var maxStep = maxStepPerSec * Time.deltaTime;
        if ( current > target)
            if ( current - target < maxStep)
                 return target;
            else
                 return current - maxStep;
        if ( current < target)
            if (target -  current < maxStep)
                 return target;
            else
                 return current + maxStep;
        return target;
    }

    /// <summary>
    /// checks if the value is within the range
    /// </summary>
    /// <param name="range">x is minimum, y is maximum</param>
    static bool InRange(float value, Vector2 range)
    {
        return value >= range[0] && value <= range[1];
    }

    //converts temperature to readable format
    public static string TempToString(float temperature)
    {
        return Mathf.RoundToInt(temperature * 80 + 20) + "°C";
    }

    public bool FavorableForLife()
    {
        if (!InRange(temperature, temperatureRange))
            return false;
        if (!InRange(humidity, humidityRange))
            return false;
        if (toxic)
            return false;
        return true;
    }

    public void Start()
    {
        name = "";
        name += prefixes[Random.Range(0, prefixes.Length)];
        name += middles[Random.Range(0, middles.Length)];
        name += suffixes[Random.Range(0, suffixes.Length)];

        StartCoroutine(UpdateNeighbors());
        StartCoroutine(UpdateTile());
    }

    public void Generate(Tile[] adjacentTiles, bool isStartingTile = false)
    {
        toxic = false;
        if (isStartingTile){
            temperature = -1f;
            humidity = 0;
            height = 0.1f;
        }
        else if (adjacentTiles.Length == 0)
        {
            temperature = Random.Range(-1f, -0.5f);
            humidity = Random.Range(0, 3);
            height = Mathf.Pow(10, Random.Range(0f, 1f))/10;
        }
        else
        {
            temperature = adjacentTiles.Average(t => t.temperature) + Random.Range(-0.2f, 0.2f);
            humidity = Random.Range(0, 4);
            height = adjacentTiles.Average(t => t.height) + Mathf.Pow(10, Random.Range(0, 1f)) / 50;
        }
        abundance = 0;
        ferocity = 0;
        intelligence = Intelligence.Primordial;
        isPlayerTile = isStartingTile;
    }

    IEnumerator UpdateNeighbors()
    {
        while (true)
        {
            neighbors = (world == null) ?
                new Tile[0] :
                world.grid.Neighbors(this).ToArray();
            yield return new WaitForSeconds(5);
        }
    }
	
	IEnumerator UpdateTile () {
        while (true)
        {
            #region geography update
            var localPos = transform.localPosition;
            localPos.y = height;
            transform.localPosition = localPos;
            groundObj.transform.localScale = new Vector3(1, 1, height);
            #endregion

            #region environment update
            if (!temperatureConstant)
                temperature -= coolingPerSecPerHeight * height * Time.deltaTime;
            if (neighbors.Any())
            {
                // try to reach a temperature average of all adjacent temperatures
                if (!temperatureConstant)
                {
                    var targetTemp = neighbors.Average(t => t.temperature);
                    temperature = Lerp(temperature, targetTemp, temperatureChangePerSec * Mathf.Abs(temperature - targetTemp));
                }

                // humidity spreads like minecraft water, with each adjacent tile having one level less
                var validSources = neighbors.Where(t => t.height > height);
                if (validSources.Any())
                {
                    var sourceHumidity = validSources.Max(t => t.humidity);
                    if (sourceHumidity > 0 && sourceHumidity - 1 > humidity)
                        humidity = sourceHumidity - 1;
                }
                if (neighbors.All(t => t.toxic) && neighbors.Count() == 6)
                    toxic = true;
            }
            if (temperature == 1 && humidity > 0) humidity--;
            temperature = Mathf.Clamp(temperature, -1f, 1f);
            humidity = Mathf.Clamp(humidity, 0, 3);
            #endregion

            if (intelligence == Intelligence.Primitive) this.infrastructure = Infrastructure.Farm;
            if (intelligence == Intelligence.Advanced) this.infrastructure = Infrastructure.City;

            #region life update
            // adjust abundance based on environment
            if (abundance > 0 && FavorableForLife())
                abundance += abundanceGrowthPerSec * Time.deltaTime;
            else
                abundance -= abundanceShrinkPerSec * Time.deltaTime;

            abundance = Mathf.Clamp(abundance, 0, 1);
            if (abundance > minAbundanceForMigration && neighbors.Any())
                Migrate(neighbors.Where(t => t.FavorableForLife()));

            if (abundance > 0 && neighbors.Where(t => t.abundance > 0).Count() > 3)
            {
                //find the lowest intelligence with 3 representatives
                Intelligence maximumInt = Intelligence.Primordial;
                for (Intelligence i = Intelligence.Advanced; i>Intelligence.Primordial; i--)
                {
                    if (neighbors.Where(t => t.intelligence >= i).Count() > 3)
                    {
                        maximumInt = i;
                        break;
                    }
                }
                if (maximumInt >= intelligence && maximumInt < world.allowedIntelligence)
                {
                    this.intelligence = (Intelligence)Mathf.Min((int)intelligence + 1, (int)Intelligence.Advanced);
                }
            }

            if (abundance > 0) Game.lifeContainingTiles.Add(this);
            else Game.lifeContainingTiles.Remove(this);

            #endregion
            if (isPlayerTile)
                yield return new WaitForEndOfFrame();
            else
                yield return new WaitForSeconds(Vector3.SqrMagnitude(transform.position - playerTransform.position) * 0.01f);
        }
    }

    void Migrate(IEnumerable<Tile> destinations)
    {
        var destinationsForAbundance = destinations.Where(t=>t.abundance <= 0.1f);
        var destinationsForIntelligence = destinations.Where(t=>t.intelligence < intelligence);
        var abundanceMigrated = migratedAbundancePerSec * Time.deltaTime;
        var abundancePerDest = abundanceMigrated / destinationsForAbundance.Count();
        foreach (var tile in destinationsForAbundance)
        {
            var abundanceTransferred = Mathf.Min(1 - tile.abundance, abundancePerDest);
            tile.abundance += abundanceTransferred;
            this.abundance -= abundanceTransferred;
        }
        foreach (var tile in destinationsForIntelligence)
            if (tile.abundance > 0.9f)
            {
                tile.abundance = 0.2f;
                tile.intelligence++;
            }
    }

    public void OnClick()
    {
        if (!Input.GetMouseButton(1))
            return; // wrong button
        // attempt to remove the tile
        if (abundance > 0)
        {
            Game.hints.ShowError("Life is precious. Do not destroy it.");
            return;
        }
        if (world.tiles.Length == 1)
        {
            Game.hints.ShowError("Do not destroy all you have.");
            return;
        }
        if (world != Game.player)
            return;

        world.grid.Remove(this);
        Destroy(gameObject);
    }
}
