using UnityEngine;
using UnityEngine.SceneManagement;

namespace Fungus
{
    public class SaveManager : MonoBehaviour 
    {
        const string ActiveSlotKey = "active_slot";

        const string SlotKeyFormat = "slot{0}";

        // Make serialize data extensible (subclassing?)
        // Save key, use save profile and variable substitution
        // Store scene name, flowchart name and block name to execute after load
        // Show link to Block to be executed
        // Handle New Save Slot case: Scene to load?
        // Save command stores data in SaveManager for writing later
        // If SaveImmediately is selected then save it straight away (SaveGame command)
        // If not selected, then Save when a Save button is pressed
        // Select / Load button - set active slot (in playerprefs) and load the state
        // Get list of saved games
        // Delete Save Game
        // Rename SaveFlowchart to SaveGame

        protected string saveBuffer = "";

        protected static SaveManager instance;

        protected SavePointData tempSaveData;

        protected virtual void Awake()
        {
            instance = this;
        }

        protected virtual string FormatSaveKey(int slot)
        {
            return string.Format(SlotKeyFormat, slot);
        }

        protected virtual bool LoadNewGame(string key, string startScene)
        {
            if (PlayerPrefs.HasKey(key) &&
                PlayerPrefs.GetString(key) != "")
            {
                return false;
            }

            // Create a new save entry
            PlayerPrefs.SetString(key, "");

            // Load the start scene
            SceneManager.LoadScene(startScene);

            return true;
        }

        protected virtual string CreateSaveData(Flowchart flowchart, string resumeBlockName)
        {
            var saveData = new SavePointData();

            // Store the scene, flowchart and block to execute on resume
            saveData.sceneName = SceneManager.GetActiveScene().name;
            saveData.flowchartName = flowchart.name;
            saveData.resumeBlockName = resumeBlockName;

            for (int i = 0; i < flowchart.Variables.Count; i++) 
            {
                var v = flowchart.Variables[i];

                // Save string
                var stringVariable = v as StringVariable;
                if (stringVariable != null)
                {
                    var d = new StringVar();
                    d.key = stringVariable.Key;
                    d.value = stringVariable.Value;
                    saveData.stringVars.Add(d);
                }

                // Save int
                var intVariable = v as IntegerVariable;
                if (intVariable != null)
                {
                    var d = new IntVar();
                    d.key = intVariable.Key;
                    d.value = intVariable.Value;
                    saveData.intVars.Add(d);
                }

                // Save float
                var floatVariable = v as FloatVariable;
                if (floatVariable != null)
                {
                    var d = new FloatVar();
                    d.key = floatVariable.Key;
                    d.value = floatVariable.Value;
                    saveData.floatVars.Add(d);
                }

                // Save bool
                var boolVariable = v as BooleanVariable;
                if (boolVariable != null)
                {
                    var d = new BoolVar();
                    d.key = boolVariable.Key;
                    d.value = boolVariable.Value;
                    saveData.boolVars.Add(d);
                }
            }

            return JsonUtility.ToJson(saveData, true);
        }

        protected virtual void RestoreSavedGame(SavePointData saveData)
        {
            var go = GameObject.Find(saveData.flowchartName);
            if (go == null)
            {
                return;
            }

            var flowchart = go.GetComponent<Flowchart>();
            if (flowchart == null)
            {
                return;
            }

            for (int i = 0; i < saveData.boolVars.Count; i++)
            {
                var boolVar = saveData.boolVars[i];
                flowchart.SetBooleanVariable(boolVar.key, boolVar.value);
            }
            for (int i = 0; i < saveData.intVars.Count; i++)
            {
                var intVar = saveData.intVars[i];
                flowchart.SetIntegerVariable(intVar.key, intVar.value);
            }
            for (int i = 0; i < saveData.floatVars.Count; i++)
            {
                var floatVar = saveData.floatVars[i];
                flowchart.SetFloatVariable(floatVar.key, floatVar.value);
            }
            for (int i = 0; i < saveData.stringVars.Count; i++)
            {
                var stringVar = saveData.stringVars[i];
                flowchart.SetStringVariable(stringVar.key, stringVar.value);
            }

            flowchart.ExecuteBlock(saveData.resumeBlockName);
        }

        protected virtual void StoreJSONData(string key, string jsonData)
        {
            if (key.Length > 0)
            {
                PlayerPrefs.SetString(key, jsonData);
            }
        }

        protected virtual string LoadJSONData(string key)
        {
            if (key.Length > 0)
            {
                return PlayerPrefs.GetString(key);
            }

            return "";
        }

        protected virtual void OnSceneLoaded(Scene scene, LoadSceneMode mode)
        {
            if (scene.name == tempSaveData.sceneName)
            {
                RestoreSavedGame(tempSaveData);
            }

            SceneManager.sceneLoaded -= OnSceneLoaded;
        }

        #region Public members

        public static SaveManager Instance { get { return instance; } }

        public virtual int ActiveSlot
        {
            get
            {
                return PlayerPrefs.GetInt(ActiveSlotKey);
            }
            set
            {
                PlayerPrefs.SetInt(ActiveSlotKey, value);
            }
        }

        public virtual void Save()
        {
            if (saveBuffer == "")
            {
                // Nothing to save
                return;
            }

            var key = FormatSaveKey(ActiveSlot);

            PlayerPrefs.SetString(key, saveBuffer);

            saveBuffer = "";
        }

        public virtual void Load(int slot, string startScene = "")
        {
            ActiveSlot = slot;

            var key = FormatSaveKey(slot);

            if (LoadNewGame(key, startScene))
            {
                return;
            }

            var jsonData = LoadJSONData(key);
            tempSaveData = JsonUtility.FromJson<SavePointData>(jsonData);

            SceneManager.sceneLoaded += OnSceneLoaded;
            SceneManager.LoadScene(tempSaveData.sceneName);
        }

        public virtual void Delete(int slot)
        {
            var key = FormatSaveKey(slot);
            PlayerPrefs.DeleteKey(key);
        }

        public virtual void PopulateSaveBuffer(Flowchart flowchart, string resumeBlockName)
        {
            saveBuffer = CreateSaveData(flowchart, resumeBlockName);
        }

        #endregion
    }
}