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

public class LayerManager : MonoBehaviour
{
    public static LayerManager Instance { get; private set; }

    public static readonly int DEFAULT_HEIGHT = 1080;
    public static readonly int DEFAULT_WIDTH = 1920;
    public static readonly float MAGIC_NUMBER = 1.0f;

	private static float mRequiredOrtographicSize;
    public static int ScreenWidth { get { if (!Application.isPlaying) return 320; else return Screen.width; } }
    public static int ScreenHeight { get { if (!Application.isPlaying) return 480; else return Screen.height; } }
    public static float NormalizedScreenHeight { get; private set; }
    public static float NormalizedScreenWidth { get; private set; }
    public static float ScreenHeightNormalizer { get; private set; }
    public static float ScreenWidthNormalizer { get; private set; }

    private const float BASE_HEIGHT = 200;
    private const float HEIGHT_INC = 10;
    private InputManager mInputManager = new InputManager();
    private List<GameObject> mLayersList = new List<GameObject>();
    private int mCurrentDepth = 0;
    private float mCurrentHeight = BASE_HEIGHT;
    private Dictionary<KeyValuePair<GameObject, bool>, List<LayerSlice>> mTransitioningLayers = new Dictionary<KeyValuePair<GameObject, bool>, List<LayerSlice>>();
    private List<GameObject> mRemovalQueue = new List<GameObject>();
    private GameObject mCurrentlyOpenedLoading = null;
    private List<GameObject> mLayerStack = new List<GameObject>();

    public enum Layers
    {
        DialogScreen,
        ChoiceScreen,
        ControlsScreen
    }

    static LayerManager()
    {
        GameObject layerMngObj = new GameObject("LayerManager");
        DontDestroyOnLoad(layerMngObj);
        Instance = layerMngObj.AddComponent<LayerManager>();
        //Util.AssertTrue(ScreenHeight >= ScreenWidth, "Screen-related functionalites are only supported in portrait mode");
        CalculateRequiredOrtographicSize();
        CalculateNormalizedValues();
    }

    private static void CalculateRequiredOrtographicSize()
    {
        if (ScreenHeight <= DEFAULT_HEIGHT)
        {   // The device's height is smaller than the default. Screens are scaled down.
            float defaultRatio = (float)DEFAULT_HEIGHT / (float)DEFAULT_WIDTH;
            float currentRatio = (float)ScreenHeight / (float)ScreenWidth;
            float ratioRatio = currentRatio / defaultRatio;
            mRequiredOrtographicSize = MAGIC_NUMBER * ratioRatio;
        }
        else
        {   // The device's height is bigger than the default. Screens are kept pixel perfect.
            float heightRatio = (float)ScreenHeight / (float)DEFAULT_HEIGHT;
            mRequiredOrtographicSize = MAGIC_NUMBER * heightRatio;
            if(heightRatio.Compare(2.0f) >= 1)
			{   // The device's height is WAY bigger than the default. Screens are scaled up.
                mRequiredOrtographicSize /= 2.0f;
			}
        }

        /*float defaultRatio = (float)DEFAULT_HEIGHT / (float)DEFAULT_WIDTH;
        float currentRatio = (float)ScreenHeight / (float)ScreenWidth;
        float ratioRatio = currentRatio / defaultRatio;
        mRequiredOrtographicSize = 1.5f * ratioRatio;*/
    }

    private static void CalculateNormalizedValues()
    {
        float aspectRatio = (float)ScreenWidth / (float)ScreenHeight;
        NormalizedScreenHeight = 2 * mRequiredOrtographicSize;
        NormalizedScreenWidth = NormalizedScreenHeight * aspectRatio;
        ScreenWidthNormalizer = NormalizedScreenWidth / (float)ScreenWidth;
        ScreenHeightNormalizer = NormalizedScreenHeight / (float)ScreenHeight;
    }

    public static void KillWhenInEditor()
    {
        if (!Application.isPlaying)
        {
            if (Instance != null)
            {
                DestroyImmediate(Instance.gameObject);
                Instance = null;
            }
        }
    }

    public void RegisterInputReceivableOnTop(IInputReceivable receivable, bool inputShallNotPass)
    {
        mInputManager.RegisterInputReceivableOnTop(receivable, inputShallNotPass);
    }

    public void UnregisterInputReceivable(IInputReceivable receivable)
    {
        mInputManager.UnregisterInputReceivable(receivable);
    }

    /*public void RequestLoadingScreen()
    {
        Util.AssertTrue(mCurrentlyOpenedLoading == null, "Loading should only be requested if o loading is pending");
        GameObject newLayerObj = MonoBehaviour.Instantiate(Resources.Load("Layers" + "/" + Layers.Loading.ToString())) as GameObject;
        Camera newLayerCamera = newLayerObj.GetComponent<Camera>();
        newLayerCamera.clearFlags = CameraClearFlags.Depth;
        newLayerCamera.depth = 10;
        newLayerObj.transform.position = new Vector3(0, 1000, 0);
        mCurrentlyOpenedLoading = newLayerObj;
    }

    public void RemoveLoadingScreen()
    {
        MonoBehaviour.Destroy(mCurrentlyOpenedLoading);
        mCurrentlyOpenedLoading = null;
    }*/

    private bool CheckSpritesCollections(GameObject gameObj)
    {
        /*tk2dBaseSprite[] sprites = gameObj.GetComponentsInChildren<tk2dBaseSprite>();
        if (sprites.Length == 0)
        {
            return true;
        }
        string templateCollectionID = sprites[0].Collection.spriteCollectionGUID;
        foreach(tk2dBaseSprite sprite in gameObj.GetComponentsInChildren<tk2dBaseSprite>(true))
        {
            if (!sprite.Collection.spriteCollectionGUID.Equals(templateCollectionID))
            {
                return false;
            }
        }*/
        return true;
    }

    public GameObject AddLayerOnTop(Layers layer, bool inputShallNotPass, object dataPack = null, bool removePrevious = true)
    {
        GameObject newLayerObj = MonoBehaviour.Instantiate(Resources.Load("Layers" + "/" + layer.ToString())) as GameObject;
        Util.AssertTrue(newLayerObj != null, "The layer could not be loaded");
        if (removePrevious)
        {
            if (mLayerStack.Count > 0)
            {
                mLayerStack.Add(newLayerObj);
                RemoveTopLayer();
            }
            else
            {
                mLayerStack.Add(newLayerObj);
            }
        }
        else
        {
            mLayerStack.Add(newLayerObj);
        }
        DontDestroyOnLoad(newLayerObj);
        return AddLayerOnTop(newLayerObj, inputShallNotPass, dataPack);
    }

    public GameObject AddLayerOnTop(GameObject newLayerObj, bool inputShallNotPass, object dataPack = null)
    {
        Util.AssertTrue(CheckSpritesCollections(newLayerObj), "Layer does not use only 1 atlas: " + newLayerObj.name);
        Camera newLayerCamera = newLayerObj.GetComponent<Camera>();
        newLayerCamera.enabled = true;
        Util.AssertTrue(newLayerCamera != null, "The layer gameobject must have a camera attached to it");
        Util.AssertTrue(newLayerCamera.isOrthoGraphic, "All layers' cameras must be ortographic");
        Util.AssertTrue(newLayerObj.GetComponent<IInputReceivable>() != null, "");
        if (!newLayerObj.GetComponent<IInputReceivable>().Initialized)
        {
            newLayerObj.GetComponent<IInputReceivable>().Initialized = true;
            newLayerCamera.orthographicSize = mRequiredOrtographicSize * newLayerCamera.rect.height;
            newLayerCamera.clearFlags = CameraClearFlags.Depth;
            newLayerCamera.depth = mLayerStack.Count;
            newLayerObj.transform.position = new Vector3(0, BASE_HEIGHT + HEIGHT_INC * mLayerStack.Count, 0);
            newLayerObj.GetComponentsInChildren<Anchor>().ForEach(anchorSon => anchorSon.ApplyAnchors(newLayerObj.transform.position));

            GameObject[] layerSliceObjs = newLayerObj.transform.GetChildren().Select(transf => transf.gameObject).ToArray();
            Util.AssertTrue(layerSliceObjs.TrueForAll(sliceObj => sliceObj.GetComponent<LayerSlice>() != null), "All direct sons of a layer must be layerslices");
            List<LayerSlice> layerSliceScripts = new List<LayerSlice>();
            layerSliceScripts.AddRange(layerSliceObjs.Select(sliceObj => sliceObj.GetComponent<LayerSlice>()));
            layerSliceScripts.ForEach(sliceScript => sliceScript.OnLayerPositionSet());
        }
        mLayersList.Add(newLayerObj);
        if(dataPack != null)
        {
            newLayerObj.GetComponent<IInputReceivable>().OnDataPackReceived(dataPack);
        }
        mInputManager.RegisterInputReceivableOnTop(newLayerObj.GetComponent<IInputReceivable>(), inputShallNotPass);
        OrganizeEffects(newLayerObj, false);
        return newLayerObj;
    }

    private bool IsLayerUnderAdding(GameObject layer)
    {
        return mTransitioningLayers.Any(kvp => kvp.Key.Key == layer && !kvp.Key.Value);
    }

    public void RemoveLayer(GameObject layer, bool unstackPrevious = false, bool destroy = false)
    {        
        int index = mLayersList.IndexOf(layer);
        RemoveLayer(index, unstackPrevious, destroy);
    }

    public void RemoveTopLayer(bool unstackPrevious = false, bool destroy = false)
    {
        int index = mLayersList.Count - 1;
        RemoveLayer(index, unstackPrevious, destroy);
    }

    public void RemoveLayer(int index, bool unstackPrevious = false, bool removeAndDestroy = false)
    {
        Util.AssertTrue(index >= 0, "Invalid layer index: " + index);
        Util.AssertTrue(mLayersList.Count > 0, "There is no layer to be removed");
        GameObject toDestroyLayerObj = mLayersList[index];
        Util.AssertTrue(toDestroyLayerObj.GetComponent<IInputReceivable>() != null, "This gameobject does not have an 'IInputReceivable' and so is not recognized as a layer");

        

        toDestroyLayerObj.GetComponent<IInputReceivable>().RemoveAndDestroy = removeAndDestroy;

        if (IsLayerUnderAdding(toDestroyLayerObj))
        {
            mRemovalQueue.Add(toDestroyLayerObj);
        }
        else
        {
            mLayersList.RemoveAt(index);
            mInputManager.UnregisterInputReceivable(toDestroyLayerObj.GetComponent<IInputReceivable>());
            if (removeAndDestroy)
            {
                mCurrentDepth--;
                mCurrentHeight -= HEIGHT_INC;
            }
            OrganizeEffects(toDestroyLayerObj, true);
        }


        if (unstackPrevious)
        {
            //mLayerStack[mLayerStack.Count - 2].SetActiveRecursively(true);
            //AddLayerOnTop(mLayerStack[mLayerStack.Count - 2], true);
            AddLayerOnTop(mLayerStack[mLayerStack.IndexOf(toDestroyLayerObj) - 1], true);
        }
        else
        {

        }

        if (toDestroyLayerObj.GetComponent<IInputReceivable>().RemoveAndDestroy)
        {
            mLayerStack.Remove(toDestroyLayerObj);
            //MonoBehaviour.Destroy(layerObj);
        }
    }

    public void DestroyHiddenStackedLayers()
    {
        while (mLayerStack.Count > 1)
        {
            if (mLayersList.Contains(mLayerStack[0]))
            {
                break;
            }
            MonoBehaviour.Destroy(mLayerStack[0]);
            mLayerStack.RemoveAt(0);
        }
    }

    private void OrganizeEffects(GameObject layerObj, bool removing)
    {
        GameObject[] layerSliceObjs = layerObj.transform.GetChildren().Select(transf => transf.gameObject).ToArray();
        Util.AssertTrue(layerSliceObjs.TrueForAll(sliceObj => sliceObj.GetComponent<LayerSlice>() != null), "All direct sons of a layer must be layerslices");
        List<LayerSlice> layerSliceScripts = new List<LayerSlice>();
        layerSliceScripts.AddRange(layerSliceObjs.Select(sliceObj => sliceObj.GetComponent<LayerSlice>()));
        layerSliceScripts.Sort((layer1, layer2) =>
        {
            if (layer1.InsertionOrder < layer2.InsertionOrder) if (removing) return 1; else return -1;
            else if (layer1.InsertionOrder == layer2.InsertionOrder) return 0;
            else if (removing) return -1; else return 1;
        });
        layerSliceScripts.ForEach(slice => slice.Init(removing));
        if (removing)
        {
            layerSliceScripts.ForEach(slice => slice.InsertionOrder = 1);
        }
        mTransitioningLayers.Add(new KeyValuePair<GameObject, bool>(layerObj, removing) , layerSliceScripts);
        mInputManager.IsInputBlocked = mTransitioningLayers.Count != 0;
    }

    private void Update()
    {
        mInputManager.Update(Time.deltaTime);
        for(int i=0; i<mTransitioningLayers.Count; i++)
        {
            List<LayerSlice> layerSliceScripts = mTransitioningLayers.ElementAt(i).Value;                        
            KeyValuePair<GameObject, bool> layerObjKvp = mTransitioningLayers.ElementAt(i).Key;
            GameObject layerObj = layerObjKvp.Key;
            bool removing = layerObjKvp.Value;
            int currentPriority = layerSliceScripts[0].InsertionOrder;
            int j = 0;
            while (j < layerSliceScripts.Count && layerSliceScripts[j].InsertionOrder == currentPriority)
            {
                if (layerSliceScripts[j].CustomUpdate(Time.deltaTime))
                {
                    if (removing)
                    {
                        //layerSliceScripts[j].gameObject.SetActiveRecursively(false);
                    }
                    layerSliceScripts.RemoveAt(j);
                }
                j++;
            }
            if (layerSliceScripts.Count == 0)
            {
                layerObj.GetComponent<IInputReceivable>().OnLayerEffectFinished(removing);
                if (removing)
                {
                    if(layerObj.GetComponent<IInputReceivable>().RemoveAndDestroy)
                    {
                        //mLayerStack.Remove(layerObj);
                        MonoBehaviour.Destroy(layerObj);
                    }
                    layerObj.GetComponent<Camera>().enabled = false;
                }
                mTransitioningLayers.Remove(layerObjKvp);
                mInputManager.IsInputBlocked = mTransitioningLayers.Count != 0;
                if(mRemovalQueue.Count > 0)
                {
                    GameObject toRemoveLayer = mRemovalQueue[0];
                    mRemovalQueue.RemoveAt(0);
                    RemoveLayer(toRemoveLayer);
                }
            }
        }
    }
}