﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

using PlayFab;
using PlayFab.DataModels;

using UnityEngine;

namespace Bayat.SaveSystem.Storage
{

    public class PlayFabEntityObjectsStorage : StorageBase
    {

        public const string DefaultEntityType = "title_player_account";

        protected bool useBase64 = true;
        protected string entityId;
        protected string entityType;

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

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

        protected override Task CommitWriteStreamInternal(IStorageStream stream)
        {
            PlayFabEntityObjectsStorageStream playFabStream = (PlayFabEntityObjectsStorageStream)stream;
            SetObjectsRequest request = new SetObjectsRequest();
            request.Entity = new PlayFab.DataModels.EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            string data = null;
            if (this.useBase64)
            {
                data = '"' + Convert.ToBase64String(playFabStream.MemoryStream.ToArray()) + '"';
            }
            else
            {
                data = TextEncoding.GetString(playFabStream.MemoryStream.ToArray());
            }
            request.Objects = new List<SetObject>()
            {
                new SetObject() {
                    ObjectName = stream.Identifier,
                    EscapedDataObject = data
                }
            };
            TaskCompletionSource<SetObjectsResponse> taskCompletionSource = new TaskCompletionSource<SetObjectsResponse>();
            PlayFabDataAPI.SetObjects(request, result =>
            {
                taskCompletionSource.SetResult(result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override Task<IStorageStream> GetReadStream(string identifier)
        {
            TaskCompletionSource<IStorageStream> taskCompletionSource = new TaskCompletionSource<IStorageStream>();
            GetObjectsRequest request = new GetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.EscapeObject = true;
            PlayFabDataAPI.GetObjects(request, result =>
            {
                ObjectResult objectResult;
                if (result.Objects.TryGetValue(identifier, out objectResult))
                {
                    MemoryStream memoryStream = null;
                    if (this.useBase64)
                    {
                        memoryStream = new MemoryStream(Convert.FromBase64String(objectResult.EscapedDataObject.Substring(1, objectResult.EscapedDataObject.Length - 2)));
                    }
                    else
                    {
                        memoryStream = new MemoryStream(TextEncoding.GetBytes(objectResult.EscapedDataObject));
                    }
                    taskCompletionSource.SetResult(new PlayFabEntityObjectsStorageStream(identifier, memoryStream));
                }
                else
                {
                    taskCompletionSource.SetCanceled();
                }
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        protected override Task WriteAllTextInternal(string identifier, string data)
        {
            SetObjectsRequest request = new SetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.Objects = new List<SetObject>()
            {
                new SetObject() {
                    ObjectName = identifier,
                    EscapedDataObject = data
                }
            };
            TaskCompletionSource<SetObjectsResponse> taskCompletionSource = new TaskCompletionSource<SetObjectsResponse>();
            PlayFabDataAPI.SetObjects(request, result =>
            {
                taskCompletionSource.SetResult(result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override Task<string> ReadAllText(string identifier)
        {
            TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
            GetObjectsRequest request = new GetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.EscapeObject = true;
            PlayFabDataAPI.GetObjects(request, result =>
            {
                ObjectResult objectResult;
                if (result.Objects.TryGetValue(identifier, out objectResult))
                {
                    taskCompletionSource.SetResult(objectResult.EscapedDataObject);
                }
                else
                {
                    taskCompletionSource.SetCanceled();
                }
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        protected override Task WriteAllBytesInternal(string identifier, byte[] data)
        {
            SetObjectsRequest request = new SetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            string stringData = null;
            if (this.useBase64)
            {
                stringData = Convert.ToBase64String(data);
            }
            else
            {
                stringData = TextEncoding.GetString(data);
            }
            request.Objects = new List<SetObject>()
            {
                new SetObject() {
                    ObjectName = identifier,
                    EscapedDataObject = stringData
                }
            };
            TaskCompletionSource<SetObjectsResponse> taskCompletionSource = new TaskCompletionSource<SetObjectsResponse>();
            PlayFabDataAPI.SetObjects(request, result =>
            {
                taskCompletionSource.SetResult(result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override Task<byte[]> ReadAllBytes(string identifier)
        {
            TaskCompletionSource<byte[]> taskCompletionSource = new TaskCompletionSource<byte[]>();
            GetObjectsRequest request = new GetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.EscapeObject = true;
            PlayFabDataAPI.GetObjects(request, result =>
            {
                ObjectResult objectResult;
                if (result.Objects.TryGetValue(identifier, out objectResult))
                {
                    byte[] data = null;
                    if (this.useBase64)
                    {
                        data = Convert.FromBase64String(objectResult.EscapedDataObject);
                    }
                    else
                    {
                        data = TextEncoding.GetBytes(objectResult.EscapedDataObject);
                    }
                    taskCompletionSource.SetResult(data);
                }
                else
                {
                    taskCompletionSource.SetCanceled();
                }
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetCanceled();
            });
            return taskCompletionSource.Task;
        }

        public override async Task<StorageClearOperationResult> Clear()
        {
            TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
            List<string> removedObjects = new List<string>();
            bool allDeleted = true;
            GetObjectsRequest request = new GetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.EscapeObject = true;
            PlayFabDataAPI.GetObjects(request, async result =>
            {
                SetObjectsRequest deleteRequest = new SetObjectsRequest();
                deleteRequest.Entity = new EntityKey()
                {
                    Id = this.entityId,
                    Type = this.entityType
                };
                deleteRequest.Objects = new List<SetObject>();
                foreach (var obj in result.Objects)
                {
                    deleteRequest.Objects.Add(new SetObject()
                    {
                        ObjectName = obj.Key,
                        DeleteObject = true
                    });
                    removedObjects.Add(obj.Key);
                }
                TaskCompletionSource<bool> deleteTaskCompletionSource = new TaskCompletionSource<bool>();
                PlayFabDataAPI.SetObjects(deleteRequest, deleteResult =>
                {
                    deleteTaskCompletionSource.SetResult(true);
                }, error =>
                {
                    Debug.LogError(error.ErrorMessage);
                    deleteTaskCompletionSource.SetResult(false);
                    allDeleted = false;
                });
                await deleteTaskCompletionSource.Task;
                taskCompletionSource.SetResult(deleteTaskCompletionSource.Task.Result);
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(false);
            });
            await taskCompletionSource.Task;
            return new StorageClearOperationResult(allDeleted, removedObjects.ToArray());
        }

        protected override async Task<StorageDeleteOperationResult> DeleteInternal(string identifier)
        {
            SetObjectsRequest request = new SetObjectsRequest();
            bool succeed = true;
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.Objects = new List<SetObject>()
            {
                new SetObject() {
                    ObjectName = identifier,
                    DeleteObject = true
                }
            };
            TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
            PlayFabDataAPI.SetObjects(request, result =>
            {
                taskCompletionSource.SetResult(true);
                succeed = true;
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(false);
                succeed = false;
            });
            await taskCompletionSource.Task;
            return new StorageDeleteOperationResult(succeed);
        }

        public override Task<bool> Exists(string identifier)
        {
            TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
            GetObjectsRequest request = new GetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.EscapeObject = true;
            PlayFabDataAPI.GetObjects(request, result =>
            {
                taskCompletionSource.SetResult(result.Objects.ContainsKey(identifier));
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(false);
            });
            return taskCompletionSource.Task;
        }

        protected override async Task<StorageMoveOperationResult> MoveInternal(string oldIdentifier, string newIdentifier, bool replace)
        {
            string data = await ReadAllText(oldIdentifier);
            await WriteAllText(newIdentifier, data);
            StorageDeleteOperationResult deleteOp = await Delete(oldIdentifier);
            return new StorageMoveOperationResult(deleteOp.Succeed, newIdentifier);
        }

        protected override async Task<StorageCopyOperationResult> CopyInternal(string fromIdentifier, string toIdentifier, bool replace)
        {
            string data = await ReadAllText(fromIdentifier);
            await WriteAllText(toIdentifier, data);
            return new StorageCopyOperationResult(true, toIdentifier);
        }

        public override Task<string[]> List(string identifier, StorageListOptions options)
        {
            List<string> items = new List<string>();
            TaskCompletionSource<string[]> taskCompletionSource = new TaskCompletionSource<string[]>();
            GetObjectsRequest request = new GetObjectsRequest();
            request.Entity = new EntityKey()
            {
                Id = this.entityId,
                Type = this.entityType
            };
            request.EscapeObject = true;
            PlayFabDataAPI.GetObjects(request, result =>
            {
                foreach (var obj in result.Objects)
                {
                    items.Add(obj.Key);
                }
                taskCompletionSource.SetResult(items.FindAll(item => item.Contains(identifier)).ToArray());
            }, error =>
            {
                Debug.LogError(error.ErrorMessage);
                taskCompletionSource.SetResult(items.ToArray());
            });
            return taskCompletionSource.Task;
        }

        public override Task<string[]> ListAll()
        {
            return List(string.Empty, new StorageListOptions()
            {
                Recurse = true
            });
        }

        /// Disable Meta Data and Catalog handling
        public override Task SaveMetaData(string identifier, StorageMetaData metaData)
        {
            return Task.CompletedTask;
        }

        public override Task<StorageMetaData> LoadMetaData(string identifier)
        {
            return Task.FromResult(new StorageMetaData());
        }

        public override Task SaveCatalog(List<string> catalog)
        {
            return Task.CompletedTask;
        }

        public override Task<List<string>> LoadCatalog()
        {
            return Task.FromResult(new List<string>());
        }

    }

    /// <summary>
    /// The PlayFab entity objects storage stream wrapper.
    /// </summary>
    public class PlayFabEntityObjectsStorageStream : StorageStream
    {

        protected readonly MemoryStream memoryStream;

        public virtual MemoryStream MemoryStream
        {
            get
            {
                return this.memoryStream;
            }
        }

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

    }

}