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

public delegate void GameStart();
public delegate void GameEnd();

public delegate void WinEvent();
public delegate void LoseEvent();

[System.Serializable]
public struct DifficultySetting
{
    public float patchRadiusFromEachOther;
    public float patchFromHoleAllowance;
    public List<ClothType>   numHoles;
    public int  numberOfAllowedLinks;

    public float holeBleedFromWorldBound;
    public float holeDistanceFromEachOther;
    public float worldWidth;
    public float worldHeight;
}


public class GameController : MonoBehaviour
{
    public WorldGenerator    worldGenerator;
    public ThreadController  threadController;
    public GameObject        player;
    public DifficultySetting setting;

    public static event GameStart GameStartHandler;
    public static event GameEnd   GameEndHandler;
    public static event LoseEvent RanOutOfThread;
    public static event LoseEvent OutOfWorld;
    public static event WinEvent  AllHolesFilled;
    public static event OnLinkUpdate LinkLeft;

    public List<GameObject> activeHoles = new List<GameObject>();
    private List<ClothType>  presentColors; // For holes
    private Bounds worldBounds;
    private CircleCollider2D playerCollider2D;
    private bool outOfBounds;

    private int currNumThread = 0;

    public AudioSource m_AudioPlayer;
    public AudioSource m_MusicPlayer;
    public AudioClip m_MusicClip;

    public AudioClip m_LoseSound;

    public AudioClip m_WinSound;

    // Start is called before the first frame update
    private void Awake()
    {
        if (MainMenuController.hasPayload){
            setting = MainMenuController.payload;
        }
        
        threadController.onLinkUpdateHandler += ListenForThreadChange;
    }
    void Start()
    {
        m_MusicPlayer.clip = m_MusicClip;
        m_MusicPlayer.Play();

        ListenForThreadChange(0);
        StartGame();
        
        playerCollider2D = player.GetComponent<CircleCollider2D>();
        outOfBounds = false;
        worldBounds = new Bounds(Vector3.zero, new Vector3(setting.worldWidth + setting.patchFromHoleAllowance, setting.worldHeight + setting.patchFromHoleAllowance, 10));
    }

    void ListenForThreadChange(int val)
    {
        currNumThread = val;
        int threadLeft = setting.numberOfAllowedLinks - currNumThread;

        if (threadLeft < 0) { 
            LinkLeft(threadLeft);
            // Lose Event
            if (RanOutOfThread != null)
            {
                m_AudioPlayer.clip = m_LoseSound;
                m_AudioPlayer.Play();
                m_MusicPlayer.Stop();
                Debug.Log("LOSE: Player Ran Out of Thread");
                RanOutOfThread();
            }

            EndLevel();
        }

        else if (LinkLeft != null) {
            Debug.Log(threadLeft);
            LinkLeft(threadLeft);
        }
    }

    void ListenForPlayerOutOfLevel()
    {
        m_AudioPlayer.clip = m_LoseSound;
        m_AudioPlayer.Play();
        m_MusicPlayer.Stop();
        Debug.Log("LOSE: Player out of level");
        if (OutOfWorld != null) OutOfWorld();
        EndLevel();
    }

    // Update is called once per frame
    void Update()
    {
        // Check for out of bounds
        if (!outOfBounds && !worldBounds.Intersects(playerCollider2D.bounds))
        {
            outOfBounds = true;
            ListenForPlayerOutOfLevel();
        }
    }

    void StartGame()
    {
        
        presentColors = new List<ClothType>(setting.numHoles);
        worldGenerator.Generate(setting);
        activeHoles  = worldGenerator._spawnHoles;
        foreach (GameObject hole in worldGenerator._spawnHoles)
        {
            HoleBehaviour holeComp = hole.GetComponent<HoleBehaviour>();
            holeComp.holeCompleteHandler += ListenForHoleComplete;
        }

        SpawnPlayerNextToNearbyPatch();

        if (GameStartHandler != null) GameStartHandler();
    }

    public ClothType RequestForClothColor()
    {
        Debug.Log("Before: " + presentColors.Count);
        ClothType toReturn = presentColors.First();
        presentColors.RemoveAt(0);
        Debug.Log("After: "  + presentColors.Count);
        return toReturn;
    }

    public ClothType ReturnRandomPresentColor()
    {
        return setting.numHoles[Random.Range(0,setting.numHoles.Count)];
    }

    void SpawnPlayerNextToNearbyPatch()
    {
        var patches = worldGenerator._spawnPatchs;
        Debug.Log(patches.Count + " patches");
        GameObject closest =  
        patches.OrderBy(patch => Vector3.Distance(patch.transform.position, player.transform.position)).FirstOrDefault();

        float oldZ = player.transform.position.z;
        Vector3 targetPos = closest.transform.position + Vector3.up * 5.0f;
        targetPos.z = oldZ;
        player.transform.position = targetPos;
    }

    void ListenForHoleComplete(GameObject holeObj)
    {
        Debug.Log(holeObj + " has been completed!");
        activeHoles.Remove(holeObj);
        if (activeHoles.Count == 0)
        {
            m_AudioPlayer.clip = m_WinSound;
            m_AudioPlayer.Play();
            m_MusicPlayer.Stop();
            if (AllHolesFilled != null) AllHolesFilled();
            EndLevel();
        }
    }

    void EndLevel()
    {
        Debug.Log("<color=blue> Level End </color>");
        if (GameEndHandler != null) GameEndHandler();
    }

    /// <summary>
    /// This function is called when the MonoBehaviour will be destroyed.
    /// </summary>
    void OnDestroy()
    {
        GameStartHandler = null;
        GameEndHandler   = null;
        RanOutOfThread   = null;
        OutOfWorld       = null;
        AllHolesFilled   = null;

        PatchBehaviour.Cleanup();
    }
}
