using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
using async_enumerable_dotnet;
using System.Threading;
using Dacryptero.Data;
using Dacryptero.Search;
using Dacryptero.Search.AST;

namespace Dacryptero.DB
{
    public class DBSession : IDBDataQuery
    {
        protected readonly ConcurrentBag<DBSingleController> controller 
            = new ConcurrentBag<DBSingleController>();
        
        protected ReadOnlyMemory<DBSingleController> currentController
            = ReadOnlyMemory<DBSingleController>.Empty;

        protected void UpdateControllerList()
        {
            currentController = controller.ToArray();
        }

        public ReadOnlyMemory<DBSingleController> GetCurrentController()
            => currentController;

        public DBSingleController? GetController(string name)
        {
            foreach (var entry in currentController.Span)
                if (entry.Config.Name == name)
                    return entry;
            return null;
        }

        public DBSingleController AddController(DBConfig config)
        {
            var single = new DBSingleController(config);
            controller.Add(single);
            UpdateControllerList();
            return single;
        }

        public void AddController(DBSingleController controller)
        {
            this.controller.Add(controller);
            UpdateControllerList();
        }

        private IAsyncEnumerable<T> CombineValue<T>(
            Func<DBSingleController, IAsyncEnumerable<T>> fn)
        {
            return AsyncEnumerable.Merge(currentController.Select(fn).ToArray());
        }

        private async Task<DBValue<T>?> SearchValue<T>(
            Func<DBSingleController, Task<DBValue<T>?>> fn
        )
        {
            return await AsyncEnumerable
                .Merge(
                    controller
                        .Select(fn)
                        .Select(AsyncEnumerable.FromTask)
                        .ToArray()
                )
                .Filter(x => x is not null)
                .FirstAsync(null)
                .ConfigureAwait(false);
        }

        public async Task<DBValue<Person>?> GetPersonAsync(LiteDB.ObjectId id,
            CancellationToken cancellatonToken)
        {
            cancellatonToken.ThrowIfCancellationRequested();
            return await SearchValue(c => c.GetPersonAsync(id, cancellatonToken))
                .ConfigureAwait(false);
        }

        public async Task<DBValue<bool>?> UpdatePersonAsync(Data.Person person, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return await SearchValue(c => c.UpdatePersonAsync(person, cancellationToken))
                .ConfigureAwait(false);
        }

        public async Task<DBValue<Data.File>?> GetFileAsync(LiteDB.ObjectId id, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return await SearchValue(c => c.GetFileAsync(id, cancellationToken))
                .ConfigureAwait(false);
        }

        public async Task<DBValue<Data.FileEntry>?> GetFileEntryAsync(LiteDB.ObjectId id, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return await SearchValue(c => c.GetFileEntryAsync(id, cancellationToken))
                .ConfigureAwait(false);
        }

        public IAsyncEnumerable<DBValue<Data.File>> EnumerateFilesAsync(LiteDB.ObjectId person, LiteDB.ObjectId? root, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return CombineValue(c => c.EnumerateFilesAsync(person, root, cancellationToken));
        }

        public async Task<DBValue<bool>?> UpdateFile(Data.File file, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return await SearchValue(c => c.UpdateFile(file, cancellationToken))
                .ConfigureAwait(false);
        }

        public async Task<DBValue<Data.Interview>?> GetInterviewAsync(LiteDB.ObjectId id,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return await SearchValue(c => c.GetInterviewAsync(id, cancellationToken))
                .ConfigureAwait(false);
        }

        public IAsyncEnumerable<DBValue<Data.Person>> EnumerateRawPeopleFromInterviewAsync(
            LiteDB.ObjectId interview, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return CombineValue(
                c => c.EnumerateRawPeopleFromInterviewAsync(interview, cancellationToken)
            );
        }

        public async Task<DBValue<bool>?> UpdateInterviewAsync(Data.Interview interview, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            return await SearchValue(c => c.UpdateInterviewAsync(interview, cancellationToken))
                .ConfigureAwait(false);
        }

        public IAsyncEnumerable<SearchResult> DoSearch(AstBase ast, 
            Search.Sorting.SortConfig sortConfig, CancellationToken cancellationToken
        )
        {
            cancellationToken.ThrowIfCancellationRequested();
            return CombineValue(
                c => c.DoSearch(ast, sortConfig, cancellationToken)
            );
        }
    }
}