using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

/// <summary>
/// A class for holding all progress belonging to a user
/// </summary>
[Serializable]
public class Progress
{
    /// <summary>
    /// A helper class for handling the stored progress
    /// </summary>
    [Serializable]
    protected class DataEntry
    {
        /// <summary>
        /// The key, used to reference the data object
        /// </summary>
        public string key;

        /// <summary>
        /// The object, representated as a list of byte (which can be serialized)
        /// </summary>
        public List<byte> bytes = new List<byte>();

        public DataEntry(string key, byte[] data)
        {
            this.key = key;
            this.bytes = new List<byte>(data);
        }
    }

    /// <summary>
    /// Entries in the <c>Progress</c> object
    /// </summary>
    [SerializeField]
    private List<DataEntry> entries = new List<DataEntry>();


    /// <summary>
    /// Update the value of a certain key,
    /// or add a new value if the key was not present
    /// </summary>
    /// <typeparam name="T">The type of the data to be added/updated</typeparam>
    /// <param name="key">The key, used for referencing the data</param>
    /// <param name="data">The object of type <typeparamref name="T"/></param>
    /// <returns><c>true</c> if successful, <c>false</c> otherwise</returns>
    public bool AddOrUpdate<T>(string key, T data)
    {
        if (data == null)
            return false;

        DataEntry entry = entries.Find(x => x.key == key);

        // Hacky serialization stuff
        BinaryFormatter bf = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream())
        {
            bf.Serialize(ms, data);
            if (entry != null)
            {
                entry.bytes.Clear();
                entry.bytes.AddRange(ms.ToArray());
            }
            else
            {
                entries.Add(new DataEntry(key, ms.ToArray()));
            }
            return true;
        }
    }

    // Get the value of type `T` belonging to `key`
    /// <summary>
    /// Get the data object of a certain key
    /// </summary>
    /// <typeparam name="T">The type of the data object</typeparam>
    /// <param name="key">The key referencing the data object</param>
    /// <returns>The data, cast to a type <typeparamref name="T"/></returns>
    /// <exception cref="KeyNotFoundException"></exception>
    public T Get<T>(string key)
    {
        BinaryFormatter bf = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream())
        {
            // Find the correct key
            foreach (DataEntry entry in entries)
            {
                if (entry.key == key)
                {
                    // Hacky serialization stuff
                    byte[] data = entry.bytes.ToArray();
                    ms.Write(data, 0, data.Length);
                    ms.Seek(0, SeekOrigin.Begin);
                    return (T)bf.Deserialize(ms);
                }
            }
        }
        // Raise an exception when key is not found
        throw new KeyNotFoundException();
    }
}
