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

public class GameLogic : MonoBehaviour
{
    public int NumberOfPlayers;
    public static Color[] PlayerColors = {
        //Color.red,
        //Color.green,
        //Color.blue,
        //Color.yellow
        Color.white,
        new Color(127f/255f,185f/255f,96f/255f), // green
        new Color(51f/255f,153f/255f,204f/255f), // blue
        new Color(153f/255f,102f/255f,204f/255f) // violet
    };

    public int DefeatTimerSeconds;
    public float ZoomOutSeconds;
    public float TargetPlanetMinDistanceFromOthers;

    public SpawnInfo[] SpawnConfig;

    [System.Serializable]
    public struct SpawnInfo
    {
        public int PlanetsToSpawn;
        public int PiratesToSpawn;
        public int WormholesToSpawn;
        public SpawnBounds Bounds;
    }

    [System.Serializable]
    public struct SpawnBounds
    {
        public float minXDistance;
        public float minYDistance;
        public float maxXDistance;
        public float maxYDistance;
    }

    public Image VictoryText;
    public Image DefeatText;
    public Text Timer;
    public TargetPlanet TargetPlanetPrefabToSpawn;
    public Camera MainCamera;
    public Camera FogVisCamera;
    public Sprite SunSprite;
    public int PlanetRequiredCrewMin = 3;
    public int PlanetRequiredCrewMax = 7;
    public GameObject WormholePrefab;
    public GameObject PiratePrefab;
    public SpriteRenderer GoodPlanet;
    public SpriteRenderer BadPlanet;

    private List<List<TargetPlanet>> planets;
    private bool isPlaying = true;
    private int currentLevel = 0;
    private bool isZoomingOut = false;
    private bool isZoomingIn = false;
    private float zoomStartTime;
    private float zoomStartMainCameraSize;
    private float zoomEndMainCameraSize;
    private float zoomStartFogCameraSize;
    private float zoomEndFogCameraSize;
    private float zoomOriginalMainCameraSize;
    private float zoomOriginalFogCameraSize;

    #region MonoBehaviour Callbacks

    void Start()
    {
        this.spawnTargetPlanets();
        this.spawnEnemies();
        this.VictoryText.gameObject.SetActive(false);
        this.DefeatText.gameObject.SetActive(false);
        this.BadPlanet.gameObject.SetActive(true);
        this.GoodPlanet.gameObject.SetActive(false);
        this.zoomOriginalMainCameraSize = this.MainCamera.orthographicSize;
        this.zoomOriginalFogCameraSize = this.FogVisCamera.orthographicSize;
    }

    void Update()
    {
        if (this.isPlaying)
        {
            this.updateTimer();
            this.CheckForDefeat();
            this.CheckForVictory();
        }
        if (this.isPlaying)
        {
            if (this.isZoomingOut)
            {
                this.updateZoomOut();
            }
            else
            {
                this.CheckForZoomOut();
            }
        }
        if (this.isZoomingIn)
        {
            this.updateZoomIn();
        }
    }

    #endregion

    #region Public Methods

    public void IncDefeatTimer(int amount = 30)
    {
        this.DefeatTimerSeconds += amount;
    }

    public void CheckForVictory()
    {
        if (this.IsCurrentLevelComplete() && this.currentLevel >= this.planets.Count - 1)
        {
            this.TriggerVictory();
        }
    }

    public bool IsCurrentLevelComplete()
    {
        foreach (TargetPlanet tp in planets[currentLevel])
        {
            if (!tp.IsComplete())
            {
                return false;
            }
        }
        return true;
    }

    public void CheckForZoomOut()
    {
        if (this.IsCurrentLevelComplete())
        {
            this.TriggerZoomOut();
            this.currentLevel++;
            this.spawnEnemies();
        }
    }

    public void TriggerZoomOut()
    {
        Debug.Log("Triggering ZoomOut");
        this.isZoomingOut = true;
        this.zoomStartTime = Time.timeSinceLevelLoad;
        this.zoomStartFogCameraSize = this.FogVisCamera.orthographicSize;
        this.zoomEndFogCameraSize = this.zoomStartFogCameraSize * this.SpawnConfig[currentLevel + 1].Bounds.maxYDistance / this.SpawnConfig[currentLevel].Bounds.maxYDistance;
        this.zoomStartMainCameraSize = this.MainCamera.orthographicSize;
        this.zoomEndMainCameraSize = this.zoomStartMainCameraSize * this.SpawnConfig[currentLevel + 1].Bounds.maxYDistance / this.SpawnConfig[currentLevel].Bounds.maxYDistance;
    }

    public void TriggerZoomIn()
    {
        Debug.Log("Triggering ZoomIn");
        this.isZoomingIn = true;
        this.zoomStartTime = Time.timeSinceLevelLoad;
        this.zoomStartFogCameraSize = this.FogVisCamera.orthographicSize;
        this.zoomEndFogCameraSize = this.zoomOriginalFogCameraSize;
        this.zoomStartMainCameraSize = this.MainCamera.orthographicSize;
        this.zoomEndMainCameraSize = this.zoomOriginalMainCameraSize;
    }

    public void CheckForDefeat()
    {
        if (Time.timeSinceLevelLoad > DefeatTimerSeconds)
        {
            this.TriggerDefeat();
        }
    }

    public void TriggerVictory()
    {
        Debug.Log("Triggering Victory");
        this.isPlaying = false;
        this.VictoryText.gameObject.SetActive(true);
        this.BadPlanet.gameObject.SetActive(false);
        this.GoodPlanet.gameObject.SetActive(true);
        this.TriggerZoomIn();
    }

    public void TriggerDefeat()
    {
        this.DefeatText.gameObject.SetActive(true);
        this.isPlaying = false;
    }

    #endregion

    #region Private Methods

    private void spawnEnemies()
    {
        for (int i = 0; i < this.SpawnConfig[this.currentLevel].WormholesToSpawn; i++)
        {
            Vector3 randomPos = this.getSuitableRandomPositionForEnemy(this.currentLevel);

            Instantiate<GameObject>(WormholePrefab, randomPos, Quaternion.identity);
        }
        for (int i = 0; i < this.SpawnConfig[this.currentLevel].PiratesToSpawn; i++)
        {
            Vector3 randomPos = this.getSuitableRandomPositionForEnemy(this.currentLevel);

            Instantiate<GameObject>(PiratePrefab, randomPos, Quaternion.identity);
        }
    }

    private void updateZoomOut()
    {
        if (Time.timeSinceLevelLoad - zoomStartTime >= ZoomOutSeconds)
        {
            this.isZoomingOut = false;
            return;
        }
        this.MainCamera.orthographicSize = Mathf.Lerp(this.zoomStartMainCameraSize, this.zoomEndMainCameraSize, (Time.timeSinceLevelLoad - zoomStartTime) / ZoomOutSeconds);
    }

    private void updateZoomIn()
    {
        if (Time.timeSinceLevelLoad - zoomStartTime >= ZoomOutSeconds)
        {
            this.isZoomingIn = false;
            return;
        }
        this.MainCamera.orthographicSize = Mathf.Lerp(this.zoomStartMainCameraSize, this.zoomEndMainCameraSize, (Time.timeSinceLevelLoad - zoomStartTime) / ZoomOutSeconds);
    }

    private void updateTimer()
    {
        float timeLeft = this.DefeatTimerSeconds - Time.timeSinceLevelLoad;
        this.Timer.text = timeLeft.ToString("0.00");
    }

    private void spawnTargetPlanets()
    {
        this.planets = new List<List<TargetPlanet>>();
        for (int level = 0; level < this.SpawnConfig.Length; level++)
        {
            this.planets.Add(new List<TargetPlanet>());
            for (int planetIndex = 0; planetIndex < this.SpawnConfig[level].PlanetsToSpawn; planetIndex++)
            {
                Vector3 randomPos = getSuitableRandomPosition(level);

                TargetPlanet newTargetPlanet = Instantiate<TargetPlanet>(TargetPlanetPrefabToSpawn, randomPos, Quaternion.identity);
                if (level == this.SpawnConfig.Length-1)
                {
                    this.setPlanetToSun(newTargetPlanet);
                }
                else
                {
                    newTargetPlanet.RandomizePlanetSprite();
                }
                this.planets[level].Add(newTargetPlanet);
            }
        }
    }

    private void setPlanetToSun(TargetPlanet tp)
    {
        tp.GetComponent<SpriteRenderer>().sprite = this.SunSprite;
    }

    private Vector3 getSuitableRandomPosition(int level)
    {
        // Pick a random position to spawn a target planet
        float randomX = Random.Range(-this.SpawnConfig[level].Bounds.maxXDistance, this.SpawnConfig[level].Bounds.maxXDistance);
        float randomY = Random.Range(-this.SpawnConfig[level].Bounds.maxYDistance, this.SpawnConfig[level].Bounds.maxYDistance);

        // Ensure it's at least a min distance away from origin
        if (Mathf.Abs(randomX) < this.SpawnConfig[level].Bounds.minXDistance || Mathf.Abs(randomY) < this.SpawnConfig[level].Bounds.minYDistance)
        {
            return getSuitableRandomPosition(level);
        }
        Vector3 randomPos = new Vector3(randomX, randomY, -1);

        // Ensure it's at least a min distance away from other target planets
        float minDist = float.MaxValue;
        for (int i = 0; i < planets.Count; i++)
        {
            foreach (TargetPlanet tp in planets[i])
            {
                Vector3 diff = tp.transform.position - randomPos;
                if (diff.magnitude < minDist)
                {
                    minDist = diff.magnitude;
                }
                if (minDist < TargetPlanetMinDistanceFromOthers)
                {
                    return getSuitableRandomPosition(level);
                }
            }
        }
        return randomPos;
    }

    // This is like the above one but doesn't care about min distance away from others
    private Vector3 getSuitableRandomPositionForEnemy(int level)
    {
        // Pick a random position to spawn a target enemy
        float randomX = Random.Range(-this.SpawnConfig[level].Bounds.maxXDistance, this.SpawnConfig[level].Bounds.maxXDistance);
        float randomY = Random.Range(-this.SpawnConfig[level].Bounds.maxYDistance, this.SpawnConfig[level].Bounds.maxYDistance);

        // Ensure it's at least a min distance away from origin
        if (Mathf.Abs(randomX) < this.SpawnConfig[level].Bounds.minXDistance || Mathf.Abs(randomY) < this.SpawnConfig[level].Bounds.minYDistance)
        {
            return getSuitableRandomPosition(level);
        }
        Vector3 randomPos = new Vector3(randomX, randomY, -1);

        return randomPos;
    }

    #endregion
}
