using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using UnityEngine;

namespace Framework.DataHandling
{
    [Serializable]
    [AddComponentMenu("Scripts/Data Handling/SaveStateBehaviour")]
    public class SaveStateBehaviour : MonoBehaviour
    {
        public string prefabName;
        private DataID dataID;

        /// <summary>
        /// Called during the awake phase.
        /// </summary>
        protected void Awake()
        {
            // If the prefabName is empty try the behaviour name
            if (this.prefabName.Trim() == "")
                this.prefabName = this.name;

            // See if we can load the prefab
            if (Resources.Load(this.prefabName) != null)
            {
                this.dataID = new DataID(prefabName, GetInstanceID());
                GameData.Register(this);
            }
            else
                throw new PrefabNotFoundException("SaveStateBehaviour requires a prefab for loading but the prefab '" + this.prefabName + "' cannot be found in the resources folder.");
        }

        /// <summary>
        /// Called during the start phase.
        /// </summary>
        protected void Start()
        {
        }

        public DataID GetDataID()
        {
            return this.dataID;
        }

        /// <summary>
        /// Called during the update phase.
        /// </summary>
        protected void Update()
        {
        }

        protected void OnDestroy()
        {
            GameData.Deregister(this);
        }

        /// <summary>
        /// Saves the data of this GameObject.
        /// </summary>
        /// <returns></returns>
        public DataCollection SaveData()  
        {
            DataCollection data = new DataCollection();
            data.Add("Transform", new TransformData(this.transform));
            if(this.GetComponent<Rigidbody>() != null)
                data.Add("Rigidbody", new RigidbodyData(this.GetComponent<Rigidbody>()));
            NavMeshAgent navMeshAgent = GetComponent<NavMeshAgent>();
            if (navMeshAgent != null)
                data.Add("NavMeshAgent", new NavMeshAgentData(navMeshAgent));

            ISavableBehaviour[] components = GetComponents(typeof(ISavableBehaviour)).Cast<ISavableBehaviour>().ToArray();
            for (int i = 0; i < components.Length; i++)
            {
                // Combine the current data with data from a component
                data = new DataCollection(data.Concat(components[i].SaveData()).GroupBy(d => d.Key)
                        .ToDictionary(d => d.Key, d => d.First().Value));
            }

            return data;
        }

        /// <summary>
        /// Loads the data for this GameObject.
        /// </summary>
        /// <param name="data">The data.</param>
        public void LoadData(DataCollection data)
        {
            ((TransformData)data["Transform"]).ToTransform(this.transform);
            if(this.GetComponent<Rigidbody>() != null)
                ((RigidbodyData)data["Rigidbody"]).ToRigidbody(this.GetComponent<Rigidbody>());
            NavMeshAgent navMeshAgent = GetComponent<NavMeshAgent>();
            if (navMeshAgent != null)
                ((NavMeshAgentData)data["NavMeshAgent"]).ToNavMeshAgent(navMeshAgent);

            ISavableBehaviour[] components = GetComponents(typeof(ISavableBehaviour)).Cast<ISavableBehaviour>().ToArray();
            for (int i = 0; i < components.Length; i++)
            {
                components[i].LoadData(data);
            }
        }
    }

    public class PrefabNotFoundException : Exception
    {
        public PrefabNotFoundException()
            : base()
        {
        }

        public PrefabNotFoundException(string str)
            : base(str)
        {
        }
    }
}
