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

/// <summary>
/// These utilities are built for a save system where:
///     *   Each save game has a particular name. That name is used as a parent directory
///         that houses all files for that save game.
///     *   One can have multiple save games.
/// </summary>
public static class SaveSystemUtilities
{
    const string SAVE_FILE_EXTENSION = ".bin";
    const string PARENT_SAVE_DIRECTORY_NAME = "saves";
    const string DEFAULT_SAVE_NAME = "NewGame";

    // Returns the parent directory that contains all save directories
    private static string GetSaveSystemParentDirectory()
    {
        return Path.Combine(Application.persistentDataPath, PARENT_SAVE_DIRECTORY_NAME);
    }

    // Returns a path for a save directory with a given save name
    private static string GetSaveDirectoryPath(string saveName)
    {
        return Path.Combine(GetSaveSystemParentDirectory(), saveName);
    }

    // Returns a filepath that incorporates the name of the data and save name.
    private static string GetSaveDataFilePath(string dataName, string saveName)
    {
        return Path.Combine(GetSaveDirectoryPath(saveName), dataName + SAVE_FILE_EXTENSION);
    }

    private static void CreateSaveDirectories(string saveName)
    {
        string saveSystemDirectoryPath = GetSaveSystemParentDirectory();
        string saveDirectoryPath = GetSaveDirectoryPath(saveName);
        if (!Directory.Exists(saveSystemDirectoryPath))
        {
            Directory.CreateDirectory(saveSystemDirectoryPath);
        }
        if (!Directory.Exists(saveDirectoryPath))
        {
            Directory.CreateDirectory(saveDirectoryPath);
        }
    }

    // Save your data to a file with a path that incorporates the name of the data and save file name.
    public static void Save<T>(T data, string dataName, string saveName)
    {
        // If the directories we save files to have not been created, create them.
        CreateSaveDirectories(saveName);

        BinaryFormatter binaryFormatter = new BinaryFormatter();
        string saveDataFilePath = GetSaveDataFilePath(dataName, saveName);
        FileStream fileStream = new FileStream(saveDataFilePath, FileMode.Create);
        binaryFormatter.Serialize(fileStream, data);
        fileStream.Close();
    }

    // Attempts to load the data for the specified data under the save file.
    // If the data is found it returns it, otherwise it returns the default value of the type.
    public static T Load<T>(string dataName, string saveName)
    {
        T data = default;
        if (SaveDataExists(dataName, saveName))
        {
            string saveDataFilePath = GetSaveDataFilePath(dataName, saveName);
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            FileStream fileStream = new FileStream(saveDataFilePath, FileMode.Open);
            data = (T)binaryFormatter.Deserialize(fileStream);
        }

        return data;
    }

    // Returns true if the save (directory) exists
    public static bool SaveExists(string saveName)
    {
        string saveDirectoryPath = GetSaveDirectoryPath(saveName);
        return Directory.Exists(saveDirectoryPath);
    }

    // Returns true if the save file exists for data under a save file name
    public static bool SaveDataExists(string dataName, string saveName)
    {
        string saveDataFilePath = GetSaveDataFilePath(dataName, saveName);
        return File.Exists(saveDataFilePath);
    }

    // Deletes a save (directory) if it exists
    public static void DeleteSave(string saveName)
    {
        if (SaveExists(saveName))
        {
            // Delete the save directory and its subdirectories.
            Directory.Delete(GetSaveDirectoryPath(saveName), true);
        }
    }

    // Returns a list of all save names that exist
    public static List<string> GetAllSaveNames()
    {
        List<string> saveNames = new List<string>();
        DirectoryInfo directoryInfo = new DirectoryInfo(GetSaveSystemParentDirectory());
        DirectoryInfo[] directories = directoryInfo.GetDirectories();
        foreach (DirectoryInfo directory in directories)
        {
            saveNames.Add(directory.Name);
        }
        return saveNames;
    }

    // Generates a save name that has not been used yet.
    public static string GenerateNewSaveName()
    {
        string saveName;
        int count = 0;

        do
        {
            saveName = DEFAULT_SAVE_NAME + count;
            count += 1;
        }
        while (SaveExists(saveName));

        return saveName;
    }
}
