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

using Firebase;
using Firebase.Database;

using UnityEngine;

namespace Bayat.SaveSystem.Storage
{

    public class FirebaseRealtimeDatabaseStorage : StorageBase
    {

        protected bool useBase64 = true;
        protected FirebaseDatabase databaseInstance;
        protected DatabaseReference databaseReference;

        //public override bool UseCatalog
        //{
        //    get
        //    {
        //        return false;
        //    }
        //    set
        //    {

        //    }
        //}

        //public override bool UseMetaData
        //{
        //    get
        //    {
        //        return false;
        //    }
        //    set
        //    {

        //    }
        //}

        public override string MetaSuffix => "-meta";
        public override string CatalogIdentifier => "-catalog";

        public FirebaseRealtimeDatabaseStorage(string encodingName, bool useBase64, FirebaseDatabase databaseInstance, DatabaseReference databaseReference) : base(encodingName)
        {
            this.useBase64 = useBase64;
            this.databaseInstance = databaseInstance ?? FirebaseDatabase.DefaultInstance;
            this.databaseReference = databaseReference ?? this.databaseInstance.RootReference;
        }

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

        protected override Task CommitWriteStreamInternal(IStorageStream stream)
        {
            FirebaseRealtimeDatabaseStorageStream firebaseStream = (FirebaseRealtimeDatabaseStorageStream)stream;
            string data = null;
            if (this.useBase64)
            {
                data = Convert.ToBase64String(firebaseStream.MemoryStream.ToArray());
            }
            else
            {
                data = TextEncoding.GetString(firebaseStream.MemoryStream.ToArray());
            }
            return this.databaseReference.Child(stream.Identifier).SetValueAsync(data);
        }

        public override async Task<IStorageStream> GetReadStream(string identifier)
        {
            DataSnapshot dataSnapshot = await this.databaseReference.Child(identifier).GetValueAsync();
            MemoryStream memoryStream = null;
            if (this.useBase64)
            {
                memoryStream = new MemoryStream(Convert.FromBase64String((string)dataSnapshot.GetValue(true)));
            }
            else
            {
                memoryStream = new MemoryStream(TextEncoding.GetBytes((string)dataSnapshot.GetValue(true)));
            }
            return new FirebaseRealtimeDatabaseStorageStream(identifier, memoryStream);
        }

        protected override Task WriteAllTextInternal(string identifier, string data)
        {
            return this.databaseReference.Child(identifier).SetValueAsync(data);
        }

        public override async Task<string> ReadAllText(string identifier)
        {
            DataSnapshot dataSnapshot = await this.databaseReference.Child(identifier).GetValueAsync();
            return (string)dataSnapshot.GetValue(true);
        }

        protected override Task WriteAllBytesInternal(string identifier, byte[] data)
        {
            string stringData = null;
            if (this.useBase64)
            {
                stringData = Convert.ToBase64String(data);
            }
            else
            {
                stringData = TextEncoding.GetString(data);
            }
            return this.databaseReference.Child(identifier).SetValueAsync(stringData);
        }

        public override async Task<byte[]> ReadAllBytes(string identifier)
        {
            DataSnapshot dataSnapshot = await this.databaseReference.Child(identifier).GetValueAsync();
            byte[] data = null;
            if (this.useBase64)
            {
                data = Convert.FromBase64String((string)dataSnapshot.GetValue(true));
            }
            else
            {
                data = TextEncoding.GetBytes((string)dataSnapshot.GetValue(true));
            }
            return data;
        }

        public override async Task<StorageClearOperationResult> Clear()
        {
            await this.databaseReference.RemoveValueAsync();
            return new StorageClearOperationResult(true, null);
        }

        protected override async Task<StorageDeleteOperationResult> DeleteInternal(string identifier)
        {
            await this.databaseReference.Child(identifier).RemoveValueAsync();
            return new StorageDeleteOperationResult(true);
        }

        public override async Task<bool> Exists(string identifier)
        {
            DataSnapshot dataSnapshot = await this.databaseReference.Child(identifier).GetValueAsync();
            return dataSnapshot.Exists;
        }

        protected override async Task<StorageMoveOperationResult> MoveInternal(string oldIdentifier, string newIdentifier, bool replace)
        {
            string data = await ReadAllText(oldIdentifier);
            await WriteAllText(newIdentifier, data);
            await Delete(oldIdentifier);
            return new StorageMoveOperationResult(true, 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 async Task<string[]> List(string identifier, StorageListOptions options)
        {
            List<string> items = new List<string>();
            DataSnapshot dataSnapshot = await this.databaseReference.Child(identifier).GetValueAsync();
            foreach (DataSnapshot child in dataSnapshot.Children)
            {
                items.Add(identifier + child.Key);
                if (options.Recurse)
                {
                    items.AddRange(await List(identifier + child.Key, options));
                }
            }
            return items.ToArray();
        }

        public override async Task<string[]> ListAll()
        {
            List<string> items = new List<string>();
            StorageListOptions listOptions = new StorageListOptions()
            {
                Recurse = true
            };
            DataSnapshot dataSnapshot = await this.databaseReference.GetValueAsync();
            items.Add(this.databaseReference.Key);
            foreach (DataSnapshot child in dataSnapshot.Children)
            {
                items.Add(child.Key);
                items.AddRange(await List(child.Key, listOptions));
            }
            return items.ToArray();
        }

        private static string ValidateIdentifier(string identifier)
        {
            return identifier.Replace(".", string.Empty);
        }

    }

    /// <summary>
    /// The Firebase Realtime Database storage stream wrapper.
    /// </summary>
    public class FirebaseRealtimeDatabaseStorageStream : StorageStream
    {

        protected readonly MemoryStream memoryStream;

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

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

    }

}