﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
#if !UNITY_EDITOR && (UNITY_WSA || UNITY_WINRT)
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Storage;
using Windows.Storage.Streams;
#endif
using UnityEngine;

using Bayat.Core.Utilities;

using Steamworks;

namespace Bayat.SaveSystem.Storage
{

    /// <summary>
    /// Steam Cloud storage implementation.
    /// </summary>
    public class SteamCloudStorage : StorageBase
    {

        protected bool useBase64 = true;

        public SteamCloudStorage(string encodingName, bool useBase64) : base(encodingName)
        {
            this.useBase64 = useBase64;
        }

        public override Task<IStorageStream> GetWriteStream(string identifier)
        {
            return Task.FromResult<IStorageStream>(new SteamCloudStorageStream(identifier, new MemoryStream()));
        }

        protected override Task CommitWriteStreamInternal(IStorageStream stream)
        {
            SteamCloudStorageStream steamStream = (SteamCloudStorageStream)stream;
            byte[] pvData = steamStream.MemoryStream.ToArray();

            // Check quota and save the data if there are free quota available
            SteamRemoteStorage.GetQuota(out ulong pnTotalBytes, out ulong puAvailableBytes);
            if (pvData.Length > (long)puAvailableBytes)
            {
                SteamRemoteStorage.FileWrite(steamStream.Identifier, pvData, pvData.Length);
            }
            else
            {
                return Task.FromException(new SteamCloudStorageOutOfQuotaException(string.Format("Out of quota, total bytes: {0}, available bytes: {1}", pnTotalBytes, puAvailableBytes)));
            }
            return Task.CompletedTask;
        }

        public override Task<IStorageStream> GetReadStream(string identifier)
        {
            int cubDataToRead = SteamRemoteStorage.GetFileSize(identifier);
            byte[] pvData = new byte[cubDataToRead];
            SteamRemoteStorage.FileRead(identifier, pvData, cubDataToRead);
            return Task.FromResult<IStorageStream>(new SteamCloudStorageStream(identifier, new MemoryStream(pvData)));
        }

        protected override Task WriteAllTextInternal(string identifier, string data)
        {
            byte[] pvData = this.TextEncoding.GetBytes(data);

            // Check quota and save the data if there are free quota available
            SteamRemoteStorage.GetQuota(out ulong pnTotalBytes, out ulong puAvailableBytes);
            if (pvData.Length > (long)puAvailableBytes)
            {
                SteamRemoteStorage.FileWrite(identifier, pvData, pvData.Length);
            }
            else
            {
                return Task.FromException(new SteamCloudStorageOutOfQuotaException(string.Format("Out of quota, total bytes: {0}, available bytes: {1}", pnTotalBytes, puAvailableBytes)));
            }
            return Task.CompletedTask;
        }

        public override Task<string> ReadAllText(string identifier)
        {
            int cubDataToRead = SteamRemoteStorage.GetFileSize(identifier);
            byte[] pvData = new byte[cubDataToRead];
            SteamRemoteStorage.FileRead(identifier, pvData, cubDataToRead);
            return Task.FromResult(this.TextEncoding.GetString(pvData));
        }

        protected override Task WriteAllBytesInternal(string identifier, byte[] data)
        {

            // Check quota and save the data if there are free quota available
            SteamRemoteStorage.GetQuota(out ulong pnTotalBytes, out ulong puAvailableBytes);
            if (data.Length > (long)puAvailableBytes)
            {
                SteamRemoteStorage.FileWrite(identifier, data, data.Length);
            }
            else
            {
                return Task.FromException(new SteamCloudStorageOutOfQuotaException(string.Format("Out of quota, total bytes: {0}, available bytes: {1}", pnTotalBytes, puAvailableBytes)));
            }
            return Task.CompletedTask;
        }

        public override Task<byte[]> ReadAllBytes(string identifier)
        {
            int cubDataToRead = SteamRemoteStorage.GetFileSize(identifier);
            byte[] pvData = new byte[cubDataToRead];
            SteamRemoteStorage.FileRead(identifier, pvData, cubDataToRead);
            return Task.FromResult(pvData);
        }

        public override Task<StorageClearOperationResult> Clear()
        {
            bool result = true;
            int fileCount = SteamRemoteStorage.GetFileCount();
            string[] deletedItems = new string[fileCount];
            for (int iFile = 0; iFile < fileCount; iFile++)
            {
                string pchFile = SteamRemoteStorage.GetFileNameAndSize(iFile, out int pnFileSizeInBytes);
                result &= SteamRemoteStorage.FileDelete(pchFile);
                deletedItems[iFile] = pchFile;
            }
            return Task.FromResult(new StorageClearOperationResult(result, deletedItems));
        }

        protected override Task<StorageDeleteOperationResult> DeleteInternal(string identifier)
        {
            return Task.FromResult(new StorageDeleteOperationResult(SteamRemoteStorage.FileDelete(identifier)));
        }

        public override Task<bool> Exists(string identifier)
        {
            return Task.FromResult(SteamRemoteStorage.FileExists(identifier));
        }

        protected override Task<StorageMoveOperationResult> MoveInternal(string oldIdentifier, string newIdentifier, bool replace)
        {
            int size = SteamRemoteStorage.GetFileSize(oldIdentifier);
            byte[] pvData = new byte[size];
            SteamRemoteStorage.FileRead(oldIdentifier, pvData, size);
            SteamRemoteStorage.FileDelete(oldIdentifier);
            return Task.FromResult(new StorageMoveOperationResult(SteamRemoteStorage.FileWrite(newIdentifier, pvData, size), newIdentifier));
        }

        protected override Task<StorageCopyOperationResult> CopyInternal(string fromIdentifier, string toIdentifier, bool replace)
        {
            int size = SteamRemoteStorage.GetFileSize(fromIdentifier);
            byte[] pvData = new byte[size];
            SteamRemoteStorage.FileRead(fromIdentifier, pvData, size);
            return Task.FromResult(new StorageCopyOperationResult(SteamRemoteStorage.FileWrite(toIdentifier, pvData, size), toIdentifier));
        }

        public override Task<string[]> List(string identifier, StorageListOptions options)
        {
            int fileCount = SteamRemoteStorage.GetFileCount();
            string[] items = new string[fileCount];
            for (int iFile = 0; iFile < fileCount; iFile++)
            {
                items[iFile] = SteamRemoteStorage.GetFileNameAndSize(iFile, out int pnFileSizeInBytes);
            }
            return Task.FromResult(items);
        }

        public override Task<string[]> ListAll()
        {
            int fileCount = SteamRemoteStorage.GetFileCount();
            string[] items = new string[fileCount];
            for (int iFile = 0; iFile < fileCount; iFile++)
            {
                items[iFile] = SteamRemoteStorage.GetFileNameAndSize(iFile, out int pnFileSizeInBytes);
            }
            return Task.FromResult(items);
        }

    }

    /// <summary>
    /// The Steam Cloud storage stream wrapper.
    /// </summary>
    public class SteamCloudStorageStream : StorageStream
    {

        protected readonly MemoryStream memoryStream;

        /// <summary>
        /// The full path to the file.
        /// </summary>
        public virtual MemoryStream MemoryStream
        {
            get
            {
                return this.memoryStream;
            }
        }

        public SteamCloudStorageStream(string identifier, MemoryStream memoryStream) : base(identifier, memoryStream)
        {
            this.memoryStream = memoryStream;
        }

    }


    [Serializable]
    public class SteamCloudStorageException : Exception
    {
        public SteamCloudStorageException() { }
        public SteamCloudStorageException(string message) : base(message) { }
        public SteamCloudStorageException(string message, Exception inner) : base(message, inner) { }
        protected SteamCloudStorageException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
    }

    [Serializable]
    public class SteamCloudStorageOutOfQuotaException : SteamCloudStorageException
    {
        public SteamCloudStorageOutOfQuotaException() { }
        public SteamCloudStorageOutOfQuotaException(string message) : base(message) { }
        public SteamCloudStorageOutOfQuotaException(string message, Exception inner) : base(message, inner) { }
        protected SteamCloudStorageOutOfQuotaException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
    }

}