using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using TMPro;

public class Game : MonoBehaviour {
    public enum Error {
        NotJoined,
        AlreadyJoined,
        NoArguments,
        InvalidArguments,
        SpotTaken,
        NotEnoughCurrency,
        InvalidLocation,
        NotYourTower,
        AlreadyUpgraded,
        InvalidCommand
    }
    public const uint STARTING_CURRENCY = 50;
    public Dictionary<string, Player> Players;
    public delegate void OnErrorEvent(Game game, string username, Error error, string msg = null);
    public OnErrorEvent OnError;
    public TheGrid TheGrid;
    public GameObject playerTextPrefab;
    public Transform playerTextParent;
    public TextMeshProUGUI livesText;
    public TextMeshProUGUI bannerText;
    public MonsterManager mm;
    int lives = 1;
    public int Lives {
        get { return lives; }
        set {
            lives = value;
            livesText.text = lives.ToString();

            if (lives <= 0) {
                RestartGame();
            }
        }
    }

    // clear everyones money
    // resets towers
    // resets monster waves
    bool restarting = false;
    public void RestartGame() {
        if (!restarting) {
            restarting = true;
            StartCoroutine(RestartRoutine());
        }
    }

    IEnumerator RestartRoutine() {
        bannerText.gameObject.SetActive(true);
        yield return new WaitForSeconds(3.0f);
        bannerText.gameObject.SetActive(false);

        foreach (var p in Players) {
            p.Value.Money = STARTING_CURRENCY;
        }

        foreach (Transform child in TheGrid.towerParent) {
            Destroy(child.gameObject);
        }
        TheGrid.InitGrid();

        mm.Initialize();

        Lives = 100;

        restarting = false;
    }

    public IEnumerable<Player> ActivePlayers {
        get {
            return Players.Values.Where(p => p.Active);
        }
    }
    // Start is called before the first frame update
    void Awake() {
        Lives = 100;
        Application.targetFrameRate = 60;
        Application.runInBackground = true;

        bannerText.gameObject.SetActive(false);

        Players = new Dictionary<string, Player>();
    }

    public bool AddPlayer(string username) {
        if (Players.ContainsKey(username)) {
            if (!Players[username].Active) {
                SetPlayerActive(username, true);
                return true;
            }
            OnError?.Invoke(this, username, Error.AlreadyJoined);
            return false;
        }

        var go = Instantiate(playerTextPrefab, Vector3.zero, Quaternion.identity, playerTextParent);
        var txt = go.GetComponent<TextMeshProUGUI>();
        Players.Add(username, new Player(username, txt,
            Random.ColorHSV(0, 1, 0.2f, 1, 1, 1, 1, 1)));
        return true;
    }

    public bool SetPlayerActive(string username, bool active) {
        if (Players.ContainsKey(username)) {
            var p = Players[username];
            p.Active = active;
            p.Text.gameObject.SetActive(active);
            return true;
        }
        OnError?.Invoke(this, username, Error.NotJoined);
        return false;
    }

    public bool Build(string username, List<string> args) {
        if (args == null || args.Count == 0) {
            OnError?.Invoke(this, username, Error.NoArguments);
            return false;
        }
        if (Players.ContainsKey(username)) {
            string pos = args[0].ToLower();
            char letter = pos[0];
            if (letter < 'a' || letter >= 'a' + TheGrid.WIDTH) {
                OnError?.Invoke(this, username, Error.InvalidArguments, "letter bad");
                return false;
            }
            if (pos.Length <= 1) {
                OnError?.Invoke(this, username, Error.InvalidArguments, "not enuf args");
                return false;
            }

            if (int.TryParse(pos.Substring(1), out int number)) {
                if (number < 1 || number > TheGrid.WIDTH) {
                    OnError?.Invoke(this, username, Error.InvalidArguments, "number bad");
                    return false;
                }
                if (Players[username].Money >= 50) {
                    if (!TheGrid.Build(letter, number, Players[username])) {
                        OnError?.Invoke(this, username, Error.SpotTaken, pos);
                        return false;
                    }
                    Players[username].Money -= 50;
                } else {
                    OnError?.Invoke(this, username, Error.NotEnoughCurrency);
                    return false;
                }

            } else {
                OnError?.Invoke(this, username, Error.InvalidArguments, "not a number");
            }

            return true;
        }
        OnError?.Invoke(this, username, Error.NotJoined);
        return false;
    }

    public bool Upgrade(string username, List<string> args) {
        if (args == null || args.Count == 0) {
            OnError?.Invoke(this, username, Error.NoArguments);
            return false;
        }
        if (Players.ContainsKey(username)) {
            string pos = args[0].ToLower();
            char letter = pos[0];
            if (letter < 'a' || letter >= 'a' + TheGrid.WIDTH) {
                OnError?.Invoke(this, username, Error.InvalidArguments, "letter bad");
                return false;
            }
            if (pos.Length <= 1) {
                OnError?.Invoke(this, username, Error.InvalidArguments, "not enuf args");
                return false;
            }

            if (int.TryParse(pos.Substring(1), out int number)) {
                if (number < 1 || number > TheGrid.WIDTH) {
                    OnError?.Invoke(this, username, Error.InvalidArguments, "number bad");
                    return false;
                }
                if (TheGrid.GetGridValue(letter, number) != 1) {
                    OnError?.Invoke(this, username, Error.InvalidLocation, pos);
                    return false;
                }
                if (Players[username].Towers.Where(t => t.Location.ToLower() == pos.ToLower()).Count() == 0) {
                    OnError?.Invoke(this, username, Error.NotYourTower, pos);
                    return false;
                }
                Tower tower = Players[username].Towers.Where(t => t.Location.ToLower() == pos.ToLower()).FirstOrDefault();
                switch (args[1].ToLower()) {
                    case "cone":
                        if (tower.Pattern != Tower.ProjectilePattern.Line) {
                            OnError?.Invoke(this, username, Error.AlreadyUpgraded);
                            return false;
                        }
                        if (Players[username].Money >= 200) {
                            Players[username].Money -= 200;
                            tower.Pattern = Tower.ProjectilePattern.Cone;
                        } else {
                            OnError?.Invoke(this, username, Error.NotEnoughCurrency);
                            return false;
                        }
                        break;
                    case "cardinal":
                        if (tower.Pattern != Tower.ProjectilePattern.Line) {
                            OnError?.Invoke(this, username, Error.AlreadyUpgraded);
                            return false;
                        }
                        if (Players[username].Money >= 500) {
                            Players[username].Money -= 500;
                            tower.Pattern = Tower.ProjectilePattern.Cardinal;
                        } else {
                            OnError?.Invoke(this, username, Error.NotEnoughCurrency);
                            return false;
                        }
                        break;
                    case "circle":
                        if (tower.Pattern != Tower.ProjectilePattern.Line) {
                            OnError?.Invoke(this, username, Error.AlreadyUpgraded);
                            return false;
                        }
                        if (Players[username].Money >= 1000) {
                            Players[username].Money -= 1000;
                            tower.Pattern = Tower.ProjectilePattern.Circle;
                        } else {
                            OnError?.Invoke(this, username, Error.NotEnoughCurrency);
                            return false;
                        }
                        break;
                    case "speed":
                        uint cost = 100;
                        switch (tower.Pattern) {
                            case Tower.ProjectilePattern.Line:
                                cost = 100;
                                break;
                            case Tower.ProjectilePattern.Circle:
                                cost = 2000;
                                break;
                            case Tower.ProjectilePattern.Cardinal:
                                cost = 1000;
                                break;
                            case Tower.ProjectilePattern.Cone:
                                cost = 400;
                                break;
                        }

                        if (Players[username].Money >= cost) {
                            Players[username].Money -= cost;
                            tower.ShootFrequency /= 2;
                        } else {
                            OnError?.Invoke(this, username, Error.NotEnoughCurrency);
                            return false;
                        }
                        break;
                    default:
                        OnError?.Invoke(this, username, Error.InvalidArguments);
                        return false;
                }
            } else {
                OnError?.Invoke(this, username, Error.InvalidArguments, "not a number");
            }

            return true;
        }
        OnError?.Invoke(this, username, Error.NotJoined);
        return false;
    }

    public bool Gamble(string username, List<string> args) {
        if (args == null || args.Count == 0) {
            OnError?.Invoke(this, username, Error.NoArguments);
            return false;
        }
        if (Players.ContainsKey(username)) {
            return true;
        }
        OnError?.Invoke(this, username, Error.NotJoined);
        return false;
    }

    public bool Gift(string username, List<string> args) {
        if (args == null || args.Count == 0) {
            OnError?.Invoke(this, username, Error.NoArguments);
            return false;
        }
        if (Players.ContainsKey(username)) {
            return true;
        }
        OnError?.Invoke(this, username, Error.NotJoined);
        return false;
    }

    float moneyTick = 0.0f;
    // Update is called once per frame
    void Update() {
        // temp testing player money updating
        moneyTick += Time.deltaTime;
        const float interval = 2.0f;
        if (moneyTick >= interval) {
            moneyTick -= interval;
            foreach (Player player in ActivePlayers) {
                player.Money += 1;
            }
        }
    }
}
