using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Threading.Tasks;

using C4IT.FASD.Base;
using C4IT.Logging;

using static C4IT.Logging.cLogManager;
using static C4IT.FASD.Base.cF4SDHealthCardRawData;
using System.Threading;

namespace C4IT.DataHistoryProvider
{
    public class cSuccessorJob
    {
        public List<string> Tables = new List<string>();
        public cF4SDHealthCardRawData data = null;
        public Task<bool> task = null;
        public CancellationToken Token = CancellationToken.None;
        public bool Finished = false;
        public DateTime FinishedTime;
        public List<Int64> Usage = null;
    }

    public class SuccessorEntry : List<cSuccessorJob>
    {
        public List<Int64> UsageInfo = null;
    }

    public static class SuccessorCache
    {
        private static Timer _timerCache = new Timer(callbackTimerCache, null, 1000, 10000);

        private static void callbackTimerCache(object state)
        {
            var myNow = DateTime.UtcNow;
            lock (Cache)
            {
                var lstIds = new List<Guid>();

                foreach (var Entry in Cache)
                {
                    var lstJobs = new List<cSuccessorJob>();
                    foreach (var Job in Entry.Value)
                    {
                        if (Job.Finished && (myNow - Job.FinishedTime) > TimeSpan.FromSeconds(60))
                            lstJobs.Add(Job);
                    }

                    foreach (var Job in lstJobs)
                        Entry.Value.Remove(Job);

                    if (Entry.Value.Count == 0)
                        lstIds.Add(Entry.Key);
                }

                foreach (var id in lstIds)
                    Cache.Remove(id);
            }
        }

        private static Dictionary<Guid, SuccessorEntry> Cache = new Dictionary<Guid, SuccessorEntry>();


        public static bool AddTask(Guid Id, List<string> Tables, Task<List<cF4SDHealthCardRawData.cHealthCardTable>> task, List<Int64> UsageInfo, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var Job = new cSuccessorJob();
                lock (Cache)
                {
                    if (!Cache.TryGetValue(Id, out var lst))
                    {
                        lst = new SuccessorEntry() { Job };
                        Cache.Add(Id, lst);
                    }
                    else
                        lst.Add(Job);

                    if (UsageInfo != null)
                        lst.UsageInfo = UsageInfo;
                }

                Job.Tables = Tables;
                Job.task = Task.Run<bool>(async () =>
                {
                    try
                    {
                        var res = await task;
                        Dictionary<string, cHealthCardTable> tbls = null;
                        if (res != null)
                            tbls = res.ToDictionary(x => x.Name, x => x);
                        lock (Cache)
                        {
                            Job.task = null;
                            Job.data = new cF4SDHealthCardRawData()
                            {
                                Id = Id,
                                Tables = tbls
                            };
                            Job.Finished = true;
                            Job.FinishedTime = DateTime.UtcNow;
                        }
                        return true;
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                    return false;
                }, Token);


                return true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public static async Task<cF4SDHealthCardRawData> GetNextResult(cDataHistoryCollector Collector, Guid Id, bool noAwait, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
            var _startTime = DateTime.UtcNow;

            try
            {
                var lstResults = new SuccessorEntry();
                var lstTasks = new SuccessorEntry();
                var lstWaitingTables = new List<string>();
                lock (Cache)
                {
                    if (!Cache.TryGetValue(Id, out var lstObj))
                        return null;

                    foreach (var Entry in lstObj)
                    {
                        if (Entry.Finished)
                            lstResults.Add(Entry);
                        else
                        {
                            lstTasks.Add(Entry);
                            lstWaitingTables.AddRange(Entry.Tables);
                        }
                    }
                    foreach (var Entry in lstResults)
                        lstObj.Remove(Entry);
                }

                if (lstTasks.Count == 0)
                    Cache.Remove(Id);

                if (lstResults.Count > 0)
                {
                    var RetVal = lstResults[0];
                    for (int i = 1; i < lstResults.Count; i++)
                    {
                        var res = lstResults[i];
                        if (res?.data?.Tables != null)
                        {
                            foreach (var Entry in res.data.Tables)
                            {
                                if (RetVal?.data?.Tables != null)
                                    RetVal.data.Tables[Entry.Key] = Entry.Value;
                                else
                                {

                                }
                            }
                        }
                        else
                        {

                        }
                    }
                    foreach (var Table in lstWaitingTables)
                    {
                        var tbl = new cF4SDHealthCardRawData.cHealthCardTable()
                        {
                            Name = Table,
                            IsIncomplete = true,
                        };
                        if (RetVal?.data?.Tables != null)
                            RetVal.data.Tables[Table] = tbl;
                    }

                    // correct the result due to usage information
                    if (RetVal.data != null && lstTasks.UsageInfo != null)
                        cDataHistoryCollector.CorrectUsage(Collector, RetVal.data, lstTasks.UsageInfo);

                    return RetVal.data;
                }

                if (noAwait)
                    return null;

                var arrTask = new Task<bool>[lstTasks.Count];
                for (int i = 0; i < lstTasks.Count; i++)
                    arrTask[i] = lstTasks[i].task;

                var tskFinished = await Task.WhenAny(arrTask);

                return await GetNextResult(Collector, Id, true, requestInfo, LogDeep+1);

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

    }
}