using C4IT.FASD.Base;
using C4IT.FileImport;
using C4IT.Logging;
using C4IT.Nexthink.NXQL;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static C4IT.DataHistoryProvider.cNxqlDailyCollector;
using static C4IT.Logging.cLogManager;

namespace C4IT.DataHistoryProvider
{
    public class cDataHistoryCollectorNxql : cDataHistoryCollectorModule
    {
        public const string constConnectorName = "Nexthink NXQL conntector";
        public const string constLicenseId = "51EA7764-3AD3-4F90-89DB-DEB0C60D655C";

        public cNxqlColumnsValidationResult LastValidationResults = null;

        public cDataHistoryCollectorNxql(cDataHistoryCollector Collector) : base(Collector, enumDataHistoryOrigin.NexthinkNxql, constConnectorName, constLicenseId)
        {
        }

        public static bool IsNxqlColumn(cDataHistoryConfigColumnBase Entry)
        {
            if (Entry is cDataHistoryConfigColumnNxtAction)
                return true;
            if (Entry is cDataHistoryConfigColumnNxtCampaign)
                return true;
            if (Entry is cDataHistoryConfigColumnNxtCategory)
                return true;
            if (Entry is cDataHistoryConfigColumnNxtScore)
                return true;
            if (Entry is cDataHistoryConfigColumnNxtCompute)
                return true;
            if (Entry is cDataHistoryConfigColumn)
                return true;
            return false;
        }

        private static string GetTimeFilter(DateTime? time, bool isLeft)
        {
            if (time == null)
                return "now";

            var time2 = (DateTime)time;
            time2 = time2.AddTicks(-(time2.Ticks % 10000000));

            if (isLeft)
                time2.AddSeconds(-1);

            string strFrom = string.Format("{0:yyyy-MM-dd HH:mm:ss}", time2);
            strFrom = strFrom.Replace(' ', '@');
            return strFrom;
        }

        private static string GetTimeFilter(DateTime? From, DateTime? To)
        {
            var strFrom = GetTimeFilter(From, true);
            var strTo = GetTimeFilter(To, false);
            return string.Format("(between {0} {1})", strFrom, strTo);
        }

        private static string GetNxqlColumnName(string Column, bool isCustom)
        {
            var RV = Column;
            if (isCustom)
            {
                RV = Column.Replace("\"", "\"\"");
                RV = $"#\"{RV}\"";
            }
            return RV;
        }

        public static string GetNxqlColumnName(cDataHistoryConfigColumnBase Entry)
        {
            var strCol = GetNxqlColumnName2(Entry, true);
            if (!string.IsNullOrEmpty(Entry.SourceTable))
                strCol = $"({Entry.SourceTable} {strCol})";

            return strCol;
        }

        public static string GetNxqlColumnResultName(cDataHistoryConfigColumnBase Entry)
        {
            var strCol = GetNxqlColumnName2(Entry, false).ToLowerInvariant();
            if (strCol.StartsWith("*"))
                strCol = strCol.Remove(0, 1);
            if (!string.IsNullOrEmpty(Entry.SourceTable))
                strCol = $"{Entry.SourceTable.ToLowerInvariant()}/{strCol}";

            return strCol;
        }

        private static string GetNxqlColumnName2(cDataHistoryConfigColumnBase Entry, bool withCustom)
        {
            if (Entry is cDataHistoryConfigColumnNxtAction _ac)
            {
                return GetNxqlColumnName($"action:{_ac.ActionName}/{_ac.ValueName}", withCustom);
            }
            else if (Entry is cDataHistoryConfigColumnNxtScore _sc)
            {
                return GetNxqlColumnName($"score:{_sc.ScoreName}/{_sc.ValueName}{(_sc.IsPayload ? "/payload" : "")}", withCustom);
            }
            else if (Entry is cDataHistoryConfigColumnNxtCategory _ca)
            {
                return GetNxqlColumnName(_ca.SourceName, withCustom);
            }
            else if (Entry is cDataHistoryConfigColumnNxtCampaign _cn)
            {
                var strIsText = "";
                if (_cn.AsText)
                    strIsText = " (text)";
                return GetNxqlColumnName($"campaign:{_cn.CampaignName}/{_cn.ValueName}{strIsText}", withCustom);
            }
            else if (Entry is cDataHistoryConfigColumnNxtCompute _cp)
            {
                return GetNxqlColumnName(_cp.SourceName, false);
            }
            else if (Entry is cDataHistoryConfigColumn _co)
            {
                return GetNxqlColumnName(_co.SourceName, false);
            }
            return null;
        }

        public static string getNxqlStatement(string NxqlQuery, enumFasdInformationClass Class, List<object> KeyValues, List<cDataHistoryConfigColumnBase> KeyColumns, uint Limit, DateTime? From, DateTime? To, List<cDataHistoryConfigColumnBase> Columns, string TableName)
        {
            try
            {
                var strLimit = "";
                if (Limit >= 0)
                    strLimit = $"(limit {Limit})";

                var strTimeFilter = GetTimeFilter(From, To);

                var strKeyFilter = "";
                if (KeyColumns != null && KeyValues != null && KeyValues.Count > 0)
                {
                    var keyVal = Convert.ToInt64(KeyValues[0]);
                    var tbn = "device";
                    if (KeyColumns[0]?.ParentTable?.ParentTable?.ParentCluster?.InformationClass == enumFasdInformationClass.User)
                        tbn = "user";
                    strKeyFilter = $"(where {tbn} (eq id (identifier \"{keyVal}\")))";
                }

                var strColumns = "";
                var strCompus = "";
                var cols = new HashSet<string>();
                foreach (var Entry in Columns)
                {
                    if (Entry is cDataHistoryConfigColumn _col && _col.SourceType == eDataHistoryQueryType.Static)
                        continue;

                    string strCol = GetNxqlColumnName(Entry);
                    if (strCol == null)
                        continue;
                    if (cols.Contains(strCol))
                        continue;
                    cols.Add(strCol);
                    if (Entry is cDataHistoryConfigColumnNxtCompute)
                    {
                        if (strCompus != "")
                            strCompus += " ";
                        strCompus += strCol;
                    }
                    else
                    {
                        if (strColumns != "")
                            strColumns += " ";
                        strColumns += strCol;
                    }
                }

                if (strColumns == "")
                {
                    cDataHistoryConfigColumnBase firstCol = null;
                    if (KeyColumns != null)
                        foreach (var Col in KeyColumns)
                        {
                            if (Col is cDataHistoryConfigColumn _c && _c.SourceType == eDataHistoryQueryType.Static)
                                continue;
                            firstCol = Col;
                            break;
                        }

                    if (firstCol == null)
                    {
                        LogEntry($"The NXQL query with table name {TableName} has no valid columns nor a valid key column", LogLevels.Warning);
                        return null;
                    }

                    strColumns = GetNxqlColumnName(firstCol);
                }
                strColumns = "(" + strColumns + ")";
                if (!string.IsNullOrEmpty(strCompus))
                    strCompus = $"(compute {strCompus})";

                var RetVal = NxqlQuery.Replace("#Columns#", strColumns);
                RetVal = RetVal.Replace("#KeyFilter#", strKeyFilter);
                RetVal = RetVal.Replace("#Limit#", strLimit);
                RetVal = RetVal.Replace("#TimeFilter#", strTimeFilter);
                RetVal = RetVal.Replace("#Compute#", strCompus);

                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
            }

            return null;
        }

        public override async Task<List<object>> GetIds(cF4sdConnectorIds IdEntry, enumFasdInformationClass InfoClass, 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;

            var _retVal = new List<object>();

            try
            {
                Dictionary<string, object> Params;
                string QuerName;
                switch (InfoClass)
                {
                    case enumFasdInformationClass.Computer:
                        Params = new Dictionary<string, object>() { { "NAME", IdEntry.name.ToUpperInvariant() } };
                        QuerName = "FindNxtIdsComputer";
                        break;
                    case enumFasdInformationClass.User:
                        Params = new Dictionary<string, object>() { { "SID", IdEntry.sid } };
                        QuerName = "FindNxtIdsUser";
                        break;
                    default:
                        return _retVal;
                }

                if (!DataHistorySqlHelper.GetWellKnownSqlStatement(QuerName, out var Query))
                    return _retVal;

                var sqlerror = 0;
                var sqlStartTime = DateTime.UtcNow;


                try
                {
                    using (var Conn = new cDbConnection(Collector.mainDbConnection))
                    {
                        if (!Conn.IsOpen)
                        {
                            LogEntry($"Could not open main sql database '{Collector.mainDbConnection.Database}', aborting saving NXQL column validation result", LogLevels.Error);
                            return _retVal;
                        }

                        using (var Reader = await DataHistorySqlHelper.GetTableResultAsync(Conn, Query, Params, CancellationToken.None))
                        {
                            if (Reader == null)
                                return _retVal;

                            try
                            {
                                if (Reader.HasRows)
                                {
                                    if (await Reader.ReadAsync())
                                    {
                                        try
                                        {
                                            var Engine = Reader.GetString(0);
                                            var NxtId = Reader.GetInt64(1);

                                            return new List<object>() { Engine, NxtId };
                                        }
                                        catch (Exception E)
                                        {
                                            cLogManager.DefaultLogger.LogException(E);
                                        }
                                    }
                                }
                            }
                            catch (Exception E)
                            {
                                cLogManager.DefaultLogger.LogException(E);
                            }
                        }
                    }
                }
                catch (Exception E)
                {
                    sqlerror = E.HResult;
                    LogException(E);
                }
                finally
                {
                    if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, sqlStartTime, Params, sqlerror, requestInfo?.requestName);
                }

                using (var Conn = new cDbConnection(Collector.mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{Collector.mainDbConnection.Database}', aborting saving NXQL column validation result", LogLevels.Error);
                        return _retVal;
                    }

                    using (var Reader = await DataHistorySqlHelper.GetTableResultAsync(Conn, Query, Params, CancellationToken.None))
                    {
                        if (Reader == null)
                            return _retVal;

                        try
                        {
                            if (Reader.HasRows)
                            {
                                if (await Reader.ReadAsync())
                                {
                                    try
                                    {
                                        var Engine = Reader.GetString(0);
                                        var NxtId = Reader.GetInt64(1);

                                        return new List<object>() { Engine, NxtId };
                                    }
                                    catch (Exception E)
                                    {
                                        cLogManager.DefaultLogger.LogException(E);
                                    }
                                }
                            }
                        }
                        catch (Exception E)
                        {
                            cLogManager.DefaultLogger.LogException(E);
                        }
                    }
                }
            }
            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 _retVal;
        }

        public override async Task<cScanTimeInfo> GetScanTimeInfoAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            return await GetScanTimeInfoAsync(Collector.InfrastructureConfig.Nexthink.ScanTiming, "NxqlScan-all", requestInfo, LogDeep, Token);
        }

        public override void TablePostConfig(cDataHistoryConfigTable Table)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (Table.Cached == eDataHistoryTableCached.Default)
                {
                    if (Table.Type != eDataHistoryTableType.Static)
                        Table.Cached = eDataHistoryTableCached.Yes;
                    else
                        Table.Cached = eDataHistoryTableCached.No;
                }
                Table.IsVirtual = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

        }

        public override async Task<bool> DoScanAsync(bool Always, bool Rescan, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool EnhancedDebug = false)
        {
            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;

            var ShowFinished = false;
            try
            {
                await Task.Run<bool>(async () =>
                {
                    try
                    {
                        Collector.DoProcessUiMessage(0, "");

                        var scanInfo = await GetScanTimeInfoAsync(requestInfo, LogDeep + 1, Token);
                        if (scanInfo != null)
                        {
                            if (scanInfo.NextScan == null)
                            {
                                Collector.DoProcessUiMessage(0, "not valid next scan time info for NXQL connector");
                                return false;
                            }

                            var nextScan = (DateTime)scanInfo.NextScan;
                            if (!(scanInfo.LastScan is DateTime lastScan) || (lastScan >= nextScan))
                            {
                                Collector.DoProcessUiMessage(0, $"currently no scan needed for NXQL connector, lastScan={scanInfo.LastScan} (UTC), nextScan={scanInfo.NextScan} (UTC)");
                                return false;
                            }
                        }
                        else
                        {
                            if (!Always)
                            {
                                Collector.DoProcessUiMessage(0, "no valid NXQL scan done yet, please start a scan manually.");
                                return false;
                            }

                            scanInfo = new cScanTimeInfo(DateTime.MinValue);
                            scanInfo.GetFirstScanTime(Collector.InfrastructureConfig.Nexthink.ScanTiming);
                        }

                        ShowFinished = true;
                        Collector.DoProcessUiMessage(0, "Starting NXQL scan...");
                        Collector.DoProcessUiMessage(0, "");

                        if (LastValidationResults == null)
                        {
                            Collector.DoProcessUiMessage(0, "Starting NXQL column validation...");
                            Collector.DoProcessUiMessage(0, "");
                            var ColumnsValidator = new cNxqlColumnValidator(Collector);
                            await ColumnsValidator.DoValidationAsync(requestInfo, LogDeep + 1, Token).ConfigureAwait(false);
                            LastValidationResults = ColumnsValidator.ValidationResults;
                            Collector.DoProcessUiMessage(0, "NXQL column validation finished");
                            Collector.DoProcessUiMessage(0, "");
                        }
                        if (LastValidationResults == null)
                            return false;

                        foreach (var ScanInterval in scanInfo.Intervals)
                        {
                            if (Token.IsCancellationRequested)
                                return false;

                            await DoScanIntervalAsync("NxqlScan-all", ScanInterval.Key, ScanInterval.Value, requestInfo, LogDeep + 1, Token, EnhancedDebug);
                        }

                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }

                    return false;
                });

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                Collector.DoProcessUiMessage(0, "");
                if (ShowFinished)
                    Collector.DoProcessUiMessage(0, "NXQL scan finished.");
                if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
                if (CM != null) LogMethodEnd(CM);
            }

            return false;
        }

        public async Task<bool> DoScanIntervalAsync(string ScanName, DateTime FromTime, DateTime ToTime, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, bool EnhancedDebug)
        {
            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
            {
                // create the scan history entry and get the id
                var ScanId = await Collector.CreateScanHistoryEntry(ScanName, FromTime, ToTime, requestInfo, LogDeep + 1, Token);

                if (Token.IsCancellationRequested)
                    return false;

                var Validator = new cNxqlDataCollector(Collector, LastValidationResults, ScanId, EnhancedDebug);
                Validator.CreateJobs(FromTime, ToTime, requestInfo, LogDeep + 1);

                await Validator.Orchestrator.ProcessAsync(CancellationToken.None).ConfigureAwait(false);

                if (Token.IsCancellationRequested)
                    return false;

                var _sum = Validator.validResultCount + Validator.validResultCount;
                if (_sum > 0 && (Validator.errorResultCount * 1000 / _sum) > 100)
                {
                    LogEntry($"To many invalid NXQL scan to complete scan (valid: {Validator.validResultCount}, errors: {Validator.validResultCount}.", LogLevels.Error);
                    return false;
                }

                await Collector.ConfirmScanHistoryEntry(ScanId, requestInfo, LogDeep + 1, Token);

                return true;
            }
            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 false;

        }

        public override bool CheckIfLateDelivery(cDataHistoryConfigTable Table)
        {
            return Table.Cached == eDataHistoryTableCached.Yes;
        }

        private async Task GetTableValuesSqlAsync(List<Task<List<cF4SDHealthCardRawData.cHealthCardTable>>> lstTasks, Dictionary<string, cF4SDHealthCardRawData.cHealthCardTable> RetVal, 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 Result = await Task.WhenAll(lstTasks).ConfigureAwait(false);
                if (Result != null)
                {
                    LogEntry($"SQL task result for NXQL tables: {Result.Length}", LogLevels.Debug);
                    foreach (var Entry in Result)
                    {
                        if (Entry != null)
                        {
                            if (Entry.Count == 0)
                                LogEntry($"No table result for SQL task result for NXQL tables.", LogLevels.Debug);
                            foreach (var Entry2 in Entry)
                                RetVal[Entry2.Name] = Entry2;
                        }
                        else
                            LogEntry($"Empty result in SQL task result for NXQL tables.", LogLevels.Warning);
                    }
                }
                else
                    LogEntry($"Could not get a valid SQL result for the NXQL tables", LogLevels.Warning);
            }
            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);
            }

        }

        private async Task<Dictionary<string, cTableResult>> GetTableValuesNxqlAsync(List<object> ComputerIds, List<object> UserIds, List<cDataHistoryConfigTable> lstTables, DateTime TodayFrom, DateTime TodayTo, CancellationToken Token, 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
            {
                if ((ComputerIds != null && ComputerIds.Count >= 2) ||
                    (UserIds != null && UserIds.Count >= 2))
                {
                    var ColVaidation = new cNxqlColumnsValidationResult();
                    await ColVaidation.LoadFromSqlAsync(Collector, Token, requestInfo, LogDeep + 1);

                    if (ColVaidation == null || ColVaidation.Count == 0)
                        LogEntry($"No valid column validation entries found.", LogLevels.Warning);

                    var NxqlCollector = new cNxqlDailyCollector(Collector, ColVaidation);

                    if ((ComputerIds != null && ComputerIds.Count >= 2))
                        NxqlCollector.CreateJobs(lstTables, TodayFrom, TodayTo, ComputerIds[0].ToString(), Convert.ToInt64(ComputerIds[1]), enumFasdInformationClass.Computer);
                    if ((UserIds != null && UserIds.Count >= 2))
                        NxqlCollector.CreateJobs(lstTables, TodayFrom, TodayTo, UserIds[0].ToString(), Convert.ToInt64(UserIds[1]), enumFasdInformationClass.User);

                    await NxqlCollector.Orchestrator.ProcessAsync(Token);
                    return NxqlCollector.Results;
                }

            }
            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;
        }

        public override async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetTableResultsVirtualAsync(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, bool instantly, Guid? CacheId, CancellationToken Token, 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 lstNxqlTables = new List<cDataHistoryConfigTable>();
                foreach (var Table in Tables)
                {
                    if (!this.Origins.Contains(Table.ParentCluster.Origin))
                        continue;

                    lstNxqlTables.Add(Table);
                }
                if (lstNxqlTables.Count == 0)
                    return null;

                if (!instantly && CacheId == null)
                {
                    var RetValCached = new List<cF4SDHealthCardRawData.cHealthCardTable>();
                    foreach (var Table in lstNxqlTables)
                    {
                        RetValCached.Add(new cF4SDHealthCardRawData.cHealthCardTable()
                        {
                            Name = Table.Name,
                            InformationClass = Table.ParentCluster.InformationClass,
                            Origin = Table.ParentCluster.Origin,
                            IsIncomplete = true
                        }); ;
                    }

                    return RetValCached;
                }

                List<object> ComputerIds = null;
                List<object> UserIds = null;

                var lstTasks = new List<Task<List<cF4SDHealthCardRawData.cHealthCardTable>>>(Tables.Count);
                var lstTables = new List<cDataHistoryConfigTable>();

                var TodayTo = DateTime.UtcNow;
                var _sci = new cScanTimeInfo(TodayTo);
                var TodayFrom = _sci.GetScanTime(TodayTo, Collector.InfrastructureConfig.Nexthink.ScanTiming);

                LogEntry($"Todays timeframe: {TodayFrom} - {TodayTo}", LogLevels.Debug);

                foreach (var Table in lstNxqlTables)
                {
                    List<object> lstFilter;

                    switch (Table.ParentCluster.InformationClass)
                    {
                        case enumFasdInformationClass.Computer:
                            if (ComputerIds == null)
                                if (Identities.TryGetValue(enumFasdInformationClass.Computer, out var idsComputer))
                                    ComputerIds = await this.GetIds(idsComputer, enumFasdInformationClass.Computer, requestInfo, LogDeep + 1);
                            lstFilter = ComputerIds;
                            if (!lstTables.Contains(Table))
                                lstTables.Add(Table);
                            break;
                        case enumFasdInformationClass.User:
                            if (UserIds == null)
                                if (Identities.TryGetValue(enumFasdInformationClass.User, out var idsUser))
                                    UserIds = await this.GetIds(idsUser, enumFasdInformationClass.User, requestInfo, LogDeep + 1);
                            lstFilter = UserIds;
                            if (!lstTables.Contains(Table))
                                lstTables.Add(Table);
                            break;
                        default:
                            continue;
                    }

                    if (!(lstFilter?.Count >= 2))
                    {
                        LogEntry($"No valid filter properties for NXQL table '{Table?.Name}'", LogLevels.Warning);
                        continue;
                    }

                    var Filter = new Dictionary<string, object>(2) { { "engine", lstFilter[0] }, { "id_int", lstFilter[1] } };
                    lstTasks.Add(Collector.GetCachedHistoricTable(Table, Filter, RefTime, MaxAge, Collector.InfrastructureConfig.Nexthink.ScanTiming, Token, requestInfo, LogDeep + 1));
                }

                object ComputerId1 = null;
                object ComputerId2 = null;
                if (ComputerIds != null)
                {
                    ComputerId1 = ComputerIds.Count >= 1 ? ComputerIds[0] : null;
                    ComputerId2 = ComputerIds.Count >= 1 ? ComputerIds[1] : null;
                }
                LogEntry($"Computer IDs: {ComputerId1}, {ComputerId2}", LogLevels.Debug);

                object UserId1 = null;
                object UserId2 = null;
                if (UserIds != null)
                {
                    UserId1 = UserIds.Count >= 1 ? UserIds[0] : null;
                    UserId2 = UserIds.Count >= 1 ? UserIds[1] : null;
                }
                LogEntry($"User IDs: {UserId1}, {UserId2}", LogLevels.Debug);

                var RetVal = new Dictionary<string, cF4SDHealthCardRawData.cHealthCardTable>();
                Dictionary<string, cTableResult> DailyResult = null;

                var tsk1 = Task.Run(async () =>
                {
                    await GetTableValuesSqlAsync(lstTasks, RetVal, requestInfo, LogDeep + 1);
                });

                var tsk2 = Task.Run(async () =>
                {
                    DailyResult = await GetTableValuesNxqlAsync(ComputerIds, UserIds, lstTables, TodayFrom, TodayTo, Token, requestInfo, LogDeep + 1);
                });

                await Task.WhenAll(new Task[] { tsk1, tsk2 });

                if (!tsk1.IsCompleted)
                    LogEntry("The SQL task results for NXQL tables did not complete.", LogLevels.Warning);
                if (!tsk2.IsCompleted)
                    LogEntry("The todays NXQL task results for NXQL tables did not complete.", LogLevels.Warning);

                if (DailyResult == null)
                    return null;

                foreach (var DailyResultEntry in DailyResult)
                {
                    try
                    {
                        var TableName = DailyResultEntry.Key;
                        var DailyValues = DailyResultEntry.Value;

                        if (DefaultLogger.IsDebug) LogEntry($"Including daily values in NXQL result for table '{TableName}'.", LogLevels.Debug);

                        if (RetVal.TryGetValue(TableName, out var SqlValues))
                        {
                            if (DefaultLogger.IsDebug) LogEntry($"Including daily values in existing SQL result for '{TableName}'.", LogLevels.Debug);

                            if (SqlValues.StartingIndex == int.MaxValue)
                                SqlValues.StartingIndex = 1;

                            foreach (var DailyValueEntry in DailyValues)
                            {
                                if (SqlValues.Columns.TryGetValue(DailyValueEntry.Key, out var SqlValueEntry))
                                {
                                    if (SqlValues.StartingIndex > 0)
                                    {
                                        for (int i = 0; i < SqlValues.StartingIndex - 1; i++)
                                            SqlValueEntry.Values.Insert(0, null);
                                        SqlValueEntry.Values.Insert(0, DailyValueEntry.Value);
                                    }
                                }
                                else
                                {
                                    SqlValueEntry = new cF4SDHealthCardRawData.cHealthCardTableColumn(SqlValues)
                                    {
                                        ColumnName = DailyValueEntry.Key,
                                        Values = new List<object>(1) { DailyValueEntry.Value }
                                    };
                                    SqlValues.Columns.Add(DailyValueEntry.Key, SqlValueEntry);
                                }
                            }

                            foreach (var SqlValueEntry in SqlValues.Columns.Values)
                            {
                                if (DailyValues.ContainsKey(SqlValueEntry.ColumnName))
                                    continue;

                                if (SqlValues.StartingIndex > 0)
                                {
                                    for (int i = 0; i < SqlValues.StartingIndex - 1; i++)
                                        SqlValueEntry.Values.Insert(0, null);
                                    SqlValueEntry.Values.Insert(0, null);
                                }
                            }

                            if (DefaultLogger.IsDebug) LogEntry($"Including timeframe in existing SQL result for '{TableName}'.", LogLevels.Debug);
                            if (SqlValues.StartingIndex > 0 && SqlValues.TimeFrames != null)
                            {
                                try
                                {
                                    var lstTimeframes = new List<KeyValuePair<DateTime, DateTime>>
                                    {
                                        new KeyValuePair<DateTime, DateTime>(TodayFrom, TodayTo)
                                    };
                                    for (int i = 0; i < SqlValues.StartingIndex - 1; i++)
                                        lstTimeframes.Add(new KeyValuePair<DateTime, DateTime>(DateTime.MinValue, DateTime.MinValue));
                                    for (int j = 0; j < SqlValues.TimeFrames.Length / 2; j++)
                                        lstTimeframes.Add(new KeyValuePair<DateTime, DateTime>(SqlValues.TimeFrames[j, 0], SqlValues.TimeFrames[j, 1]));
                                    SqlValues.TimeFrames = new DateTime[lstTimeframes.Count, 2];
                                    for (int i = 0; i < lstTimeframes.Count; i++)
                                    {
                                        SqlValues.TimeFrames[i, 0] = lstTimeframes[i].Key;
                                        SqlValues.TimeFrames[i, 1] = lstTimeframes[i].Value;
                                    }
                                }
                                catch (Exception E)
                                {
                                    LogException(E);
                                }
                            }

                            SqlValues.StartingIndex = Math.Min(SqlValues.StartingIndex, 0);
                        }
                        else
                        {
                            if (DefaultLogger.IsDebug) LogEntry($"No valid SQL data found for NXQL results for table '{TableName}', creating a new table result with todays values only", LogLevels.Debug);
                            var TableResult = new cF4SDHealthCardRawData.cHealthCardTable()
                            {
                                Name = TableName,
                                InformationClass = DailyValues.InformationClass,
                                Origin = enumDataHistoryOrigin.NexthinkNxql,
                                IsIncomplete = true,
                                IsStatic = false,
                                TableType = eDataHistoryTableType.History,
                                StartingIndex = 0,
                                TimeFrames = new DateTime[1, 2] { { TodayFrom, TodayTo } },
                                Columns = new Dictionary<string, cF4SDHealthCardRawData.cHealthCardTableColumn>()
                            };

                            foreach (var ColEntry in DailyValues)
                            {
                                var _col = new cF4SDHealthCardRawData.cHealthCardTableColumn(TableResult)
                                {
                                    ColumnName = ColEntry.Key,
                                    Values = new List<object>(1) { ColEntry.Value }
                                };
                                TableResult.Columns[_col.ColumnName] = _col;
                            }

                            RetVal[TableName] = TableResult;
                        }
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                }

                if (RetVal.Count > 0)
                    return RetVal.Values.ToList();

            }
            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;
        }

    }

    public class cNxqlColumnValidator
    {
        public readonly cDataHistoryCollector Collector;
        public readonly cNxqlOrchestration Orchestrator;

        public readonly cNxqlColumnsValidationResult ValidationResults = new cNxqlColumnsValidationResult();

        public cNxqlColumnValidator(cDataHistoryCollector Collector)
        {
            this.Collector = Collector;
            Orchestrator = new cNxqlOrchestration("ColumnValidator");
        }

        public int SetNxqlJobsTables()
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var QueryCount = 0;
                foreach (var Portal in Collector.InfrastructureConfig.Nexthink.Portals.Values)
                {
                    var lstServer = Portal.Engines.Values.ToList();
                    if (lstServer.Count == 0)
                        continue;

                    var PortalResult = new cNxqlPortalValidationResult();
                    var indexServer = 0;
                    foreach (var Table in Collector.ClusterConfig.Tables.Values)
                    {
                        if (Table.ParentCluster.Origin != enumDataHistoryOrigin.NexthinkNxql)
                            continue;

                        foreach (var SubTable in Table.SubTables.Values)
                        {
                            if (!(SubTable.Template is cDataHistoryConfigQueryTemplateNxql NxqlTemplate))
                                continue;

                            bool isValidTable = true;
                            foreach (var Column in SubTable.ParentTable.KeyColumns)
                            {
                                if (!cDataHistoryCollectorNxql.IsNxqlColumn(Column) && (Column is cDataHistoryConfigColumn _C && _C.SourceType != eDataHistoryQueryType.Static))
                                    isValidTable = false;
                            }

                            if (!isValidTable)
                            {
                                AddResultTable(Portal, SubTable, false, "", "the table key columns are not valid.");
                            }
                            else
                            {
                                var Server = lstServer[indexServer];

                                var strNxqlQuery = cDataHistoryCollectorNxql.getNxqlStatement(NxqlTemplate.Query, Table.ParentCluster.InformationClass, null, Table.KeyColumns, 0, null, null, SubTable.ParentTable.KeyColumns, SubTable.Name);
                                if (string.IsNullOrEmpty(strNxqlQuery))
                                    AddResultTable(Portal, SubTable, false, "", "could not get a valid NXQL statement for queriing the key columns.");
                                else
                                {
                                    var Job = new cNxqlColumnValidatorTableJob(this, SubTable, Portal, strNxqlQuery);
                                    Orchestrator.AddJob($"{Portal.Name}|{SubTable.Name}", Server.Address, strNxqlQuery, Portal.Credential, Port: Server.Port.ToString(), PreProcessing: Job.PreProcessing, PostProcessing: Job.PostProcessing, ErrorProcessing: Job.ErrorProcessing);
                                    indexServer++;
                                    if (indexServer >= lstServer.Count)
                                        indexServer = 0;
                                    QueryCount++;
                                }

                            }
                        }
                    }
                }

                return QueryCount;

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

        }

        public int SetNxqlJobsColumns()
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var QueryCount = 0;
                foreach (var PortalResult in ValidationResults.Values)
                {
                    var Portal = PortalResult.Portal;
                    var lstServer = Portal.Engines.Values.ToList();
                    if (lstServer.Count == 0)
                        continue;

                    var indexServer = 0;
                    foreach (var SubTableResult in PortalResult.TableResults.Values)
                    {
                        if (!SubTableResult.IsValid)
                            continue;

                        var SubTable = SubTableResult.Table;
                        if (!(SubTable.Template is cDataHistoryConfigQueryTemplateNxql NxqlTemplate))
                            continue;

                        var tableInfo = new cNxqlColumnValidatorColumnTableInfo();
                        foreach (var Column in SubTable.Columns.Values)
                        {
                            if (!cDataHistoryCollectorNxql.IsNxqlColumn(Column))
                                continue;

                            if (Column is cDataHistoryConfigColumn _C && _C.SourceType == eDataHistoryQueryType.Static)
                                continue;

                            var Server = lstServer[indexServer];

                            var strNxqlQuery = cDataHistoryCollectorNxql.getNxqlStatement(NxqlTemplate.Query, SubTable.ParentTable.ParentCluster.InformationClass, null, SubTable.ParentTable.KeyColumns, 0, null, null, new List<cDataHistoryConfigColumnBase>() { Column }, SubTable.Name);
                            if (string.IsNullOrEmpty(strNxqlQuery))
                                AddResultColumn(Portal, Column, int.MaxValue, false, "", "Could not get valid NXQL query for the table definition.");
                            else
                            {
                                var Job = new cNxqlColumnValidatorColumnJob(this, tableInfo, Column, Portal, strNxqlQuery);
                                Orchestrator.AddJob($"{Portal.Name}|{SubTable.Name}|{Column.Name}", Server.Address, strNxqlQuery, Portal.Credential, Port: Server.Port.ToString(), PostProcessing: Job.PostProcessing, ErrorProcessing: Job.ErrorProcessing);
                                indexServer++;
                                if (indexServer >= lstServer.Count)
                                    indexServer = 0;
                                QueryCount++;
                            }

                        }
                    }
                }

                return QueryCount;

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

        }

        public async Task DoValidationAsync(cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            if (!(Collector?.InfrastructureConfig?.Nexthink?.Portals?.Count > 0))
                return;

            var QueryCount = SetNxqlJobsTables();
            if (QueryCount > 0)
                await Orchestrator.ProcessAsync(Token);

            QueryCount = SetNxqlJobsColumns();
            if (QueryCount > 0)
                await Orchestrator.ProcessAsync(Token);

            await ValidationResults.UpdateToSqlAsync(Collector, requestInfo, LogDeep + 1, Token);
        }

        public void AddResultTable(cDataHistoryNxtPortal Portal, cDataHistoryConfigSubTable Table, bool IsValid, string NXQL, string Message)
        {
            lock (ValidationResults)
            {
                if (!ValidationResults.TryGetValue(Portal.Name, out var ResultPortal))
                {
                    ResultPortal = new cNxqlPortalValidationResult() { Portal = Portal };
                    ValidationResults.Add(Portal.Name, ResultPortal);
                }

                if (!ResultPortal.TableResults.TryGetValue(Table.Name, out var TableResult))
                {
                    TableResult = new cNxqlTableValidationResult() { Table = Table, IsValid = IsValid, ErrorMessage = Message, NXQL = NXQL };
                    ResultPortal.TableResults.Add(Table.Name, TableResult);
                }
            }
        }

        public void AddResultColumn(cDataHistoryNxtPortal Portal, cDataHistoryConfigColumnBase Column, int columnCount, bool IsValid, string NXQL, string Message)
        {
            lock (ValidationResults)
            {
                if (!ValidationResults.TryGetValue(Portal.Name, out var PortalResults))
                    return;

                if (!PortalResults.TableResults.TryGetValue(Column.ParentTable?.Name, out var TableResult))
                    return;

                TableResult.ColumnResults[Column.Name] = new cNxqlColumnValidationResult() { Column = Column, IsValid = IsValid, ErrorMessage = Message, NXQL = NXQL };

                if (!IsValid)
                    this.Collector?.DoProcessUiMessage(0, $"Error : column validation of '{Column.ParentTable.Name}/{Column.Name}' on portal '{Portal.Name}' failed.");

                if (TableResult.ColumnResults.Count == columnCount)
                    this.Collector?.DoProcessUiMessage(0, $"Column validation of NXQL table '{Column.ParentTable.Name}' on portal '{Portal.Name}' finished.");

            }
        }

    }

    public class cNxqlColumnValidatorTableJob
    {
        public readonly cNxqlColumnValidator Validator;
        public readonly cDataHistoryConfigSubTable Table;
        public readonly cDataHistoryNxtPortal Portal;
        public readonly string NXQL;

        public cNxqlColumnValidatorTableJob(cNxqlColumnValidator Validator, cDataHistoryConfigSubTable Table, cDataHistoryNxtPortal Portal, string NXQL)
        {
            this.Validator = Validator;
            this.Table = Table;
            this.Portal = Portal;
            this.NXQL = NXQL;
        }

        public async Task PreProcessing(string Content, CancellationToken Token)
        {
            //Validator.Collector?.DoProcessUiMessage(0, $"Start validation NXQL table '{Table.Name}' on portal '{Portal.Name}'.");
            await Task.CompletedTask;
        }

        public async Task PostProcessing(string Content, CancellationToken Token)
        {
            var Valid = !string.IsNullOrEmpty(Content);
            Validator.AddResultTable(Portal, Table, Valid, NXQL, Valid ? null : "empty result returned.");
            if (Valid)
                Validator.Collector?.DoProcessUiMessage(0, $"Base validation of NXQL table '{Table.Name}' on portal '{Portal.Name}' succesfull.");
            else
                Validator.Collector?.DoProcessUiMessage(0, $"Error: Base validation of NXQL table '{Table.Name}' on portal '{Portal.Name}' failed.");

            await Task.CompletedTask;
        }

        public async Task ErrorProcessing(string Content, CancellationToken Token)
        {
            Validator.AddResultTable(Portal, Table, false, NXQL, Content);
            Validator.Collector?.DoProcessUiMessage(0, $"Error: Base validation of NXQL table '{Table.Name}' on portal '{Portal.Name}' failed.");
            await Task.CompletedTask;
        }
    }

    public class cNxqlColumnValidatorColumnTableInfo
    {
        public int ColCount = 0;
    }

    public class cNxqlColumnValidatorColumnJob
    {
        public readonly cNxqlColumnValidator Validator;
        public readonly cDataHistoryConfigColumnBase TableColumn;
        public readonly cDataHistoryNxtPortal Portal;
        public readonly cNxqlColumnValidatorColumnTableInfo TableInfo;
        public readonly string NXQL;

        public cNxqlColumnValidatorColumnJob(cNxqlColumnValidator Validator, cNxqlColumnValidatorColumnTableInfo TableInfo, cDataHistoryConfigColumnBase TableColumn, cDataHistoryNxtPortal Portal, string NXQL)
        {
            this.Validator = Validator;
            this.TableInfo = TableInfo;
            TableInfo.ColCount++;
            this.TableColumn = TableColumn;
            this.Portal = Portal;
            this.NXQL = NXQL;
        }

        public async Task PostProcessing(string Content, CancellationToken Token)
        {
            var Valid = !string.IsNullOrEmpty(Content);
            Validator.AddResultColumn(Portal, TableColumn, TableInfo.ColCount, Valid, NXQL, Valid ? null : "empty result returned.");
            await Task.CompletedTask;
        }

        public async Task ErrorProcessing(string Content, CancellationToken Token)
        {
            Validator.AddResultColumn(Portal, TableColumn, int.MaxValue, false, NXQL, Content);
            await Task.CompletedTask;
        }

    }

    public class cNxqlColumnsValidationResult : Dictionary<string, cNxqlPortalValidationResult>
    {
        public async Task<bool> LoadFromSqlAsync(cDataHistoryCollector Collector, CancellationToken Token, 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
            {
                if (!DataHistorySqlHelper.GetWellKnownSqlStatement("LoadColumnsValidationResult", out var Query))
                    return false;

                this.Clear();

                foreach (var Portal in Collector.InfrastructureConfig.Nexthink.Portals.Values)
                {
                    var Instance = Portal.Name;
                    var InstanceResult = new cNxqlPortalValidationResult();
                    this[Instance] = InstanceResult;

                    foreach (var Table in Collector.ClusterConfig.Tables.Values)
                    {
                        if (Table.ParentCluster.Origin != enumDataHistoryOrigin.NexthinkNxql)
                            continue;

                        foreach (var SubTable in Table.SubTables.Values)
                        {
                            var SubTableName = SubTable.Name;
                            var SubTableResult = new cNxqlTableValidationResult() { Table = SubTable };

                            InstanceResult.TableResults[SubTableName] = SubTableResult;

                            foreach (var Column in SubTable.Columns.Values)
                            {
                                if (Column is cDataHistoryConfigColumnComputation)
                                    continue;

                                var ColumnResult = new cNxqlColumnValidationResult() { Column = Column };
                                SubTableResult.ColumnResults[Column.Name] = ColumnResult;
                            }
                        }
                    }
                }

                var Params = new Dictionary<string, object>
                    {
                        { "Connector", "NXQL" }
                    };

                var sqlerror = 0;
                var sqlStartTime = DateTime.UtcNow;

                try
                {
                    using (var Conn = new cDbConnection(Collector.mainDbConnection))
                    {
                        if (!Conn.IsOpen)
                        {
                            LogEntry($"Could not open main sql database '{Collector.mainDbConnection.Database}', aborting saving NXQL column validation result", LogLevels.Error);
                            return false;
                        }

                        using (var Reader = await DataHistorySqlHelper.GetTableResultAsync(Conn, Query, Params, CancellationToken.None))
                        {
                            if (Reader == null)
                                return false;

                            try
                            {
                                if (Reader.HasRows)
                                {
                                    while (await Reader.ReadAsync())
                                    {
                                        try
                                        {
                                            var Instance = Reader.GetString(0);
                                            var SubTableName = Reader.GetString(1);
                                            var ColumnName = Reader.GetString(2);
                                            var IsValid = Reader.GetByte(3);

                                            if (this.TryGetValue(Instance, out var InstanceResult))
                                                if (InstanceResult.TableResults.TryGetValue(SubTableName, out var TableResult))
                                                    if (TableResult.ColumnResults.TryGetValue(ColumnName, out var ColumnResult))
                                                        ColumnResult.IsValid = IsValid == 1;
                                        }
                                        catch (Exception E)
                                        {
                                            cLogManager.DefaultLogger.LogException(E);
                                        }
                                    }
                                }
                            }
                            catch (Exception E)
                            {
                                cLogManager.DefaultLogger.LogException(E);
                            }
                        }
                    }

                }
                catch (Exception E)
                {
                    sqlerror = E.HResult;
                    LogException(E);
                }
                finally
                {
                    if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, sqlStartTime, Params, sqlerror, requestInfo?.requestName);
                }

                foreach (var InstanceResult in this.Values)
                {
                    foreach (var TableResult in InstanceResult.TableResults.Values)
                    {
                        TableResult.IsValid = false;
                        foreach (var ColumnResult in TableResult.ColumnResults.Values)
                            TableResult.IsValid |= ColumnResult.IsValid;
                        if (!TableResult.IsValid)
                            TableResult.ColumnResults.Clear();
                    }
                }
            }
            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 false;
        }

        public async Task UpdateToSqlAsync(cDataHistoryCollector Collector, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                using (var Conn = new cDbConnection(Collector.mainDbConnection))
                {
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{Collector.mainDbConnection.Database}', aborting saving NXQL column validation result", LogLevels.Error);
                        return;
                    }

                    foreach (var PortalEntry in this.Values)
                    {
                        foreach (var SubtableEntry in PortalEntry.TableResults.Values)
                        {
                            if (!SubtableEntry.IsValid)
                            {
                                foreach (var ColumnEntry in SubtableEntry.Table.Columns.Values)
                                    await SaveEntryAsync(Conn, PortalEntry.Portal.Name, ColumnEntry, false, requestInfo, LogDeep + 1, Token);

                            }
                            else
                            {
                                foreach (var ColumnEntry in SubtableEntry.ColumnResults.Values)
                                    await SaveEntryAsync(Conn, PortalEntry.Portal.Name, ColumnEntry.Column, ColumnEntry.IsValid, requestInfo, LogDeep + 1, Token);
                            }
                        }
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        private async Task SaveEntryAsync(cDbConnection Conn, string Portal, cDataHistoryConfigColumnBase Column, bool IsValid, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {
            try
            {
                var Params = new Dictionary<string, object>() { { "Connector", "NXQL" }, { "Instance", Portal }, { "SubTable", Column.ParentTable.Name }, { "Column", Column.Name }, { "isValid", IsValid } };
                await DataHistorySqlHelper.DoUpdateOrInsertAsync(Conn, "main-column-validation", Params, new List<string>() { "Connector", "Instance", "SubTable", "Column" }, requestInfo, LogDeep + 1, Token);
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }
    }

    public class cNxqlPortalValidationResult
    {
        public cDataHistoryNxtPortal Portal;
        public Dictionary<string, cNxqlTableValidationResult> TableResults = new Dictionary<string, cNxqlTableValidationResult>();
    }

    public class cNxqlTableValidationResult
    {
        public cDataHistoryConfigSubTable Table;
        public bool IsValid;
        public string ErrorMessage;
        public string NXQL;
        public Dictionary<string, cNxqlColumnValidationResult> ColumnResults = new Dictionary<string, cNxqlColumnValidationResult>();
    }

    public class cNxqlColumnValidationResult
    {
        public cDataHistoryConfigColumnBase Column;
        public bool IsValid;
        public string NXQL;
        public string ErrorMessage;
    }

    public class cNxqlDailyCollector
    {
        public class cTableResult : Dictionary<string, object>
        {
            public enumFasdInformationClass InformationClass = enumFasdInformationClass.Unknown;
        }

        public readonly cDataHistoryCollector Collector;
        public readonly cNxqlOrchestration Orchestrator;
        public readonly cNxqlColumnsValidationResult ColumnsValidationResults;

        public Dictionary<string, cTableResult> Results = new Dictionary<string, cTableResult>();

        public cNxqlDailyCollector(cDataHistoryCollector Collector, cNxqlColumnsValidationResult ColumnsValidationResults)
        {
            this.Collector = Collector;
            this.ColumnsValidationResults = ColumnsValidationResults;
            Orchestrator = new cNxqlOrchestration("DailyCollector");
        }

        public void CreateJobs(List<cDataHistoryConfigTable> Tables, DateTime From, DateTime To, string strEngine, Int64 Nxt_Id, enumFasdInformationClass InfoClass)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                // get the engine, portal & column validation result for the engine
                cDataHistoryNxtEngine EngineComputer = null;
                cDataHistoryNxtPortal PortalComputer = null;
                cNxqlPortalValidationResult PortalValidationResult = null; ;

                foreach (var PortalEntry in Collector.InfrastructureConfig.Nexthink.Portals.Values)
                {
                    foreach (var EngineEntry in PortalEntry.Engines.Values)
                    {
                        if (EngineEntry.Name == strEngine)
                        {
                            EngineComputer = EngineEntry;
                            PortalComputer = PortalEntry;
                            if (!ColumnsValidationResults.TryGetValue(PortalComputer.Name, out PortalValidationResult))
                            {
                                LogEntry($"no valid colum validation result for portal '{PortalComputer.Name}' available.", LogLevels.Error);
                                return;
                            }
                        }
                    }
                }

                if (EngineComputer == null)
                {
                    LogEntry($"no valid engine configuration entry found for engine name '{strEngine}' available.", LogLevels.Error);
                    return;
                }

                if (PortalValidationResult == null)
                {
                    LogEntry($"no valid colum validation result for portal '{PortalComputer.Name}' available.", LogLevels.Error);
                    return;
                }

                foreach (var Table in Tables)
                {
                    if (!(Table.ParentCluster.Origin == enumDataHistoryOrigin.NexthinkNxql))
                        continue;

                    if (Table.ParentCluster.InformationClass != InfoClass)
                        continue;

                    var TableCollector = new cNxqlDailyCollectorTable(this, EngineComputer, Table);

                    foreach (var SubTable in Table.SubTables.Values)
                    {
                        if (!PortalValidationResult.TableResults.TryGetValue(SubTable.Name, out var SubTableValidation))
                            continue;

                        if (!(SubTable.Template is cDataHistoryConfigQueryTemplateNxql NxqlTemplate))
                            continue;

                        var Cols = new List<cDataHistoryConfigColumnBase>();
                        foreach (var _res in SubTableValidation.ColumnResults.Values)
                            if (_res.IsValid)
                                Cols.Add(_res.Column);
                        if (!SubTable.Columns.TryGetValue("id_int", out var _keyCol))
                        {
                            LogEntry($"no valid column 'id_int' found in subtable configuration '{SubTable.Name}'", LogLevels.Warning);
                            return;
                        }

                        var strNxqlQuery = cDataHistoryCollectorNxql.getNxqlStatement(NxqlTemplate.Query, Table.ParentCluster.InformationClass, new List<object>(1) { Nxt_Id }, new List<cDataHistoryConfigColumnBase>(1) { _keyCol }, NxqlTemplate.Limit, From, To, Cols, SubTable.Name);

                        var SubTableCollector = new cNxqlDailyCollectorSubTable(strNxqlQuery, SubTable, TableCollector, EngineComputer);

                        Orchestrator.AddJob($"{EngineComputer.Name}|{SubTable.Name}", EngineComputer.Address, strNxqlQuery, PortalComputer.Credential, Port: EngineComputer.Port.ToString(), PostProcessing: SubTableCollector.PostProcessing, ErrorProcessing: SubTableCollector.ErrorProcessing);

                    }
                }

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

    }

    public class cNxqlDailyCollectorTable
    {
        public readonly cNxqlDailyCollector DataCollector;
        public readonly cDataHistoryConfigTable Table;
        public readonly cDataHistoryNxtEngine Engine;

        private readonly Dictionary<string, cNxqlDailyCollectorSubTable> SubTableCollectors = new Dictionary<string, cNxqlDailyCollectorSubTable>();

        public cNxqlDailyCollectorTable(cNxqlDailyCollector DataCollector, cDataHistoryNxtEngine Engine, cDataHistoryConfigTable Table)
        {
            this.DataCollector = DataCollector;
            this.Engine = Engine;
            this.Table = Table;
        }

        public void RegisterSubTableCollector(cNxqlDailyCollectorSubTable Collector)
        {
            SubTableCollectors[Collector.SubTable.Name] = Collector;
        }

        public async Task ProcessResult(string SubTable, CancellationToken Token)
        {

            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!SubTableCollectors.TryGetValue(SubTable, out var Collector))
                    return;

                var isFinished = true;
                lock (SubTableCollectors)
                {
                    Collector.finished = true;
                    foreach (var SubCollector in SubTableCollectors.Values)
                        isFinished &= SubCollector.finished;
                }
                if (!isFinished)
                    return;

                var Cols = new cTableResult();

                foreach (var SubTableResult in SubTableCollectors.Values)
                {
                    if (SubTableResult.Result == null)
                        continue;

                    // get the infos, which csv column refers to which subtable column
                    var ColInfos = new Dictionary<string, cNxqlColumnInfo>();
                    for (int i = 0; i < SubTableResult.Result.Columns.Length; i++)
                    {
                        var colNameCsv = SubTableResult.Result.Columns[i].ToLowerInvariant();
                        bool found = false;
                        foreach (var SubTableColumnEntry in SubTableResult.SubTable.Columns.Values)
                        {
                            if (SubTableColumnEntry is cDataHistoryConfigColumnComputation)
                                continue;
                            var colNameST = cDataHistoryCollectorNxql.GetNxqlColumnResultName(SubTableColumnEntry);
                            if (colNameST == colNameCsv)
                            {
                                var colInfo = new cNxqlColumnInfo()
                                {
                                    Column = SubTableColumnEntry,
                                    ColIndex = i
                                };
                                ColInfos[SubTableColumnEntry.Name] = colInfo;
                                found = true;
                            }
                        }

                        if (!found)
                            LogEntry($"No matching column could be found for NXQL column '{colNameCsv}' in sub-table '{SubTableResult.SubTable.Name}'", LogLevels.Warning);

                        // ad the static value to the column list
                        foreach (var SubTableColumnEntry in SubTableResult.SubTable.Columns.Values)
                        {
                            if (SubTableColumnEntry is cDataHistoryConfigColumn col)
                            {
                                if (col.SourceType == eDataHistoryQueryType.Static && col.SourceName.ToLowerInvariant() == "engine")
                                {
                                    var colInfo = new cNxqlColumnInfo()
                                    {
                                        Column = SubTableColumnEntry,
                                        ColIndex = -1,
                                        StaticValue = Engine.Name
                                    };
                                    ColInfos[SubTableColumnEntry.Name] = colInfo;
                                }
                            }
                        }
                    }

                    if (SubTableResult.SubTable.Aggregation)
                    {
                        var Aggs = new Dictionary<string, cNxqlAggregationEntry>(ColInfos.Count);

                        foreach (var _colInfo in ColInfos.Values)
                        {
                            if (!(_colInfo.Column is cDataHistoryConfigColumn _col))
                                continue;

                            Aggs.Add(_colInfo.Column.Name, new cNxqlAggregationEntry(_col));
                        }

                        foreach (var DataRecord in SubTableResult.Result.Data)
                        {
                            foreach (var _colInfo in ColInfos.Values)
                            {
                                if (!Aggs.TryGetValue(_colInfo.Column.Name, out var agg))
                                    continue;

                                if (_colInfo.ColIndex < 0)
                                    agg.addValue(_colInfo.StaticValue, null);
                                else
                                    agg.addValue(DataRecord[_colInfo.ColIndex], null);
                            }
                        }

                        foreach (var _colInfo in ColInfos.Values)
                        {
                            if (!Aggs.TryGetValue(_colInfo.Column.Name, out var agg))
                                continue;
                            if (!(_colInfo.Column is cDataHistoryConfigColumn _col))
                                continue;

                            var _aggVal = agg.getValue();
                            var dtype = cDataHistoryConfigClusters.ResultingAggregationType(_col.ValueType, _col.AggregationType);
                            var sqlVals = DataHistorySqlHelper.GetSqlDataValueFromHistoryType(_aggVal, dtype, _col.Cardinal);
                            if (sqlVals != null)
                            {
                                if (sqlVals.Length > 0)
                                {
                                    Cols[_colInfo.Column.Name] = sqlVals[0];
                                    /*
                                    if (sqlVals.Length > 1)
                                        Cols[_colInfo.Column.Name + "_bin"] = sqlVals[1];
                                    */
                                }
                            }
                        }
                    }
                    else
                    {
                        if (SubTableResult.Result.Data.Count > 0)
                        {
                            var DataRecord = SubTableResult.Result.Data[0];
                            foreach (var _colInfo in ColInfos.Values)
                            {
                                string val = null;
                                if (_colInfo.ColIndex < 0)
                                    val = _colInfo.StaticValue;
                                else
                                    val = DataRecord[_colInfo.ColIndex];

                                var sqlVals = DataHistorySqlHelper.GetSqlDataValueFromHistoryType(val, _colInfo.Column.ValueType, _colInfo.Column.Cardinal);
                                if (sqlVals != null)
                                {
                                    if (sqlVals.Length > 0)
                                    {
                                        Cols[_colInfo.Column.Name] = sqlVals[0];
                                        if (sqlVals.Length > 1)
                                            Cols[_colInfo.Column.Name + "_bin"] = sqlVals[1];
                                    }
                                }
                            }
                        }
                    }
                }

                lock (this.DataCollector.Results)
                    this.DataCollector.Results[this.Table.Name] = Cols;

            }
            catch (Exception E)
            {
                await Task.CompletedTask;
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }
    }

    public class cNxqlDailyCollectorSubTable
    {
        public readonly cDataHistoryConfigSubTable SubTable;
        public readonly cNxqlDailyCollectorTable TableCollector;
        public readonly cDataHistoryNxtEngine Server;
        public readonly string NXQL;

        public cCsvFile Result = null;
        public bool finished = false;

        public cNxqlDailyCollectorSubTable(string NXQL, cDataHistoryConfigSubTable SubTable, cNxqlDailyCollectorTable TableCollector, cDataHistoryNxtEngine Server)
        {
            this.NXQL = NXQL;
            this.SubTable = SubTable;
            this.TableCollector = TableCollector;
            this.Server = Server;
            TableCollector.RegisterSubTableCollector(this);
        }

        private void LogContent(string Content)
        {
            var Lines = Content.Split('\n');
            var arrDebug = new List<string>(5)
                {
                    $"NXQL query for sub-table '{SubTable.Name}': {NXQL}",
                    "Resulting lines: " + Lines.Length.ToString()
                };
            string str = "Line1: ";
            if (Lines.Length >= 1)
                str += Lines[0].Substring(0, Math.Min(255, Lines[0].Length));
            arrDebug.Add(str);
            str = "Line2: ";
            if (Lines.Length >= 2)
                str += Lines[1].Substring(0, Math.Min(255, Lines[1].Length));
            arrDebug.Add(str);
            str = "Line3: ";
            if (Lines.Length >= 3)
                str += Lines[2].Substring(0, Math.Min(255, Lines[2].Length));
            arrDebug.Add(str);
            cLogManager.DefaultLogger.LogList(LogLevels.Debug, arrDebug);
        }

        public async Task PostProcessing(string Content, CancellationToken Token)
        {
            var csvResult = new cCsvFile();
            if (cLogManager.DefaultLogger.IsDebug)
                LogContent(Content);

            if (csvResult.ImportFromString(Content))
            {
                Result = csvResult;

                var lines = Result.Data.Count;
                TableCollector?.DataCollector?.Collector?.DoProcessUiMessage(0, $"NXQL query for sub-table {SubTable.Name} on server {Server.Name}({Server.Address}) returned {lines} lines", LogLevels.Debug);

                await TableCollector.ProcessResult(SubTable.Name, Token);
            }
            else
            {
                LogEntry($"Could not convert result of NXQL query for subtable {SubTable.Name} on engine {Server.Name}({Server.Address}) to csv.", LogLevels.Error);
            }

        }

        public async Task ErrorProcessing(string Content, CancellationToken Token)
        {
            await Task.CompletedTask;
            LogEntry($"Could not get result of NXQL query for subtable {SubTable.Name} on engine {Server.Name}({Server.Address})", LogLevels.Error);
        }


    }

    public class cNxqlDataCollector
    {
        public readonly cDataHistoryCollector Collector;
        public readonly cNxqlOrchestration Orchestrator;
        public readonly Guid ScanId;

        public readonly string FileSavePath = null;

        public readonly cNxqlColumnsValidationResult ColumnsValidationResults;

        public int validResultCount { get; internal set; } = 0;
        public int errorResultCount { get; internal set; } = 0;

        public cNxqlDataCollector(cDataHistoryCollector Collector, cNxqlColumnsValidationResult ColumnsValidationResults, Guid ScanId, bool SaveResultsToFile)
        {
            this.Collector = Collector;
            this.ColumnsValidationResults = ColumnsValidationResults;
            this.ScanId = ScanId;
            if (SaveResultsToFile)
            {
                if (cLogManager.Instance is cLogManagerFile log)
                {
                    try
                    {
                        var fn = log.GetLogFileName();
                        fn = Path.Combine(fn, "..\\NxqlQueryResults");
                        if (Directory.Exists(fn))
                        {
                            var di = new DirectoryInfo(fn);
                            foreach (FileInfo file in di.EnumerateFiles())
                                file.Delete();
                            foreach (DirectoryInfo dir in di.EnumerateDirectories())
                                dir.Delete(true);
                        }
                        Directory.CreateDirectory(fn);
                        if (Directory.Exists(fn))
                            FileSavePath = fn;
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                }
            }
            Orchestrator = new cNxqlOrchestration("DataCollector");
        }

        public void CreateJobs(DateTime From, DateTime To, 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
            {
                foreach (var Portal in Collector.InfrastructureConfig?.Nexthink?.Portals?.Values)
                {
                    if (!ColumnsValidationResults.TryGetValue(Portal.Name, out var PortalValidationResult))
                        continue;

                    foreach (var Engine in Portal.Engines.Values)
                    {
                        foreach (var Table in Collector.ClusterConfig.Tables.Values)
                        {
                            if (!(Table.ParentCluster.Origin == enumDataHistoryOrigin.NexthinkNxql))
                                continue;

                            if (Table.Cached == eDataHistoryTableCached.No && FileSavePath == null)
                                continue;

                            var TableCollector = new cNxqlDataCollectorTable(this, Engine, Table);

                            foreach (var SubTable in Table.SubTables.Values)
                            {
                                if (!PortalValidationResult.TableResults.TryGetValue(SubTable.Name, out var SubTableValidation))
                                    continue;

                                if (!(SubTable.Template is cDataHistoryConfigQueryTemplateNxql NxqlTemplate))
                                    continue;

                                var Cols = new List<cDataHistoryConfigColumnBase>();
                                foreach (var _res in SubTableValidation.ColumnResults.Values)
                                    if (_res.IsValid)
                                        Cols.Add(_res.Column);

                                var strNxqlQuery = cDataHistoryCollectorNxql.getNxqlStatement(NxqlTemplate.Query, Table.ParentCluster.InformationClass, null, null, NxqlTemplate.Limit, From, To, Cols, SubTable.Name);

                                var SubTableCollector = new cNxqlDataCollectorSubTable(strNxqlQuery, SubTable, TableCollector, Engine, requestInfo, LogDeep + 1);

                                Orchestrator.AddJob($"{Engine.Name}|{SubTable.Name}", Engine.Address, strNxqlQuery, Portal.Credential, Port: Engine.Port.ToString(), PostProcessing: SubTableCollector.PostProcessing, ErrorProcessing: SubTableCollector.ErrorProcessing);

                            }
                        }
                    }
                }


            }
            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);
            }
        }
    }

    public class cNxqlDataCollectorTable
    {
        public readonly cNxqlDataCollector DataCollector;
        public readonly cDataHistoryConfigTable Table;
        public readonly cDataHistoryNxtEngine Engine;

        private readonly Dictionary<string, cNxqlDataCollectorSubTable> SubTableCollectors = new Dictionary<string, cNxqlDataCollectorSubTable>();

        public cNxqlDataCollectorTable(cNxqlDataCollector DataCollector, cDataHistoryNxtEngine Engine, cDataHistoryConfigTable Table)
        {
            this.DataCollector = DataCollector;
            this.Engine = Engine;
            this.Table = Table;
        }

        public void RegisterSubTableCollector(cNxqlDataCollectorSubTable Collector)
        {
            SubTableCollectors[Collector.SubTable.Name] = Collector;
        }

        public async Task ProcessResult(string SubTable, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
        {

            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
            {
                if (!SubTableCollectors.TryGetValue(SubTable, out var Collector))
                    return;

                var isFinished = true;
                lock (SubTableCollectors)
                {
                    Collector.finished = true;
                    foreach (var SubCollector in SubTableCollectors.Values)
                        isFinished &= SubCollector.finished;
                }
                if (!isFinished)
                    return;


                using (var Conn = new cDbConnection(DataCollector.Collector.mainDbConnection))
                {
                    var TableName = SubTableCollectors?.Values?.First()?.SubTable?.ParentTable.SourceName;
                    if (!Conn.IsOpen)
                    {
                        LogEntry($"Could not open main sql database '{DataCollector.Collector.mainDbConnection.Database}', aborting saving result for nxql table '{TableName}'", LogLevels.Error);
                        DataCollector.errorResultCount++;
                        return;
                    }

                    var firstSubTable = true;

                    foreach (var SubTableResult in SubTableCollectors.Values)
                    {
                        if (SubTableResult.Result == null)
                            continue;

                        if (SubTableResult.SubTable.ParentTable.Cached != eDataHistoryTableCached.Yes)
                            continue;

                        // get the infos, which csv column refers to which subtable column
                        var ColInfos = new Dictionary<string, cNxqlColumnInfo>();
                        for (int i = 0; i < SubTableResult.Result.Columns.Length; i++)
                        {
                            var colNameCsv = SubTableResult.Result.Columns[i].ToLowerInvariant();
                            bool found = false;
                            foreach (var SubTableColumnEntry in SubTableResult.SubTable.Columns.Values)
                            {
                                if (SubTableColumnEntry is cDataHistoryConfigColumnComputation)
                                    continue;
                                var colNameST = cDataHistoryCollectorNxql.GetNxqlColumnResultName(SubTableColumnEntry);
                                if (colNameST == colNameCsv)
                                {
                                    var colInfo = new cNxqlColumnInfo()
                                    {
                                        Column = SubTableColumnEntry,
                                        ColIndex = i
                                    };
                                    ColInfos[SubTableColumnEntry.Name] = colInfo;
                                    found = true;
                                }
                            }

                            if (!found)
                                LogEntry($"No matching column could be found for NXQL column '{colNameCsv}' in sub-table '{SubTableResult.SubTable.Name}'", LogLevels.Warning);
                        }

                        // ad the static value to the column list
                        foreach (var SubTableColumnEntry in SubTableResult.SubTable.Columns.Values)
                        {
                            if (SubTableColumnEntry is cDataHistoryConfigColumn col)
                            {
                                if (col.SourceType == eDataHistoryQueryType.Static && col.SourceName.ToLowerInvariant() == "engine")
                                {
                                    var colInfo = new cNxqlColumnInfo()
                                    {
                                        Column = SubTableColumnEntry,
                                        ColIndex = -1,
                                        StaticValue = Engine.Name
                                    };
                                    ColInfos[SubTableColumnEntry.Name] = colInfo;
                                }
                            }
                        }


                        // check if we have all necessary key columns
                        var keyValid = true;
                        var IDs = new List<string>(SubTableResult.SubTable.ParentTable.KeyColumns.Count);
                        var idInfos = new List<cNxqlColumnInfo>(SubTableResult.SubTable.ParentTable.KeyColumns.Count);
                        if (SubTableResult.SubTable.ParentTable.Type == eDataHistoryTableType.History)
                            IDs.Add("ScanId");

                        foreach (var Entry in SubTableResult.SubTable.ParentTable.KeyColumns)
                        {
                            if (!ColInfos.TryGetValue(Entry.Name, out var _ci))
                            {
                                keyValid = false;
                                LogEntry($"Could not store the results for sub-table '{SubTableResult.SubTable.Name}' because the key column '{Entry.Name} could not be found in the NXQL result on engine '{Engine.Name}({Engine.Address})'", LogLevels.Error);
                                break;
                            }
                            IDs.Add(Entry.NameBin);
                            idInfos.Add(_ci);
                        }
                        if (!keyValid)
                            continue;

                        var RecordCount = 0;
                        var ErrorCount = 0;

                        if (SubTableResult.SubTable.Aggregation)
                        {
                            var groupingDic = new Dictionary<string, List<string[]>>();
                            foreach (var DataRecord in SubTableResult.Result.Data)
                            {
                                var strKey = "";
                                foreach (var colInfo in idInfos)
                                {
                                    if (strKey != null)
                                        strKey += '|';
                                    if (colInfo.ColIndex >= 0)
                                        strKey += DataRecord[colInfo.ColIndex];
                                }
                                if (!groupingDic.TryGetValue(strKey, out var listVals))
                                {
                                    listVals = new List<string[]>();
                                    groupingDic.Add(strKey, listVals);
                                }
                                listVals.Add(DataRecord);

                            }

                            foreach (var groupEntry in groupingDic.Values)
                            {
                                var Cols = new Dictionary<string, object>(ColInfos.Count)
                                {
                                    { "ScanId", DataCollector.ScanId }
                                };
                                foreach (var _colInfo in ColInfos.Values)
                                {
                                    if (!(_colInfo.Column is cDataHistoryConfigColumn _col))
                                        continue;

                                    var agg = new cNxqlAggregationEntry(_col);

                                    foreach (var ds in groupEntry)
                                    {
                                        if (_colInfo.ColIndex < 0)
                                            agg.addValue(_colInfo.StaticValue, null);
                                        else
                                            agg.addValue(ds[_colInfo.ColIndex], null);
                                    }


                                    var _aggVal = agg.getValue();
                                    var dtype = cDataHistoryConfigClusters.ResultingAggregationType(_col.ValueType, _col.AggregationType);

                                    var sqlVals = DataHistorySqlHelper.GetSqlDataValueFromHistoryType(_aggVal, dtype, _colInfo.Column.Cardinal);

                                    if (sqlVals != null)
                                    {
                                        if (sqlVals.Length > 0)
                                        {
                                            Cols[_colInfo.Column.Name] = sqlVals[0];
                                            if (sqlVals.Length > 1)
                                                Cols[_colInfo.Column.Name + "_bin"] = sqlVals[1];
                                        }
                                    }

                                }

                                // insert or update the data record
                                bool isValid = false;
                                if (firstSubTable && SubTableResult.SubTable.ParentTable.Type != eDataHistoryTableType.Static)
                                    isValid = await DataHistorySqlHelper.DoInsertAsync(Conn, TableName, Cols, Token, requestInfo, LogDeep + 1, LogSilent: true);
                                else if (SubTableResult.SubTable.ParentTable.Type != eDataHistoryTableType.Events)
                                    isValid = await DataHistorySqlHelper.DoUpdateOrInsertAsync(Conn, TableName, Cols, IDs, requestInfo, LogDeep + 1, Token, LogSilent: true);

                                if (isValid)
                                    RecordCount++;
                                else
                                    ErrorCount++;
                            }
                        }
                        else
                        {
                            foreach (var DataRecord in SubTableResult.Result.Data)
                            {
                                var Cols = new Dictionary<string, object>(ColInfos.Count)
                                {
                                    { "ScanId", DataCollector.ScanId }
                                };
                                foreach (var _colInfo in ColInfos.Values)
                                {
                                    string val = null;
                                    if (_colInfo.ColIndex < 0)
                                        val = _colInfo.StaticValue;
                                    else
                                        val = DataRecord[_colInfo.ColIndex];

                                    var sqlVals = DataHistorySqlHelper.GetSqlDataValueFromHistoryType(val, _colInfo.Column.ValueType, _colInfo.Column.Cardinal);
                                    if (sqlVals != null)
                                    {
                                        if (sqlVals.Length > 0)
                                        {
                                            Cols[_colInfo.Column.Name] = sqlVals[0];
                                            if (sqlVals.Length > 1)
                                                Cols[_colInfo.Column.Name + "_bin"] = sqlVals[1];
                                        }
                                    }
                                }

                                // insert or update the data record
                                bool isValid = false;
                                if (firstSubTable && SubTableResult.SubTable.ParentTable.Type != eDataHistoryTableType.Static)
                                    isValid = await DataHistorySqlHelper.DoInsertAsync(Conn, TableName, Cols, Token, requestInfo, LogDeep + 1, LogSilent: true);
                                else if (SubTableResult.SubTable.ParentTable.Type != eDataHistoryTableType.Events)
                                    isValid = await DataHistorySqlHelper.DoUpdateOrInsertAsync(Conn, TableName, Cols, IDs, requestInfo, LogDeep + 1, Token, LogSilent: true);

                                if (isValid)
                                    RecordCount++;
                                else
                                    ErrorCount++;
                            }

                        }

                        LogEntry($"{RecordCount}/{SubTableResult?.Result?.Data?.Count} records inserted or updated for sub-table {SubTableResult?.SubTable?.Name} in table {SubTableResult?.SubTable?.ParentTable?.Name}", LogLevels.Debug);
                        if (ErrorCount != 0)
                            LogEntry($"{ErrorCount}/{SubTableResult?.Result?.Data?.Count} errors on inserts or updates for sub-table {SubTableResult?.SubTable?.Name} in table {SubTableResult?.SubTable?.ParentTable?.Name}", LogLevels.Error);

                        var _sum = ErrorCount + RecordCount;
                        var isValidResult = false;
                        if (_sum > 0 && (ErrorCount * 1000 / _sum) >= 100)
                            DataCollector.errorResultCount++;
                        else
                        {
                            DataCollector.validResultCount++;
                            isValidResult = true;
                        }

                        var msg = $"Result for nxql-subtable '{SubTableResult?.SubTable?.Name}' on server '{Engine.Name}': {RecordCount} records inserted";
                        if (ErrorCount != 0)
                            msg += $", {ErrorCount} records failed";
                        msg += ".";
                        DataCollector?.Collector?.DoProcessUiMessage(0, msg);
                        if (isValidResult && ErrorCount != 0)
                            DataCollector?.Collector?.DoProcessUiMessage(1, $"The result is considered valid.");
                        else if (ErrorCount != 0)
                            DataCollector?.Collector?.DoProcessUiMessage(1, $"The result is considered invalid.");

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

    public class cNxqlDataCollectorSubTable
    {
        public readonly cDataHistoryConfigSubTable SubTable;
        public readonly cNxqlDataCollectorTable TableCollector;
        public readonly cDataHistoryNxtEngine Server;
        public readonly string NXQL;

        public cCsvFile Result = null;
        public bool finished = false;

        private cF4sdWebRequestInfo requestInfo;
        private int LogDeep;

        public cNxqlDataCollectorSubTable(string NXQL, cDataHistoryConfigSubTable SubTable, cNxqlDataCollectorTable TableCollector, cDataHistoryNxtEngine Server, cF4sdWebRequestInfo requestInfo, int LogDeep)
        {
            this.NXQL = NXQL;
            this.SubTable = SubTable;
            this.TableCollector = TableCollector;
            this.Server = Server;
            this.requestInfo = requestInfo;
            this.LogDeep = LogDeep;
            TableCollector.RegisterSubTableCollector(this);
        }

        private void LogContent(string Content)
        {
            var Lines = Content.Split('\n');
            var arrDebug = new List<string>(5)
                {
                    $"NXQL query for sub-table '{SubTable.Name}': {NXQL}",
                    "Resulting lines: " + Lines.Length.ToString()
                };
            string str = "Line1: ";
            if (Lines.Length >= 1)
                str += Lines[0].Substring(0, Math.Min(255, Lines[0].Length));
            arrDebug.Add(str);
            str = "Line2: ";
            if (Lines.Length >= 2)
                str += Lines[1].Substring(0, Math.Min(255, Lines[1].Length));
            arrDebug.Add(str);
            str = "Line3: ";
            if (Lines.Length >= 3)
                str += Lines[2].Substring(0, Math.Min(255, Lines[2].Length));
            arrDebug.Add(str);
            cLogManager.DefaultLogger.LogList(LogLevels.Debug, arrDebug);
        }

        public async Task PostProcessing(string Content, CancellationToken Token)
        {

            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
            {
                if (TableCollector.DataCollector.FileSavePath != null)
                {
                    try
                    {
                        var fn = Path.Combine(TableCollector.DataCollector.FileSavePath, $"NXQL-{SubTable.Name}-{Server.Name}({Server.Address}).txt");
                        File.WriteAllText(fn, Content, Encoding.UTF8);
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                }
                var csvResult = new cCsvFile();
                if (cLogManager.DefaultLogger.IsDebug)
                    LogContent(Content);

                if (csvResult.ImportFromString(Content))
                {
                    Result = csvResult;

                    var lines = Result.Data.Count;
                    TableCollector?.DataCollector?.Collector?.DoProcessUiMessage(0, $"NXQL query for sub-table {SubTable.Name} on server {Server.Name}({Server.Address}) returned {lines} lines");

                    await TableCollector.ProcessResult(SubTable.Name, requestInfo, LogDeep + 1, Token);
                }
                else
                {
                    TableCollector.DataCollector.errorResultCount++;
                    LogEntry($"Could not convert result of NXQL query for subtable {SubTable.Name} on engine {Server.Name}({Server.Address}) to csv.", LogLevels.Error);
                }
            }
            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);
            }


        }

        public async Task ErrorProcessing(string Content, CancellationToken Token)
        {
            TableCollector.DataCollector.errorResultCount++;
            await Task.CompletedTask;
            LogEntry($"Could not get result of NXQL query for subtable {SubTable.Name} on engine {Server.Name}({Server.Address})", LogLevels.Error);
        }


    }

    public class cNxqlColumnInfo
    {
        public cDataHistoryConfigColumnBase Column;
        public int ColIndex;
        public string StaticValue;
    }

    public class cNxqlAggregationEntry
    {
        internal cDataHistoryConfigColumn Column;

        internal object LatestValue = null;
        internal DateTime? LastestEvent = null;

        internal Int64? aggInt = null;
        internal Double? aggDouble = null;
        internal Double? weightDouble = null;
        internal String aggString = null;
        internal List<string> listString = null;
        internal Boolean? aggBoolean = null;
        internal DateTime? aggEvent = null;
        internal DateTime? aggTime = null;
        internal Version aggVersion = null;

        internal cNxqlAggregationEntry(cDataHistoryConfigColumn Column)
        {
            this.Column = Column;
        }

        private static string getStingList(List<string> L)
        {
            if (L == null || L.Count == 0)
                return null;

            var RetVal = "";
            foreach (var Entry in L)
            {
                if (string.IsNullOrEmpty(Entry))
                    continue;
                if (RetVal != "")
                    RetVal += ',';
                RetVal += Entry;
            }
            return RetVal;
        }

        internal void addValue(object Value, DateTime? EventTime2)
        {
            try
            {
                DateTime EventTime;
                if (EventTime2 == null)
                    EventTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                else
                    EventTime = (DateTime)EventTime2;

                switch (Column.AggregationType)
                {
                    case eDataHistoryAggregationType.average:
                        if (Value != null)
                        {
                            switch (Column.ValueType)
                            {
                                case enumFasdValueType.INT:
                                case enumFasdValueType.BIGINT:
                                case enumFasdValueType.FLOAT:
                                    var val01 = Convert.ToDouble(Value);
                                    if (weightDouble == null)
                                        weightDouble = 1;
                                    else
                                        weightDouble++;
                                    if (aggDouble == null)
                                        aggDouble = val01;
                                    else
                                        aggDouble += val01;
                                    return;
                            }
                        }
                        return;
                    case eDataHistoryAggregationType.latestTime:
                        if (aggEvent == null || aggEvent < EventTime)
                            aggEvent = EventTime;
                        return;
                    case eDataHistoryAggregationType.count:
                        if (aggInt == null)
                            aggInt = 1;
                        else
                            aggInt++;
                        return;
                    case eDataHistoryAggregationType.valuecount:
                        if (Value != null)
                        {
                            if (aggInt == null)
                                aggInt = 1;
                            else
                                aggInt++;
                        }
                        return;
                }

                switch (Column.ValueType)
                {
                    case enumFasdValueType.INT:
                    case enumFasdValueType.BIGINT:
                        if (Value != null)
                        {
                            var val02 = Convert.ToInt64(Value);
                            switch (Column.AggregationType)
                            {
                                case eDataHistoryAggregationType.min:
                                    if (aggInt == null)
                                        aggInt = val02;
                                    else
                                        aggInt = Math.Min((Int64)aggInt, val02);
                                    return;
                                case eDataHistoryAggregationType.max:
                                    if (aggInt == null)
                                        aggInt = val02;
                                    else
                                        aggInt = Math.Max((Int64)aggInt, val02);
                                    return;
                                case eDataHistoryAggregationType.sum:
                                    if (aggInt == null)
                                        aggInt = val02;
                                    else
                                        aggInt += val02;
                                    return;
                                case eDataHistoryAggregationType.first:
                                case eDataHistoryAggregationType.Unknown:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggInt = val02;
                                    }
                                    else
                                    {
                                        if (EventTime < aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggInt = val02;
                                        }
                                    }
                                    return;
                                case eDataHistoryAggregationType.latestValue:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggInt = val02;
                                    }
                                    else
                                    {
                                        if (EventTime >= aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggInt = val02;
                                        }
                                    }
                                    return;
                            }
                        }
                        return;
                    case enumFasdValueType.FLOAT:
                        if (Value != null)
                        {
                            var val03 = Convert.ToDouble(Value);
                            switch (Column.AggregationType)
                            {
                                case eDataHistoryAggregationType.min:
                                    if (aggDouble == null)
                                        aggDouble = val03;
                                    else
                                        aggDouble = Math.Min((Double)aggDouble, val03);
                                    return;
                                case eDataHistoryAggregationType.max:
                                    if (aggDouble == null)
                                        aggDouble = val03;
                                    else
                                        aggDouble = Math.Max((Double)aggDouble, val03);
                                    return;
                                case eDataHistoryAggregationType.sum:
                                    if (aggDouble == null)
                                        aggDouble = val03;
                                    else
                                        aggDouble += val03;
                                    return;
                                case eDataHistoryAggregationType.first:
                                case eDataHistoryAggregationType.Unknown:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggDouble = val03;
                                    }
                                    else
                                    {
                                        if (EventTime < aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggDouble = val03;
                                        }
                                    }
                                    return;
                                case eDataHistoryAggregationType.latestValue:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggDouble = val03;
                                    }
                                    else
                                    {
                                        if (EventTime >= aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggDouble = val03;
                                        }
                                    }
                                    return;
                            }
                        }
                        return;
                    case enumFasdValueType.DATETIME:
                        if (Value != null)
                        {
                            var val04 = Convert.ToDateTime(Value);
                            switch (Column.AggregationType)
                            {
                                case eDataHistoryAggregationType.min:
                                    if (aggTime == null)
                                        aggTime = val04;
                                    else if (val04 < aggTime)
                                        aggTime = val04;
                                    return;
                                case eDataHistoryAggregationType.max:
                                    if (aggTime == null)
                                        aggTime = val04;
                                    else if (val04 > aggTime)
                                        aggTime = val04;
                                    return;
                                case eDataHistoryAggregationType.sum:
                                    return;
                                case eDataHistoryAggregationType.first:
                                case eDataHistoryAggregationType.Unknown:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggTime = val04;
                                    }
                                    else
                                    {
                                        if (EventTime < aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggTime = val04;
                                        }
                                    }
                                    return;
                                case eDataHistoryAggregationType.latestValue:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggTime = val04;
                                    }
                                    else
                                    {
                                        if (EventTime >= aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggTime = val04;
                                        }
                                    }
                                    return;
                            }
                        }
                        return;
                    case enumFasdValueType.VERSION:
                        if (Value != null && Version.TryParse(Value.ToString(), out var val05))
                        {
                            switch (Column.AggregationType)
                            {
                                case eDataHistoryAggregationType.min:
                                    if (aggVersion == null)
                                        aggVersion = val05;
                                    else if (val05 < aggVersion)
                                        aggVersion = val05;
                                    return;
                                case eDataHistoryAggregationType.max:
                                    if (aggVersion == null)
                                        aggVersion = val05;
                                    else if (val05 > aggVersion)
                                        aggVersion = val05;
                                    return;
                                case eDataHistoryAggregationType.sum:
                                    var strVal05 = val05.ToString();
                                    if (listString == null)
                                        listString = new List<string>() { strVal05 };
                                    else if (listString.Count < 32 && !listString.Contains(strVal05))
                                        listString.Add(strVal05);
                                    return;
                                case eDataHistoryAggregationType.first:
                                case eDataHistoryAggregationType.Unknown:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggVersion = val05;
                                    }
                                    else
                                    {
                                        if (EventTime < aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggVersion = val05;
                                        }
                                    }
                                    return;
                                case eDataHistoryAggregationType.latestValue:
                                    if (aggEvent == null)
                                    {
                                        aggEvent = EventTime;
                                        aggVersion = val05;
                                    }
                                    else
                                    {
                                        if (EventTime >= aggEvent)
                                        {
                                            aggEvent = EventTime;
                                            aggVersion = val05;
                                        }
                                    }
                                    return;
                            }
                        }
                        return;
                    case enumFasdValueType.BOOLEAN:
                        if (Value == null)
                            return;

                        bool val06 = false;
                        if (Value is bool v)
                            val06 = v;
                        else if (Value is string val06Str)
                        {
                            switch (val06Str.Trim().ToLower())
                            {
                                case "1":
                                case "true":
                                case "yes":
                                    val06 = true;
                                    break;
                                case "0":
                                case "false":
                                case "no":
                                    break;
                                default:
                                    return;
                            }
                        }
                        else if (Value is sbyte
                                || Value is byte
                                || Value is short
                                || Value is ushort
                                || Value is int
                                || Value is uint
                                || Value is long
                                || Value is ulong)
                        {
                            var val06Int = Convert.ToInt64(Value);
                            if (val06Int == 0) { }
                            else if (val06Int == 1)
                                val06 = true;
                            return;
                        }
                        else if (Value is float
                                || Value is double
                                || Value is decimal)
                        {
                            var val06Double = Convert.ToDouble(Value);
                            if (val06Double == 0) { }
                            else if (val06Double == 1)
                                val06 = true;
                            else
                                return;
                        }
                        else
                            return;

                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                                if (aggBoolean == null)
                                    aggBoolean = val06;
                                else
                                    aggBoolean &= val06;
                                return;
                            case eDataHistoryAggregationType.max:
                                if (aggBoolean == null)
                                    aggBoolean = val06;
                                else
                                    aggBoolean |= val06;
                                return;
                            case eDataHistoryAggregationType.sum:
                                return;
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.Unknown:
                                if (aggEvent == null)
                                {
                                    aggEvent = EventTime;
                                    aggBoolean = val06;
                                }
                                else
                                {
                                    if (EventTime < aggEvent)
                                    {
                                        aggEvent = EventTime;
                                        aggBoolean = val06;
                                    }
                                }
                                return;
                            case eDataHistoryAggregationType.latestValue:
                                if (aggEvent == null)
                                {
                                    aggEvent = EventTime;
                                    aggBoolean = val06;
                                }
                                else
                                {
                                    if (EventTime >= aggEvent)
                                    {
                                        aggEvent = EventTime;
                                        aggBoolean = val06;
                                    }
                                }
                                return;
                        }
                        return;
                    default:
                        if (Value == null)
                            return;
                        var val07 = Value.ToString();
                        if (val07 == "")
                            return;
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.sum:
                                if (listString == null)
                                    listString = new List<string>() { val07 };
                                else if (listString.Count < 32 && !listString.Contains(val07))
                                    listString.Add(val07);
                                return;
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.Unknown:
                                if (aggEvent == null)
                                {
                                    aggEvent = EventTime;
                                    aggString = val07;
                                }
                                else
                                {
                                    if (EventTime < aggEvent)
                                    {
                                        aggEvent = EventTime;
                                        aggString = val07;
                                    }
                                }
                                return;
                            case eDataHistoryAggregationType.latestValue:
                                if (aggEvent == null)
                                {
                                    aggEvent = EventTime;
                                    aggString = val07;
                                }
                                else
                                {
                                    if (EventTime >= aggEvent)
                                    {
                                        aggEvent = EventTime;
                                        aggString = val07;
                                    }
                                }
                                return;
                        }
                        return;
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        public object getValue()
        {
            try
            {
                switch (Column.AggregationType)
                {
                    case eDataHistoryAggregationType.average:
                        switch (Column.ValueType)
                        {
                            case enumFasdValueType.INT:
                            case enumFasdValueType.BIGINT:
                            case enumFasdValueType.FLOAT:
                                if (weightDouble == 0 || aggDouble == null)
                                    return null;
                                else
                                    return (aggDouble / weightDouble);
                        }
                        return null;
                    case eDataHistoryAggregationType.latestTime:
                        return aggEvent;
                    case eDataHistoryAggregationType.count:
                    case eDataHistoryAggregationType.valuecount:
                        return aggInt;
                }

                switch (Column.ValueType)
                {
                    case enumFasdValueType.INT:
                    case enumFasdValueType.BIGINT:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.sum:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                            case eDataHistoryAggregationType.Unknown:
                                if (Column.ValueType == enumFasdValueType.INT)
                                    return Convert.ToInt32(aggInt);
                                return aggInt;
                        }
                        return null;
                    case enumFasdValueType.FLOAT:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.sum:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                            case eDataHistoryAggregationType.Unknown:
                                return Convert.ToSingle(aggDouble);
                        }
                        return null;
                    case enumFasdValueType.DATETIME:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                            case eDataHistoryAggregationType.Unknown:
                                return aggTime;
                        }
                        return null;
                    case enumFasdValueType.VERSION:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                            case eDataHistoryAggregationType.Unknown:
                                return aggVersion;
                            case eDataHistoryAggregationType.sum:
                                return getStingList(listString);
                        }
                        return null;
                    case enumFasdValueType.BOOLEAN:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.min:
                            case eDataHistoryAggregationType.max:
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                            case eDataHistoryAggregationType.Unknown:
                                return aggBoolean;
                        }
                        return null;
                    default:
                        switch (Column.AggregationType)
                        {
                            case eDataHistoryAggregationType.first:
                            case eDataHistoryAggregationType.latestValue:
                            case eDataHistoryAggregationType.Unknown:
                                return aggString;
                            case eDataHistoryAggregationType.sum:
                                return getStingList(listString);
                        }
                        return null;
                }

            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }
    }

}
