﻿using AsmodeeDigital.PlayReal.Plugin.Domain.GameState;
using ProtoBuf.Meta;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace AsmodeeDigital.PlayReal.Plugin.Manager.Save
{
    /// <summary>
    /// Save / Load GameState
    /// </summary>
    public static class SaveManager
    {
        /// <summary>
        /// Save game in a binary format in specified FileName
        /// </summary>
        /// <remarks>The filename must not contain drive path</remarks>
        /// <example>fileName = @"AsmodeeDigital\PlayReal\save.sav"</example>
        /// <param name="gameStateBase">GameState, all classes have to be marked with [Serializable]</param>
        /// <param name="fileName">Savefile short path</param>
        public static void SaveGameState<T, U>(T gameStateBase, U typeModel, string fileName) where T : GameStateBase where U : TypeModel
        {
            string filePath = ComputeSaveFilePath(fileName);

            Serialize<T, U>(gameStateBase, typeModel, filePath);
        }

        /// <summary>
        /// Load game from the specified FileName
        /// </summary>
        /// <remarks>The filename must not contain drive path</remarks>
        /// <example>fileName = @"AsmodeeDigital\PlayReal\save.sav"</example>
        /// <param name="fileName">Savefile short path</param>
        public static T LoadGameState<T, U>(U typeModel, string fileName) where T : GameStateBase where U : TypeModel
        {
            string filePath = ComputeSaveFilePath(fileName);

            return Deserialize<T, U>(typeModel, filePath);
        }

        /// <summary>
        /// Serialize an object to a file
        /// </summary>
        /// <typeparam name="T">Type of the object</typeparam>
        /// <param name="objectToSerialize">Object to serialize</param>
        /// <param name="filePath">File path where the object will be serialized</param>
        private static void Serialize<T, U>(T objectToSerialize, U typeModel, string filePath) where T : GameStateBase where U : TypeModel
        {
            Directory.CreateDirectory(Path.GetDirectoryName(filePath));

            using (Stream stream = File.Open(filePath, FileMode.OpenOrCreate))
            {
                typeModel.Serialize(stream, objectToSerialize);
            }
        }

        /// <summary>
        /// Deserialize an object from a file
        /// </summary>
        /// <typeparam name="T">Type of the object</typeparam>
        /// <param name="filePath">File path where the object is stored</param>
        /// <returns></returns>
        private static T Deserialize<T, U>(U typeModel, string filePath) where U : TypeModel
        {
            T objectSerialized = default(T);

            try
            {
                using (Stream stream = File.Open(filePath, FileMode.OpenOrCreate))
                {
                    objectSerialized = (T)typeModel.Deserialize(stream, null, typeof(T));
                }
            }
            catch (Exception e)
            {
                Debug.LogError(String.Format("Error when deserializing file {0}. The serializer changed.\r\nException : {1}", filePath, e.Message));
            }

            return objectSerialized;
        }

        /// <summary>
        /// Get a list of all GameStates serialized according to a given file pattern
        /// </summary>
        /// <typeparam name="T">Type of the GameState</typeparam>
        /// <param name="filePattern">File path pattern. Can contain wildcard (*)</param>
        /// <returns></returns>
        public static List<T> GetLocalGameState<T, U>(U typeModel, string filePattern) where T : GameStateBase where U : TypeModel
        {
            List<T> gameStates = new List<T>();

            string[] files = GetLocalFiles(filePattern);

            foreach (string replayFile in files)
            {
                T gameState = Deserialize<T, U>(typeModel, replayFile);
                gameStates.Add(gameState);
            }

            return gameStates;
        }

        /// <summary>
        /// Get local files according to a given file pattern
        /// </summary>
        /// <param name="filePattern">File path pattern. Can contain wildcard (*)</param>
        /// <returns></returns>
        public static string[] GetLocalFiles(string filePattern)
        {
            string directory = Path.GetDirectoryName(filePattern);
            string fileName = Path.GetFileName(filePattern);

            string filePath = ComputeSaveFilePath(directory);

            if (Directory.Exists(filePath))
                return Directory.GetFiles(filePath, fileName, SearchOption.TopDirectoryOnly);
            else
                return new string[] { };
        }

        /// <summary>
        /// Compute the file path for a given filename.
        /// For OSX, Linux and Windows, the Savefile is stored in the local application data of the user, steam cloud can synchronize the file.
        /// For iOS and Android, the Savefile is stored near the application
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private static string ComputeSaveFilePath(string fileName)
        {
            //•	Android: Persistent Data Path(/ data / data /[com.mycompanyname] /[myapp] / files)
            //•	iOS: Persistent Data Path(/ var / mobile / Applications / xxxxx - xx - x - xx - xxxx / Documents /)
            //•	Windows: Local application Data(C: \Users\[username]\appdata\local\[filePath])
            //•	MacOSX : Local application Data(/Applications/Unity/MonoDevelop.app/Contents/MacOS/../Frameworks/Mono.framework/Versions/Current/share/[filePath])
            //•	Linux : Local application Data(~/.[filePath])

            string filePath = string.Empty;

            switch (Application.platform)
            {
                case RuntimePlatform.Android:
                case RuntimePlatform.IPhonePlayer:

                    filePath = Path.Combine(Application.persistentDataPath, Path.GetFileName(fileName));

                    break;
                case RuntimePlatform.LinuxPlayer:
                case RuntimePlatform.OSXEditor:
                case RuntimePlatform.OSXPlayer:
                case RuntimePlatform.WP8Player:
                case RuntimePlatform.WindowsEditor:
                case RuntimePlatform.WindowsPlayer:

                    filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), fileName);
                    break;

                default:
                    break;
            }

            return filePath;
        }
    }
}
