using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using ES3Internal;
#if UNITY_2018_3_OR_NEWER
using UnityEngine.Networking;
#endif
public static class ES3
{
	public enum Location 		{ File, PlayerPrefs, InternalMS, Resources, Cache };
	public enum Directory		{ PersistentDataPath, DataPath }
	public enum EncryptionType 	{ None, AES };
    public enum CompressionType { None, Gzip};
    public enum Format 			{ JSON };
	public enum ReferenceMode	{ ByRef, ByValue, ByRefAndValue};
    #region ES3.Save
    // Saves the value to the default file with the given key. 
    /// (key, value, new ES3Settings());
    }
    /// Saves the value to a file with the given key. 
    /// (key, value, new ES3Settings(filePath));
    }
    /// Saves the value to a file with the given key. 
    /// (key, value, new ES3Settings(filePath, settings));
    }
    /// Saves the value to a file with the given key. 
    /// (key, value, settings);
    }
    /// Saves the value to the default file with the given key. 
    /// (string key, T value)
    {
        Save(key, value, new ES3Settings());
    }
    /// Saves the value to a file with the given key. 
    /// (string key, T value, string filePath)
    {
        Save(key, value, new ES3Settings(filePath));
    }
    /// Saves the value to a file with the given key. 
    /// (string key, T value, string filePath, ES3Settings settings)
    {
        Save(key, value, new ES3Settings(filePath, settings));
    }
    /// Saves the value to a file with the given key. 
    /// (string key, T value, ES3Settings settings)
    {
        if (settings.location == Location.Cache)
        {
            ES3File.GetOrCreateCachedFile(settings).Save(key, value);
            return;
        }
        using (var writer = ES3Writer.Create(settings))
        {
            writer.Write(key, value);
            writer.Save();
        }
    }
    /// Creates or overwrites a file with the specified raw bytes. 
    /// Creates or overwrites a file with the specified raw bytes. 
    /// Creates or overwrites a file with the specified raw bytes. 
    /// Creates or overwrites a file with the specified raw bytes. 
    /// Creates or overwrites the default file with the specified raw bytes. 
    /// Creates or overwrites the default file with the specified raw bytes. 
    /// Creates or overwrites a file with the specified raw bytes. 
    /// Creates or overwrites a file with the specified raw bytes. 
    /// Creates or appends the specified bytes to a file. 
    /// Creates or appends the specified bytes to a file. 
    /// Creates or appends the specified bytes to a file. 
    /// Creates or appends the specified bytes to the default file. 
    /// Creates or appends the specified bytes to a file. 
    /// Creates or appends the specified bytes to a file. 
    /// Saves a Texture2D as a PNG or JPG, depending on the file extension used for the filePath. 
    /// Saves a Texture2D as a PNG or JPG, depending on the file extension used for the filePath. 
    /// Saves a Texture2D as a PNG or JPG, depending on the file extension used for the filePath. 
    /// Saves a Texture2D as a PNG or JPG, depending on the file extension used for the filePath. 
    /// Saves a Texture2D as a PNG or JPG, depending on the file extension used for the filePath. 
    /// Saves a Texture2D as a PNG or JPG, depending on the file extension used for the filePath. 
    /// 
    /* Standard load methods */
    /// Loads the value from a file with the given key. 
    /// (key, new ES3Settings());
    }
    /// Loads the value from a file with the given key. 
    /// (key, new ES3Settings(filePath));
    }
    /// Loads the value from a file with the given key. 
    /// (key, new ES3Settings(filePath, settings));
    }
    /// Loads the value from a file with the given key. 
    /// (key, settings);
    }
    /// Loads the value from a file with the given key. 
    /// (string key)
    {
        return Load(key, new ES3Settings());
    }
    /// Loads the value from a file with the given key. 
    /// (string key, string filePath)
    {
        return Load(key, new ES3Settings(filePath));
    }
    /// Loads the value from a file with the given key. 
    /// (string key, string filePath, ES3Settings settings)
    {
        return Load(key, new ES3Settings(filePath, settings));
    }
    /// Loads the value from a file with the given key. 
    /// (string key, ES3Settings settings)
    {
        if (settings.location == Location.Cache)
            return ES3File.GetOrCreateCachedFile(settings).Load(key);
        using (var reader = ES3Reader.Create(settings))
        {
            if (reader == null)
                throw new System.IO.FileNotFoundException("File \"" + settings.FullPath + "\" could not be found.");
            return reader.Read(key);
        }
    }
    /// Loads the value from a file with the given key. 
    /// (string key, T defaultValue)
    {
        return Load(key, defaultValue, new ES3Settings());
    }
    /// Loads the value from a file with the given key. 
    /// (string key, string filePath, T defaultValue)
    {
        return Load(key, defaultValue, new ES3Settings(filePath));
    }
    /// Loads the value from a file with the given key. 
    /// (string key, string filePath, T defaultValue, ES3Settings settings)
    {
        return Load(key, defaultValue, new ES3Settings(filePath, settings));
    }
    /// Loads the value from a file with the given key. 
    /// (string key, T defaultValue, ES3Settings settings)
    {
        if (settings.location == Location.Cache)
            return ES3File.GetOrCreateCachedFile(settings).Load(key, defaultValue);
        using (var reader = ES3Reader.Create(settings))
        {
            if (reader == null)
                return defaultValue;
            return reader.Read(key, defaultValue);
        }
    }
    /* Self-assigning load methods */
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (string key, object obj) where T : class
    {
        LoadInto(key, obj, new ES3Settings());
    }
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (key, obj, new ES3Settings(filePath));
    }
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (key, obj, new ES3Settings(filePath, settings));
    }
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (key, obj, settings);
    }
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (string key, T obj) where T : class
    {
        LoadInto(key, obj, new ES3Settings());
    }
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (string key, string filePath, T obj) where T : class
    {
        LoadInto(key, obj, new ES3Settings(filePath));
    }
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (string key, string filePath, T obj, ES3Settings settings) where T : class
    {
        LoadInto(key, obj, new ES3Settings(filePath, settings));
    }
    /// Loads the value from a file with the given key into an existing object, rather than creating a new instance. 
    /// (string key, T obj, ES3Settings settings) where T : class
    {
        if (ES3Reflection.IsValueType(obj.GetType()))
            throw new InvalidOperationException("ES3.LoadInto can only be used with reference types, but the data you're loading is a value type. Use ES3.Load instead.");
        if (settings.location == Location.Cache)
        {
            ES3File.GetOrCreateCachedFile(settings).LoadInto(key, obj);
            return;
        }
        if (settings == null) settings = new ES3Settings();
        using (var reader = ES3Reader.Create(settings))
        {
            if (reader == null)
                throw new System.IO.FileNotFoundException("File \"" + settings.FullPath + "\" could not be found.");
            reader.ReadInto(key, obj);
        }
    }
    /* LoadString method, as this can be difficult with overloads. */
    /// Loads the value from a file with the given key. 
    /// (key, filePath, defaultValue, new ES3Settings(filePath));
    }
    #endregion
    #region Other ES3.Load Methods
    /// Loads the default file as a byte array. 
    public static byte[] LoadRawBytes()
    {
        return LoadRawBytes(new ES3Settings());
    }
    /// Loads a file as a byte array. 
    /// Loads a file as a byte array. 
    /// Loads the default file as a byte array. 
    /// (settings.FullPath);
			return textAsset.bytes;
		}
		return null;*/
    }
    /// Loads the default file as a byte array. 
    public static string LoadRawString()
    {
        return LoadRawString(new ES3Settings());
    }
    /// Loads a file as a byte array. 
    /// Loads a file as a byte array. 
    /// Loads the default file as a byte array. 
    /// Loads a PNG or JPG as a Texture2D. 
    /// Loads a PNG or JPG as a Texture2D. 
    /// Loads a PNG or JPG as a Texture2D. 
    /// Loads a PNG or JPG as a Texture2D. 
    /// Loads an audio file as an AudioClip. Note that MP3 files are not supported on standalone platforms and Ogg Vorbis files are not supported on mobile platforms. 
    /// Loads an audio file as an AudioClip. Note that MP3 files are not supported on standalone platforms and Ogg Vorbis files are not supported on mobile platforms. 
    /// (T value, ES3Settings settings=null)
    {
        if (settings == null) settings = new ES3Settings();
        using (var ms = new System.IO.MemoryStream())
        {
            using (var stream = ES3Stream.CreateStream(ms, settings, ES3FileMode.Write))
            {
                using (var baseWriter = ES3Writer.Create(stream, settings, false, false))
                {
                    // If T is object, use the value to get it's type. Otherwise, use T so that it works with inheritence.
                    var type = typeof(T) != typeof(object) ? typeof(T) : (value == null ? typeof(T) : value.GetType());
                    baseWriter.Write(value, ES3TypeMgr.GetOrCreateES3Type(type), settings.referenceMode);
                }
                return ms.ToArray();
            }
        }
    }
    public static T Deserialize(byte[] bytes, ES3Settings settings=null)
    {
        return (T)Deserialize(ES3TypeMgr.GetOrCreateES3Type(typeof(T)), bytes, settings);
    }
    internal static object Deserialize(ES3Types.ES3Type type, byte[] bytes, ES3Settings settings = null)
    {
        if (settings == null)
            settings = new ES3Settings();
        using (var ms = new System.IO.MemoryStream(bytes, false))
            using (var stream = ES3Stream.CreateStream(ms, settings, ES3FileMode.Read))
                using (var reader = ES3Reader.Create(stream, settings, false))
                    return reader.Read(type);
    }
    public static void DeserializeInto(byte[] bytes, T obj, ES3Settings settings = null) where T : class
    {
        DeserializeInto(ES3TypeMgr.GetOrCreateES3Type(typeof(T)), bytes, obj, settings);
    }
    public static void DeserializeInto(ES3Types.ES3Type type, byte[] bytes, T obj, ES3Settings settings = null) where T : class
    {
        if (settings == null)
            settings = new ES3Settings();
        using (var ms = new System.IO.MemoryStream(bytes, false))
            using (var reader = ES3Reader.Create(ms, settings, false))
                reader.ReadInto(obj, type);
    }
    #endregion
    #region Other ES3 Methods
    public static byte[] EncryptBytes(byte[] bytes, string password=null)
    {
        if (string.IsNullOrEmpty(password))
            password = ES3Settings.defaultSettings.encryptionPassword;
        return new AESEncryptionAlgorithm().Encrypt(bytes, password, ES3Settings.defaultSettings.bufferSize);
    }
    public static byte[] DecryptBytes(byte[] bytes, string password=null)
    {
        if (string.IsNullOrEmpty(password))
            password = ES3Settings.defaultSettings.encryptionPassword;
        return new AESEncryptionAlgorithm().Decrypt(bytes, password, ES3Settings.defaultSettings.bufferSize);
    }
    public static string EncryptString(string str, string password=null)
    {
        return ES3Settings.defaultSettings.encoding.GetString(EncryptBytes(ES3Settings.defaultSettings.encoding.GetBytes(str), password));
    }
    public static string DecryptString(string str, string password=null)
    {
        return ES3Settings.defaultSettings.encoding.GetString(DecryptBytes(ES3Settings.defaultSettings.encoding.GetBytes(str), password));
    }
    /// Deletes the default file. 
    public static void DeleteFile()
    {
        DeleteFile(new ES3Settings());
    }
    /// Deletes the file at the given path using the default settings. 
    /// Deletes the file at the given path using the settings provided. 
    /// Deletes the file specified by the ES3Settings object provided as a parameter. 
    /// Copies a file from one path to another. 
    /// Copies a file from one location to another, using the ES3Settings provided to override any default settings. 
    /// Copies a file from one location to another, using the ES3Settings provided to determine the locations. 
    /// Renames a file. 
    /// Renames a file. 
    /// Renames a file. 
    /// Copies a file from one path to another. 
    /// Copies a file from one location to another, using the ES3Settings provided to override any default settings. 
    /// Copies a file from one location to another, using the ES3Settings provided to determine the locations. 
    /// Renames a file. 
    /// Renames a file. 
    /// Renames a file. 
    /// Deletes the directory at the given path using the settings provided. 
    /// Deletes the directory at the given path using the settings provided. 
    /// Deletes the directory at the given path using the settings provided. 
    /// Deletes a key in the default file. 
    /// Deletes a key in the file specified. 
    /// Deletes a key in the file specified by the ES3Settings object. 
    /// Checks whether a key exists in the default file. 
    /// True if the key exists, otherwise False. 
    public static bool KeyExists(string key)
    {
        return KeyExists(key, new ES3Settings());
    }
    /// Checks whether a key exists in the specified file. 
    /// True if the key exists, otherwise False. 
    public static bool KeyExists(string key, string filePath)
    {
        return KeyExists(key, new ES3Settings(filePath));
    }
    /// Checks whether a key exists in the default file. 
    /// True if the key exists, otherwise False. 
    public static bool KeyExists(string key, string filePath, ES3Settings settings)
    {
        return KeyExists(key, new ES3Settings(filePath, settings));
    }
    /// Checks whether a key exists in a file. 
    /// True if the file exists, otherwise False. 
    public static bool KeyExists(string key, ES3Settings settings)
    {
        if (settings.location == Location.Cache)
            return ES3File.KeyExists(key, settings);
        using (var reader = ES3Reader.Create(settings))
        {
            if (reader == null)
                return false;
            return reader.Goto(key);
        }
    }
    /// Checks whether the default file exists. 
    /// True if the file exists, otherwise False. 
    public static bool FileExists()
    {
        return FileExists(new ES3Settings());
    }
    /// Checks whether a file exists. 
    /// True if the file exists, otherwise False. 
    public static bool FileExists(string filePath)
    {
        return FileExists(new ES3Settings(filePath));
    }
    /// Checks whether a file exists. 
    /// True if the file exists, otherwise False. 
    public static bool FileExists(string filePath, ES3Settings settings)
    {
        return FileExists(new ES3Settings(filePath, settings));
    }
    /// Checks whether a file exists. 
    /// True if the file exists, otherwise False. 
    public static bool FileExists(ES3Settings settings)
    {
        if (settings.location == Location.File)
            return ES3IO.FileExists(settings.FullPath);
        else if (settings.location == Location.PlayerPrefs)
            return PlayerPrefs.HasKey(settings.FullPath);
        else if (settings.location == Location.Cache)
            return ES3File.FileExists(settings);
        else if (settings.location == Location.Resources)
            return Resources.Load(settings.FullPath) != null;
        return false;
    }
    /// Checks whether a folder exists. 
    /// True if the folder exists, otherwise False. 
    public static bool DirectoryExists(string folderPath)
    {
        return DirectoryExists(new ES3Settings(folderPath));
    }
    /// Checks whether a file exists. 
    /// True if the folder exists, otherwise False. 
    public static bool DirectoryExists(string folderPath, ES3Settings settings)
    {
        return DirectoryExists(new ES3Settings(folderPath, settings));
    }
    /// Checks whether a folder exists. 
    /// True if the folder exists, otherwise False. 
    public static bool DirectoryExists(ES3Settings settings)
    {
        if (settings.location == Location.File)
            return ES3IO.DirectoryExists(settings.FullPath);
        else if (settings.location == Location.PlayerPrefs || settings.location == Location.Cache)
            throw new System.NotSupportedException("Directories are not supported for the Cache and PlayerPrefs location.");
        else if (settings.location == Location.Resources)
            throw new System.NotSupportedException("Checking existence of folder in Resources not supported.");
        return false;
    }
    /// Gets an array of all of the key names in the default file. 
    public static string[] GetKeys()
    {
        return GetKeys(new ES3Settings());
    }
    /// Gets an array of all of the key names in a file. 
    /// Gets an array of all of the key names in a file. 
    /// Gets an array of all of the key names in a file. 
    /// ();
        using (var reader = ES3Reader.Create(settings))
        {
            foreach (string key in reader.Properties)
            {
                keys.Add(key);
                reader.Skip();
            }
        }
        return keys.ToArray();
    }
    /// Gets an array of all of the file names in a directory. 
    public static string[] GetFiles()
    {
        var settings = new ES3Settings();
        if (settings.location == ES3.Location.File)
        {
            if (settings.directory == ES3.Directory.PersistentDataPath)
                settings.path = Application.persistentDataPath;
            else 
                settings.path = Application.dataPath;
        }
        return GetFiles(settings);
    }
    /// Gets an array of all of the file names in a directory. 
    /// Gets an array of all of the file names in a directory. 
    /// Gets an array of all of the file names in a directory. 
    /// Gets an array of all of the sub-directory names in a directory. 
    public static string[] GetDirectories()
    {
        return GetDirectories(new ES3Settings());
    }
    /// Gets an array of all of the sub-directory names in a directory. 
    /// Gets an array of all of the sub-directory names in a directory. 
    /// Gets an array of all of the sub-directory names in a directory. 
    /// Creates a backup of the default file . 
    /// A backup is created by copying the file and giving it a .bak extension. 
    /// If a backup already exists it will be overwritten, so you will need to ensure that the old backup will not be required before calling this method. 
    public static void CreateBackup()
    {
        CreateBackup(new ES3Settings());
    }
    /// Creates a backup of a file. 
    /// A backup is created by copying the file and giving it a .bak extension. 
    /// If a backup already exists it will be overwritten, so you will need to ensure that the old backup will not be required before calling this method. 
    /// Creates a backup of a file. 
    /// A backup is created by copying the file and giving it a .bak extension. 
    /// If a backup already exists it will be overwritten, so you will need to ensure that the old backup will not be required before calling this method. 
    /// Creates a backup of a file. 
    /// A backup is created by copying the file and giving it a .bak extension. 
    /// If a backup already exists it will be overwritten, so you will need to ensure that the old backup will not be required before calling this method. 
    /// Restores a backup of a file. 
    /// True if a backup was restored, or False if no backup could be found. 
    public static bool RestoreBackup(string filePath)
    {
        return RestoreBackup(new ES3Settings(filePath));
    }
    /// Restores a backup of a file. 
    /// True if a backup was restored, or False if no backup could be found. 
    public static bool RestoreBackup(string filePath, ES3Settings settings)
    {
        return RestoreBackup(new ES3Settings(filePath, settings));
    }
    /// Restores a backup of a file. 
    /// True if a backup was restored, or False if no backup could be found. 
    public static bool RestoreBackup(ES3Settings settings)
    {
        var backupSettings = new ES3Settings(settings.path + ES3IO.backupFileSuffix, settings);
        if (!FileExists(backupSettings))
            return false;
        ES3.RenameFile(backupSettings, settings);
        return true;
    }
    public static DateTime GetTimestamp()
    {
        return GetTimestamp(new ES3Settings());
    }
    public static DateTime GetTimestamp(string filePath)
    {
        return GetTimestamp(new ES3Settings(filePath));
    }
    public static DateTime GetTimestamp(string filePath, ES3Settings settings)
    {
        return GetTimestamp(new ES3Settings(filePath, settings));
    }
    /// Gets the date and time the file was last updated, in the UTC timezone. 
    /// A DateTime object represeting the UTC date and time the file was last updated. 
    public static DateTime GetTimestamp(ES3Settings settings)
    {
        if (settings.location == Location.File)
            return ES3IO.GetTimestamp(settings.FullPath);
        else if (settings.location == Location.PlayerPrefs)
            return new DateTime(long.Parse(PlayerPrefs.GetString("timestamp_" + settings.FullPath, "0")), DateTimeKind.Utc);
        else if (settings.location == Location.Cache)
            return ES3File.GetTimestamp(settings);
        else
            return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    }
    /// Stores the default cached file to persistent storage. 
	/// A backup is created by copying the file and giving it a .bak extension. 
	/// If a backup already exists it will be overwritten, so you will need to ensure that the old backup will not be required before calling this method. 
	public static void StoreCachedFile()
    {
        ES3File.Store();
    }
    /// Stores a cached file to persistent storage. 
    /// Creates a backup of a file. 
    /// Stores a cached file to persistent storage. 
    /// Loads the default file in persistent storage into the cache. 
	/// A backup is created by copying the file and giving it a .bak extension. 
	/// If a backup already exists it will be overwritten, so you will need to ensure that the old backup will not be required before calling this method. 
	public static void CacheFile()
    {
        CacheFile(new ES3Settings());
    }
    /// Loads a file from persistent storage into the cache. 
    /// Creates a backup of a file. 
    /// Stores a cached file to persistent storage. 
    /// Initialises Easy Save. This happens automatically when any ES3 methods are called, but is useful if you want to perform initialisation before calling an ES3 method. 
    public static void Init()
    {
        var settings = ES3Settings.defaultSettings;
        ES3TypeMgr.Init();
    }
    #endregion
}