using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
//using Framework.Dbugging;

namespace Framework.DataHandling
{
    /// <summary>
    /// The GameData class is used to save any GameObjects that have been setup
    /// to have their state saved.
    /// </summary>
    public class GameData
    {
        static public string path = Application.dataPath + "/Data/";
        static private List<SaveStateBehaviour> gameObjects = new List<SaveStateBehaviour>();

        /// <summary>
        /// Registers the specified GameObject.
        /// </summary>
        /// <param name="gameObject">The game object.</param>
        static public void Register(SaveStateBehaviour gameObject)
        {
            gameObjects.Add(gameObject);
            Debug.Log("Registered GameObject ID: " + gameObject.GetInstanceID() + ", with GameData."); 
        }

        static public void Deregister(SaveStateBehaviour gameObject)
        {
            gameObjects.Remove(gameObject);
            Debug.Log("Deregistered GameObject ID: " + gameObject.GetInstanceID() + ", from GameData.");
        }

        /// <summary>
        /// Saves all registered GameObjects.
        /// </summary>
        static public void Save()
        {
            SaveHeader();
            for (int i = 0; i < gameObjects.Count; i++)
            {
                SaveGameObject(gameObjects[i]);
            }
            Debug.Log("Saved game.");
        }

        static private void SaveHeader()
        {
            FileStream stream = new FileStream(path + "header", FileMode.Create);
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(stream, gameObjects.Select(i => i.GetDataID()).ToArray());
            stream.Flush();
            stream.Close();
        }

        /// <summary>
        /// Saves the game object.
        /// </summary>
        /// <param name="gameObject">The game object.</param>
        static private void SaveGameObject(SaveStateBehaviour gameObject)
        {
            DataCollection data = gameObject.SaveData();

            FileStream stream = new FileStream(path + gameObject.GetInstanceID(), FileMode.Create);
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(stream, data);
            stream.Flush();
            stream.Close();
        }

        /// <summary>
        /// Loads all registered GameObjects.
        /// </summary>
        static public void Load()
        {
            LoadHeader();
            for (int i = 0; i < gameObjects.Count; i++)
            {
                LoadGameObject(gameObjects[i]);
            }
            Debug.Log("Loaded game.");
        }

        static private void LoadHeader()
        {
            // Get the header file DataIDs
            FileStream stream = new FileStream(path + "header", FileMode.Open);
            BinaryFormatter bf = new BinaryFormatter();
            DataID[] expectedGameObjectDataIDs = (DataID[])bf.Deserialize(stream);
            stream.Flush();
            stream.Close();
            
            // Get the IDs from the data
            //int[] expectedGameObjectIDs = expectedGameObjectDataIDs.Select(i => i.GetID()).ToArray();

            //Dbug.Log("Expected GameObjects: " + "\n" + expectedGameObjectDataIDs.ElementsToString<DataID>("\n") + "\n");
            //string[] paths = Directory.GetFiles(path).Select(i => Path.GetFileName(i)).ToArray();

            // Get DataIDs of objects to create and destroy
            DataID[] gameObjectDataIDs = gameObjects.Select(i => i.GetDataID()).ToArray();
            DataID[] toCreate = expectedGameObjectDataIDs.Except(gameObjectDataIDs).ToArray();
            DataID[] toDestroy = gameObjectDataIDs.Except(expectedGameObjectDataIDs).ToArray();
            //Dbug.Log("GameObjects to create: " + "\n" + toCreate.ElementsToString<DataID>("\n") + "\n");
            //Dbug.Log("GameObjects to destroy: " + "\n" + toDestroy.ElementsToString<DataID>("\n") + "\n");

            // Destroy objects not recorded
            for (int i = 0; i < toDestroy.Length; i++)
            {
                SaveStateBehaviour behaviour = gameObjects.Find(j => j.GetInstanceID() == toDestroy[i].GetID());
                if (behaviour != null)
                    GameObject.DestroyImmediate(behaviour.gameObject);
            }

            // Create objects found to be missing
            for (int i = 0; i < toCreate.Length; i++)
            {
                string prefabName = toCreate[i].GetPrefabName();
                GameObject resource = (GameObject)Resources.Load(prefabName);
                SaveStateBehaviour behaviour = (SaveStateBehaviour)((GameObject)GameObject.Instantiate(resource)).GetComponent(typeof(SaveStateBehaviour));
                int idToChange = toCreate[i].GetID();
                SaveHeader();
                System.IO.File.Move(@path + idToChange, @path + behaviour.GetInstanceID());
            }
            
        }

        /// <summary>
        /// Loads the game object.
        /// </summary>
        /// <param name="gameObject">The game object.</param>
        static private void LoadGameObject(SaveStateBehaviour gameObject)
        {
            try
            {
                FileStream stream = new FileStream(path + gameObject.GetInstanceID(), FileMode.Open);
                BinaryFormatter bf = new BinaryFormatter();
                DataCollection data = (DataCollection)bf.Deserialize(stream);
                gameObject.LoadData(data);
                stream.Flush();
                stream.Close();
            }
            catch (IOException)
            {
                Debug.Log("The file: " + path + gameObject.GetInstanceID() + " is not current available.");
            }
        }
    }
}
