using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;

using Newtonsoft.Json;

using C4IT.Logging;
using C4IT.XML;
using C4IT.Configuration;
using C4IT.FASD.Base;
using static C4IT.Logging.cLogManager;

namespace C4IT.DataHistoryProvider
{
    public enum eDataHistoryTableCached { Default = 0, Yes = 1, No = 2 };

    public enum eDataHistoryQueryType { Query = 0, Static = 1 }

    public enum eDataHistoryAggregationType
    {
        Unknown = 0,
        first = 1,
        min = 2,
        max = 3,
        sum = 4,
        average = 5,
        count = 6,
        valuecount = 7,
        latestValue = 8,
        latestTime = 9
    }

    public class cDataHistoryConfigClusters : cFasdBaseConfig
    {
        public static bool confUseBinValuesAsPrimaryKey = true;

        public const string constFileNameF4sdConfig = "F4SD-DataClusters-Configuration.xml";
        private const string constFileNameF4sdSchema = "F4SD-DataClusters-Configuration.xsd";
        private const string constConfigRootElement = "F4SD-DataClusters-Configuration";

        public static readonly Guid ConfigId = new Guid("28C26B80-C3E9-43E1-934E-57B7C2DBC374");

        public Guid getId() { return ConfigId; }

        public Dictionary<string, cDataHistoryConfigCluster> Clusters { get; private set; } = null;

        public cDataHistoryConfigCluster MainCluster { get; private set; } = null;

        public readonly Dictionary<string, cDataHistoryConfigTable> Tables = new Dictionary<string, cDataHistoryConfigTable>();

        public readonly Dictionary<string, string> AlternateTableNames = new Dictionary<string, string>();
        
        internal cDataHistoryConfigClusters() :
            base(constFileNameF4sdConfig, constFileNameF4sdSchema, constConfigRootElement)
        {
        }

        public override bool InstantiateProperties(XmlElement XRoot, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                MainCluster = new cDataHistoryConfigClusterMain(XRoot, this, Parser);
                if (MainCluster == null)
                {
                    Parser.AddMessage(XRoot, @"No valid main cluster were found in the configuration.", LogLevels.Fatal);
                    return false;
                }

                Clusters = cDataHistoryConfigCluster.LoadFromXml(XRoot, this, Parser);
                if (Clusters == null)
                {
                    Parser.AddMessage(XRoot, @"No valid clusters were found in the configuration.", LogLevels.Fatal);
                    return false;
                }

                Clusters.Add(MainCluster.Name, MainCluster);

                // resolve the references
                var _IsValid = true;
                foreach (var Cluster in Clusters.Values)
                    foreach (var Table in Cluster.Tables.Values)
                        IsValid &= Table.ResolveReferences(Tables, Parser);

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

            return false;
        }

        public override bool DoXmlUpdates(XmlElement XmlRoot, bool withM42Config = false, bool withIntuneConfig = false, bool withMobileDeviceConfig = false, bool withCitrixConfig = false)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var RetVal = false;
            try
            {
                RetVal |= DoXmlInsertElement(XmlRoot
                     , ""
                     , "DataCluster[@Name='Main-ComputerInfo']"
                     , "<DataCluster Name=\"Main-ComputerInfo\" Origin=\"Main\" InformationClass=\"Computer\"/>"
                     , "InformationClasses"
                     );

                RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='Main-ComputerInfo']"
                    , "Table[@Name='main-computer-quickaction-details']"
                    , "<Table Name=\"main-computer-quickaction-details\" SourceName=\"main-computer-quickaction\" Type=\"Events\" Key=\"DeviceId\" EventTimeCol=\"Start\" Cached=\"Yes\" LateDelivery=\"true\"><Table-Columns><Table-Column Name=\"DeviceId\" Type=\"guid\"/><Table-Column Name=\"Start\" Type=\"datetime\"/><Table-Column Name=\"Finish\" Type=\"datetime\"/><Table-Column Name=\"QActionName\" Type=\"string\" Cardinal=\"100\"/><Table-Column Name=\"ResultType\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"ExecutionType\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"Parameters\" Type=\"string\"/><Table-Column Name=\"Result\" Type=\"string\"/><Table-Column Name=\"ErrorCode\" Type=\"int\"/><Table-Column Name=\"ErrorDescription\" Type=\"string\"/></Table-Columns><AutoCreate-Historic-Table Name=\"main-computer-quickaction-history\"/></Table>"
                    , ""
                    );

                RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-UserInfo", "ad-user", "<Table-Column Name=\"domainDns\" SourceType =\"Static\" Type=\"string\" Cardinal=\"128\"/>", "account");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-UserInfo", "ad-user", "<Table-Column Name=\"domainNB\" SourceType =\"Static\" Type=\"string\" Cardinal=\"16\"/>", "account");

                RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-ComputerInfo", "ad-computer", "<Table-Column Name=\"domainDns\" SourceType =\"Static\" Type=\"string\" Cardinal=\"128\"/>", "account");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "AD-ComputerInfo", "ad-computer", "<Table-Column Name=\"domainNB\" SourceType =\"Static\" Type=\"string\" Cardinal=\"16\"/>", "account");

                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer", "<Table-Column Name=\"IsPhoenixInstalled\" SourceName=\"Phoenix Installed\" Type=\"boolean\" Aggregation=\"latestValue\"/>");

                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"DeviceActivity\" SourceName=\"Device Activity\" Type=\"bigint\" Aggregation=\"sum\"/>");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"UserActivity\" SourceName=\"User Activity\" Type=\"bigint\" Aggregation=\"sum\"/>");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"ProcessorTaskManager\" SourceName=\"Task Manager % Score\" Type=\"float\" Aggregation=\"average\" LateDelivery=\"true\" />");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"CPUSpeed\" SourceName=\"CPU Speed\" Type=\"float\" Aggregation=\"average\" LateDelivery=\"true\" />");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"CPUBaseSpeed\" SourceName=\"CPU Base Speed\" Type=\"float\" Aggregation=\"average\" LateDelivery=\"true\" />");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"ProzessorHighEvents\" SourceName=\"Prozessor High Events\" Type=\"datetime\" Aggregation=\"count\" />");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"LoginDuration\" SourceName=\"Login Duration\" Type=\"bigint\" Aggregation=\"max\" />");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-DeviceInfo", "agnt-computer-event-numerical", "<Table-Column Name=\"ExtendedLoginDuration\" SourceName=\"Extended Login Duration\" Type=\"bigint\" Aggregation=\"max\" />");

                RetVal |= DoXmlInsertTableRow(XmlRoot, "Agent-UserInfo", "agnt-user", "<Table-Column Name=\"accountType\" SourceType =\"Static\" Type=\"string\" Cardinal=\"16\"/>");

                RetVal |= DoXmlInsertElement(XmlRoot,
                    "DataCluster[@Name='Agent-DeviceInfo']/Table[@Name='agnt-computer-event-numerical']",
                    "AdditionalTableName[@Name='agnt-computer-event-numerical-latest']",
                    "<AdditionalTableName Name =\"agnt-computer-event-numerical-latest\"/>",
                    "Table-Columns");

                RetVal |= DoXmlInsertElement(XmlRoot,
                    "DataCluster[@Name='Agent-DeviceInfo']/Table[@Name='agnt-computer-event-string']",
                    "AdditionalTableName[@Name='agnt-computer-event-string-latest']",
                    "<AdditionalTableName Name =\"agnt-computer-event-string-latest\"/>",
                    "Table-Columns");

                RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='Agent-DeviceInfo']"
                    , "Table[@Name='agnt-computer-event-details-processorTaskManager']"
                    , "<Table Name=\"agnt-computer-event-details-processorTaskManager\" Type=\"Events\" Key=\"id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"id\" SourceName=\"DEVICECODE\" Type=\"int\" /><Table-Column Name=\"event\" SourceName=\"Task Manager % Score\" Type=\"string\" /><Table-Column Name=\"time\" SourceName=\"EVENTDATE\" Type=\"datetime\" /><Table-Column Name=\"Duration\" SourceName=\"DURATION\" Type=\"bigint\" /><Table-Column Name=\"Value\" SourceName=\"VALUE\" Type=\"float\" /></Table-Columns></Table>"
                    , "Table[@Name='agnt-computer-event-details-processorUsage']"
                    );

                RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='Agent-DeviceInfo']"
                    , "Table[@Name='agnt-computer-event-details-processorTaskManager']"
                    , "<Table Name=\"agnt-computer-event-details-processorTaskManager\" Type=\"Events\" Key=\"id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"id\" SourceName=\"DEVICECODE\" Type=\"int\" /><Table-Column Name=\"event\" SourceName=\"Task Manager % Score\" Type=\"string\" /><Table-Column Name=\"time\" SourceName=\"EVENTDATE\" Type=\"datetime\" /><Table-Column Name=\"Duration\" SourceName=\"DURATION\" Type=\"bigint\" /><Table-Column Name=\"Value\" SourceName=\"VALUE\" Type=\"float\" /></Table-Columns></Table>"
                    , "Table[@Name='agnt-computer-event-details-processorUsage']"
                    );

                RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='Agent-DeviceInfo']"
                    , "Table[@Name='agnt-computer-event-details-CPUSpeed']"
                    , "<Table Name=\"agnt-computer-event-details-CPUSpeed\" Type=\"Events\" Key=\"id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"id\" SourceName=\"DEVICECODE\" Type=\"int\" /><Table-Column Name=\"event\" SourceName=\"CPU Speed\" Type=\"string\" /><Table-Column Name=\"time\" SourceName=\"EVENTDATE\" Type=\"datetime\" /><Table-Column Name=\"Duration\" SourceName=\"DURATION\" Type=\"bigint\" /><Table-Column Name=\"Value\" SourceName=\"VALUE\" Type=\"float\" /></Table-Columns></Table>"
                    , "Table[@Name='agnt-computer-event-details-processorTaskManager']"
                    );

                RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='Agent-DeviceInfo']"
                    , "Table[@Name='agnt-computer-event-details-ProzessorHighEvents']"
                    , "<Table Name=\"agnt-computer-event-details-ProzessorHighEvents\" Type=\"Events\" Key=\"id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"id\" SourceName=\"DEVICECODE\" Type=\"int\" /><Table-Column Name=\"event\" SourceName=\"Prozessor High Events\" Type=\"string\" /><Table-Column Name=\"time\" SourceName=\"EVENTDATE\" Type=\"datetime\" /><Table-Column Name=\"Duration\" SourceName=\"DURATION\" Type=\"bigint\" /><Table-Column Name=\"Value\" SourceName=\"VALUE\" Type=\"float\" /></Table-Columns></Table>"
                    , "Table[@Name='agnt-computer-event-details-CPUSpeed']"
                    );
                if (withIntuneConfig)
                {
                    RetVal |= DoXmlInsertElement(XmlRoot
                    , ""
                    , "DataCluster[@Name='IntuneDeviceInfo']"
                    , "<DataCluster Name=\"IntuneDeviceInfo\" Origin=\"Intune\" InformationClass=\"Computer\"/>"
                    , "DataCluster[@Name='AD-ComputerInfo']"
                    );
                    RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='IntuneDeviceInfo']"
                    , "Table[@Name='intune-deviceInfo']"
                    , "<Table Name=\"intune-deviceInfo\" Type=\"Static\" Key=\"id\"><Table-Columns><Table-Column Name=\"id\" Type=\"guid\"/><Table-Column Name=\"complianceState\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"userPrincipalName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"managementState\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"userDisplayName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"lastSyncDateTime\" Type=\"string\" Cardinal=\"64\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                    , ""
                    , "DataCluster[@Name='IntuneUserInfo']"
                    , "<DataCluster Name=\"IntuneUserInfo\" Origin=\"Intune\" InformationClass=\"User\"/>"
                    , "DataCluster[@Name='IntuneDeviceInfo']"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='IntuneUserInfo']"
                    , "Table[@Name='intune-userDeviceCount']"
                    , "<Table Name=\"intune-userDeviceCount\" Type=\"Static\" Key=\"id\"><Table-Columns><Table-Column Name=\"id\" Type=\"guid\"/><Table-Column Name=\"windows\" Type=\"int\" Default=\"0\"/><Table-Column Name=\"iOS\" Type=\"int\" Default=\"0\"/><Table-Column Name=\"android\" Type=\"int\" Default=\"0\"/><Table-Column Name=\"linux\" Type=\"int\" Default=\"0\"/><Table-Column Name=\"macOs\" Type=\"int\" Default=\"0\"/><Table-Column Name=\"total\" Type=\"int\" Default=\"0\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                     , "DataCluster[@Name='IntuneUserInfo']"
                     , "Table[@Name='intune-userDeviceCount-details']"
                     , "<Table Name=\"intune-userDeviceCount-details\" Type=\"StaticDetails\" Key=\"id\"><Table-Columns><Table-Column Name=\"id\" Type=\"guid\"/><Table-Column Name=\"deviceName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"model\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"operatingSystem\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"serialNumber\" Type=\"string\" Cardinal=\"64\"/></Table-Columns></Table>"
                     );
                }

                if (withIntuneConfig && withMobileDeviceConfig)
                {
                    RetVal |= DoXmlInsertElement(XmlRoot
                    , ""
                    , "DataCluster[@Name='IntuneMobileDeviceInfo']"
                    , "<DataCluster Name=\"IntuneMobileDeviceInfo\" Origin=\"Intune\" InformationClass=\"MobileDevice\"/>"
                    , "DataCluster[@Name='IntuneUserInfo']"
                    );
                    RetVal |= DoXmlInsertElement(XmlRoot
                    , "DataCluster[@Name='IntuneMobileDeviceInfo']"
                    , "Table[@Name='intune-mobile-deviceInfo']"
                    , "<Table Name=\"intune-mobile-deviceInfo\" Type=\"Static\" Key=\"id\"><Table-Columns><Table-Column Name=\"id\" Type=\"guid\"/><Table-Column Name=\"complianceState\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"userPrincipalName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"managementState\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"userDisplayName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"lastSyncDateTime\" Type=\"datetime\"/><Table-Column Name=\"deviceName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"deviceRegistrationState\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"deviceType\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"easDeviceId\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"enrolledDateTime\" Type=\"datetime\"/><Table-Column Name=\"enrollmentProfileName\" Type=\"string\" Cardinal=\"128\"/><Table-Column Name=\"freeStorageSpaceInBytes\" Type=\"bigint\"/><Table-Column Name=\"imei\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"isEncrypted\" Type=\"boolean\"/><Table-Column Name=\"isSupervised\" Type=\"boolean\"/><Table-Column Name=\"jailBroken\" Type=\"boolean\"/><Table-Column Name=\"lostModeState\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"managedDeviceOwnerType\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"managementAgent\" Type=\"string\" Cardinal=\"16\"/><Table-Column Name=\"managementCertificateExpirationDate\" Type=\"datetime\"/><Table-Column Name=\"manufacturer\" Type=\"string\" Cardinal=\"128\"/><Table-Column Name=\"model\" Type=\"string\" Cardinal=\"128\"/><Table-Column Name=\"operatingSystem\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"osVersion\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"ownerType\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"phoneNumber\" Type=\"string\" Cardinal=\"32\"/><Table-Column Name=\"physicalMemoryInBytes\" Type=\"bigint\"/><Table-Column Name=\"serialNumber\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"subscriberCarrier\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"totalStorageSpaceInBytes\" Type=\"bigint\"/><Table-Column Name=\"wiFiMacAddress\" Type=\"string\" Cardinal=\"32\"/></Table-Columns></Table>"
                    );
                }

                if (withCitrixConfig)
                {
                    RetVal |= DoXmlInsertElement(XmlRoot
                        , ""
                        , "DataCluster[@Name='citrixMetrics']"
                        , "<DataCluster Name=\"citrixMetrics\" Origin=\"Citrix\" InformationClass=\"VirtualSession\"/>"
                        , "DataCluster[@Name='AD-ComputerInfo']"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='citrixMetrics']"
                        , "Table[@Name='citrix-session']"
                        , "<Table Name=\"citrix-session\" Type=\"Static\" Key=\"Id\"><Table-Columns><Table-Column Name=\"Id\" SourceName=\"SessionKey\" Type=\"guid\"/><Table-Column Name=\"StartDate\" Type=\"datetime\"/><Table-Column Name=\"EndDate\" Type=\"datetime\"/><Table-Column Name=\"LogOnDuration\" Type=\"int\"/><Table-Column Name=\"ConnectionState\" Type=\"int\" Cardinal=\"64\"/><Table-Column Name=\"EndpointName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"IsAnonymous\" Type=\"boolean\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                          , "DataCluster[@Name='citrixMetrics']"
                          , "Table[@Name='citrix-session-metrics']"
                          , "<Table Name=\"citrix-session-metrics\" Type=\"History\" Key=\"Id\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"CollectedDate\" Type=\"datetime\"/><Table-Column Name=\"IcaRttMS\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"IcaLatency\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"ClientL7Latency\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"ServerL7Latency\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"ConnectionState\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"InputBandwidthUsed\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"OutputBandwidthUsed\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"OutputBandwidthAvailable\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"Fps\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"InputFps\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"OutputFps\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"WanLatency\" Type=\"int\" Aggregation=\"average\"/><Table-Column Name=\"DcLatency\" Type=\"int\" Aggregation=\"average\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-connectionState-metrics']"
                         , "<Table Name=\"citrix-connectionState-metrics\" Type=\"History\" Key=\"Id\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\" /><Table-Column Name=\"StartDate\" Type=\"datetime\" /><Table-Column Name=\"ConnectionState\" Type=\"int\" /><Table-Column Name=\"ConnectionCount\" Type=\"int\" /></Table-Columns></Table>");

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-icaRttMS']"
                         , "<Table Name=\"citrix-session-details-icaRttMS\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"IcaRttMS\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-icaLatency']"
                         , "<Table Name=\"citrix-session-details-icaLatency\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"IcaLatency\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-clientL7Latency']"
                         , "<Table Name=\"citrix-session-details-clientL7Latency\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"ClientL7Latency\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                     );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-serverL7Latency']"
                         , "<Table Name=\"citrix-session-details-serverL7Latency\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"ServerL7Latency\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-inputBandwidthUsed']"
                         , "<Table Name=\"citrix-session-details-inputBandwidthUsed\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"InputBandwidthUsed\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );
                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-outputBandwidthUsed']"
                         , "<Table Name=\"citrix-session-details-outputBandwidthUsed\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\" /><Table-Column Name=\"Value\" SourceName=\"OutputBandwidthUsed\" Type=\"int\" /><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\" /></Table-Columns></Table>");

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-outputBandwidthAvailable']"
                         , "<Table Name=\"citrix-session-details-outputBandwidthAvailable\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"OutputBandwidthAvailable\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-fps']"
                         , "<Table Name=\"citrix-session-details-fps\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"Fps\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-inputFps']"
                         , "<Table Name=\"citrix-session-details-inputFps\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"InputFps\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-outputFps']"
                         , "<Table Name=\"citrix-session-details-outputFps\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"OutputFps\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-wanLatency']"
                         , "<Table Name=\"citrix-session-details-wanLatency\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"WanLatency\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-details-dcLatency']"
                         , "<Table Name=\"citrix-session-details-dcLatency\" Type=\"Events\" Key=\"Id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"Id\" Type=\"int\"/><Table-Column Name=\"Value\" SourceName=\"DcLatency\" Type=\"int\"/><Table-Column Name=\"time\" SourceName=\"CollectedDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-machine']"
                         , "<Table Name=\"citrix-session-machine\" Type=\"Static\" Key=\"Id\"><Table-Columns><Table-Column Name=\"Id\" Type=\"guid\" /><Table-Column Name=\"Name\" Type=\"string\" /><Table-Column Name=\"CatalogName\" SourceName=\"Catalog\" SourceJsonField=\"Name\" Type=\"string\" Default=\"0\" /><Table-Column Name=\"AllocationType\" SourceName=\"Catalog\" SourceJsonField=\"AllocationType\" Type=\"int\" Default=\"0\" /><Table-Column Name=\"DesktopGroupName\" SourceName=\"DesktopGroup\" SourceJsonField=\"Name\" Type=\"string\" /><Table-Column Name=\"DnsName\" Type=\"string\" /><Table-Column Name=\"HostedMachine\" SourceName=\"HostedMachineId\" Type=\"string\" /><Table-Column Name=\"IPAddress\" Type=\"string\" /><Table-Column Name=\"WindowsConnectionSetting\" Type=\"int\" Default=\"0\" /><Table-Column Name=\"CurrentRegistrationState\" Type=\"int\" Default=\"0\" /><Table-Column Name=\"AgentVersion\" Type=\"string\" /><Table-Column Name=\"CurrentSessionCount\" Type=\"int\" /><Table-Column Name=\"OSType\" Type=\"string\" /><Table-Column Name=\"IsInMaintenanceMode\" Type=\"boolean\" /><Table-Column Name=\"Cpu\" SourceName=\"CurrentLoadIndex\" SourceJsonField=\"Cpu\" Type=\"int\" Default=\"0\" /><Table-Column Name=\"Memory\" SourceName=\"CurrentLoadIndex\" SourceJsonField=\"Memory\" Type=\"int\" Default=\"0\" /><Table-Column Name=\"Disk\" SourceName=\"CurrentLoadIndex\" SourceJsonField=\"Disk\" Type=\"int\" Default=\"0\" /></Table-Columns></Table>");

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-user']"
                         , "<Table Name=\"citrix-session-user\" Type=\"Static\" Key=\"Id\"><Table-Columns><Table-Column Name=\"Id\" Type=\"guid\"/><Table-Column Name=\"UserName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"FullName\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"Domain\" Type=\"string\" Cardinal=\"64\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-connection']"
                         , "<Table Name=\"citrix-session-connection\" Type=\"Static\" Key=\"Id\"><Table-Columns><Table-Column Name=\"Id\" Type=\"guid\"/><Table-Column Name=\"LogOnStartDate\" Type=\"datetime\"/><Table-Column Name=\"LogOnEndDate\" Type=\"datetime\"/><Table-Column Name=\"BrokeringDuration\" Type=\"int\"/><Table-Column Name=\"VMStartStartDate\" Type=\"datetime\"/><Table-Column Name=\"VMStartEndDate\" Type=\"datetime\"/><Table-Column Name=\"HdxStartDate\" Type=\"datetime\"/><Table-Column Name=\"HdxEndDate\" Type=\"datetime\"/><Table-Column Name=\"AuthenticationDuration\" Type=\"int\"/><Table-Column Name=\"GpoStartDate\" Type=\"datetime\"/><Table-Column Name=\"GpoEndDate\" Type=\"datetime\"/><Table-Column Name=\"LogOnScriptsStartDate\" Type=\"datetime\"/><Table-Column Name=\"LogOnScriptsEndDate\" Type=\"datetime\"/><Table-Column Name=\"ProfileLoadStartDate\" Type=\"datetime\"/><Table-Column Name=\"ProfileLoadEndDate\" Type=\"datetime\"/><Table-Column Name=\"InteractiveStartDate\" Type=\"datetime\"/><Table-Column Name=\"InteractiveEndDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    RetVal |= DoXmlInsertElement(XmlRoot
                         , "DataCluster[@Name='citrixMetrics']"
                         , "Table[@Name='citrix-session-currentConnection']"
                         , "<Table Name=\"citrix-session-currentConnection\" Type=\"Static\" Key=\"Id\"><Table-Columns><Table-Column Name=\"Id\" Type=\"guid\"/><Table-Column Name=\"ClientName\" Type=\"string\"/><Table-Column Name=\"ClientAddress\" Type=\"string\"/><Table-Column Name=\"ClientVersion\" Type=\"string\"/><Table-Column Name=\"ClientPlatform\" Type=\"string\"/><Table-Column Name=\"ConnectedViaIPAddress\" Type=\"string\"/><Table-Column Name=\"LaunchedViaHostName\" Type=\"string\"/><Table-Column Name=\"LogOnStartDate\" Type=\"datetime\"/><Table-Column Name=\"LogOnEndDate\" Type=\"datetime\"/><Table-Column Name=\"BrokeringDuration\" Type=\"int\"/><Table-Column Name=\"VMStartStartDate\" Type=\"datetime\"/><Table-Column Name=\"VMStartEndDate\" Type=\"datetime\"/><Table-Column Name=\"HdxStartDate\" Type=\"datetime\"/><Table-Column Name=\"HdxEndDate\" Type=\"datetime\"/><Table-Column Name=\"AuthenticationDuration\" Type=\"int\"/><Table-Column Name=\"GpoStartDate\" Type=\"datetime\"/><Table-Column Name=\"GpoEndDate\" Type=\"datetime\"/><Table-Column Name=\"LogOnScriptsStartDate\" Type=\"datetime\"/><Table-Column Name=\"LogOnScriptsEndDate\" Type=\"datetime\"/><Table-Column Name=\"ProfileLoadStartDate\" Type=\"datetime\"/><Table-Column Name=\"ProfileLoadEndDate\" Type=\"datetime\"/><Table-Column Name=\"InteractiveStartDate\" Type=\"datetime\"/><Table-Column Name=\"InteractiveEndDate\" Type=\"datetime\"/><Table-Column Name=\"UserInitStartDate\" Type=\"datetime\"/><Table-Column Name=\"UserInitEndDate\" Type=\"datetime\"/><Table-Column Name=\"AppxAssociationStartDate\" Type=\"datetime\"/><Table-Column Name=\"AppxAssociationEndDate\" Type=\"datetime\"/><Table-Column Name=\"AppxLoadPackageStartDate\" Type=\"datetime\"/><Table-Column Name=\"AppxLoadPackageEndDate\" Type=\"datetime\"/></Table-Columns></Table>"
                    );

                    
                }

                if (withM42Config)
                {
                    RetVal |= DoXmlInsertElement(XmlRoot
                        , ""
                        , "DataCluster[@Name='M42Tickets']"
                        , "<DataCluster Name=\"M42Tickets\" Origin=\"M42Wpm\" InformationClass=\"Ticket\" Description=\"Ticket &amp; incident data from Matrix42 Workplace Management\"/>"
                        , "DataCluster[@Name='AD-ComputerInfo']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Tickets']"
                        , "<Table Name=\"M42Wpm-Tickets\" Type=\"Static\" Key=\"id\"><Table-Columns><Table-Column Name=\"id\" SourceName=\"TicketObjectId\" Type=\"guid\"/><Table-Column Name=\"name\" SourceName=\"Name\" Type=\"string\" Cardinal =\"10\"/><Table-Column Name=\"Summary\" Type=\"string\" Cardinal =\"1024\"/><Table-Column Name=\"Status\" Type=\"string\" Cardinal =\"64\"/><Table-Column Name=\"StatusId\" Type=\"int\"/><Table-Column Name=\"AssetCIName\" Type=\"string\" Cardinal =\"64\"/><Table-Column Name=\"AssetName\" Type=\"string\" Cardinal =\"64\"/><Table-Column Name=\"AffectedUserId\" Type=\"guid\"/><Table-Column Name=\"AssetId\" Type=\"guid\"/><Table-Column Name=\"CreationDate\" Type=\"datetime\"/><Table-Column Name=\"ClosingDate\" Type=\"datetime\"/><Table-Column Name=\"CreationSourceId\" Type=\"int\"/><Table-Column Name=\"CreationSource\" Type=\"string\" Cardinal =\"256\"/><Table-Column Name=\"Description\" Type=\"string\"/><Table-Column Name=\"DescriptionHtml\" Type=\"string\"/><Table-Column Name=\"Solution\" Type=\"string\"/><Table-Column Name=\"SolutionHtml\" Type=\"string\" IsWritable=\"true\"/><Table-Column Name=\"PriorityId\" Type=\"int\"/><Table-Column Name=\"Priority\" Type=\"string\" Cardinal =\"256\"/><Table-Column Name=\"CategoryId\" Type=\"guid\"/><Table-Column Name=\"Category\" Type=\"string\" Cardinal =\"256\"/><Table-Column Name=\"CategoryHierarchical\" Type=\"string\" Cardinal =\"1024\"/><Table-Column Name=\"CIName\" Type=\"string\" Cardinal =\"64\"/><Table-Column Name=\"DirectLinkEdit\" Type=\"string\" Cardinal =\"1024\"/><Table-Column Name=\"AssetCIId\" Type=\"guid\"/><Table-Column Name=\"AssetSKUAssetGroupId\" Type=\"int\"/><Table-Column Name=\"AssetSKUAssetGroup\" Type=\"string\" Cardinal =\"256\"/><Table-Column Name=\"AssetSKUTypeId\" Type=\"int\"/><Table-Column Name=\"AssetSKUType\" Type=\"string\" Cardinal =\"256\"/><Table-Column Name=\"DirectLinkPreview\" Type=\"string\" Cardinal =\"1024\"/><Table-Column Name=\"DirectLinkClose\" Type=\"string\" Cardinal =\"1024\"/><Table-Column Name=\"AffectedUser\" Type=\"string\" Cardinal =\"256\"/><Table-Column Name=\"Urgency\" Type=\"string\" Cardinal=\"64\" /><Table-Column Name=\"UrgencyId\" Type=\"int\" /><Table-Column Name=\"Impact\" Type=\"string\" Cardinal=\"64\" /><Table-Column Name=\"ImpactId\" Type=\"int\" /></Table-Columns></Table>"
                        , "-first-"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-History']"
                        , "<Table Name=\"M42Wpm-Ticket-History\" Type=\"HistoryEvents\" Key=\"id\" EventTimeCol=\"time\"><Table-Columns><Table-Column Name=\"id\" SourceName=\"ActivityObjectId\" Type=\"guid\"/><Table-Column Name=\"time\" SourceName=\"CreationDate\" Type=\"datetime\"/><Table-Column Name=\"CreatedBy\" Type=\"string\" Cardinal=\"64\"/><Table-Column Name=\"Header\" Type=\"string\" Cardinal=\"1024\"/><Table-Column Name=\"Description\" Type=\"string\"/><Table-Column Name=\"DescriptionHtml\" Type=\"string\"/><Table-Column Name=\"IsVisibleForUser\" Type=\"boolean\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Tickets']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-Services']"
                        , "<Table Name=\"M42Wpm-Ticket-Services\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SPSArticleClassBase\" EntityTypeNames=\"SPSArticleTypeService SPSArticleTypeGroup SVCServiceTypeBundle SVCServiceTypeConfigGroup\" OrderBy=\"\" WhereExpression=\"\" /><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"SysName\" SourceName=\"Sys-Name\" Type=\"string\" Cardinal=\"50\"/><Table-Column Name=\"PortalName\" Type=\"string\" Cardinal =\"300\"/><Table-Column Name=\"Name\" Type=\"string\" Cardinal =\"300\"/><Table-Column Name=\"Status\" Type=\"string\" Cardinal =\"50\"/><Table-Column Name=\"ServiceType\" Type=\"int\"/><Table-Column Name=\"Contact\" Type=\"string\" Cardinal=\"1024\"/><Table-Column Name=\"TechnicalOwner\" Type=\"string\" Cardinal=\"1024\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-History']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-Assets']"
                        , "<Table Name=\"M42Wpm-Ticket-Assets\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SPSAssetClassBase\" EntityTypeNames=\"SPSComputerType SPSMonitorType SPSPrinterType SPSAssetTypeMobile SPSAssetTypeSIM SPSAssetTypeGenericPeripheralDevice SPSAssetTypeNetworkDevice SPSAssetType\" OrderBy=\"\" WhereExpression=\"\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"SysName\" SourceName=\"Sys-Name\" Type=\"string\" Cardinal=\"50\"/><Table-Column Name=\"Asset\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"AssetName\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Host\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"InventoryNumber\" Type=\"string\" Cardinal=\"100\"/><Table-Column Name=\"AssetAssignedUser\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"AssetSKUManName\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"AssetSKUModel\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"SerialNumber\" Type=\"string\" Cardinal=\"100\"/><Table-Column Name=\"CostCenter\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Location\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"OrganizationalUnit\" Type=\"string\" Cardinal=\"300\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-Services']"
                        );

                RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-QuickCalls']"
                        , "<Table Name=\"M42Wpm-Ticket-QuickCalls\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SPSQuickCallClassBase\" EntityTypeNames=\"SPSQuickCallType\" OrderBy=\"\" WhereExpression=\"Restricted = 0\" DataQueryName=\"C4IT - F4SD - Quick Calls\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"SysName\" SourceName=\"Sys-Name\" Type=\"string\" Cardinal=\"50\"/><Table-Column Name=\"Name\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Subject\" Type=\"string\" Cardinal=\"1024\"/><Table-Column Name=\"Priority\" Type=\"string\" Cardinal=\"100\"/><Table-Column Name=\"Responsible\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"ResponsibleRole\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Category\" SourceName=\"Category_Value\" Type=\"guid\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-Assets']"
                        );

                RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-Categories']"
                        , "<Table Name=\"M42Wpm-Ticket-Categories\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SPSScCategoryClassBase\" EntityTypeNames=\"SPSScCategoryType\" OrderBy=\"\" WhereExpression=\"Hidden = 0\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"SysName\" SourceName=\"Sys-Name\" Type=\"string\" Cardinal=\"50\"/><Table-Column Name=\"Name\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"parentValue\" SourceName=\"Parent_Value\" Type=\"guid\"/><Table-Column Name=\"parent\" SourceName=\"Parent\" Type=\"string\" Cardinal=\"300\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-QuickCalls']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-SLAs']"
                        , "<Table Name=\"M42Wpm-Ticket-SLAs\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SVCServiceLevelAgreementClassBase\" EntityTypeNames=\"\" OrderBy=\"\" WhereExpression=\"\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"SysName\" SourceName=\"Sys-Name\" Type=\"string\" Cardinal=\"50\"/><Table-Column Name=\"ContractNumber\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Summary\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Responsible\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"EffectiveDate\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"ExpireDate\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"State\" Type=\"string\" Cardinal=\"300\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-Categories']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-CloseCase-Services']"
                        , "<Table Name=\"M42Wpm-Ticket-CloseCase-Services\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SPSArticleClassBase\" EntityTypeNames=\"SPSArticleTypeService SPSArticleTypeGroup SVCServiceTypeBundle SVCServiceTypeConfigGroup\" OrderBy=\"\" WhereExpression=\"EXISTS(SUBQUERY(SVCServiceBookingClassBase AS B, B.ID, base.ID = B.Service AND B.ProvisioningStatus = 1 AND b.Uninstalled = 0 AND b.OrderType &lt;&gt; 20 AND B.Consumer.Accounts.T(SPSAccountClassAD).ADCn = '{0}'))\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"DisplayName\" SourceName=\"Sys-DisplayName\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Name\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"ServiceID\" SourceName=\"ArticleID\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Description\" Type=\"string\" Cardinal=\"300\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-SLAs']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-Roles']"
                        , "<Table Name=\"M42Wpm-Ticket-Roles\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SPSScRoleClassBase\" EntityTypeNames=\"SPSSecurityTypeRole\" OrderBy=\"\" WhereExpression=\"T(SPSSecurityClassRole).ShowInForwardAction = 1 AND T(SPSSecurityClassRole).Queue.ID IS NULL\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"Name\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Description\" Type=\"string\" Cardinal=\"300\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-CloseCase-Services']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42Tickets']"
                        , "Table[@Name='M42Wpm-Ticket-Persons']"
                        , "<Table Name=\"M42Wpm-Ticket-Persons\" Type=\"Selection\" Key=\"id\"><Matrix42-DataQueryItems-Template EntityClassName=\"SPSUserClassBase\" EntityTypeNames=\"\" OrderBy=\"\" WhereExpression=\"(EXISTS(SUBQUERY(SPSSecurityClassRole as Role, Role.ID, Role.ID='6167BCDB-9A7D-4736-B215-4218FB64360D' and Role.ShowInForwardAction = 1)) OR MemberOf.ShowInForwardAction = 1) AND MemberOf.Queue.ID IS NULL AND T(SPSCommonClassBase).State = 2023\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Id\" Type=\"guid\"/><Table-Column Name=\"SysObjectId\" SourceName=\"Sys-ObjectId\" Type=\"guid\"/><Table-Column Name=\"LastName\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"FirstName\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"MailAddress\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"BusinessPhone\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"MobilePhone\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"CostCenter\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Organization\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Location\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Position\" Type=\"string\" Cardinal=\"300\"/><Table-Column Name=\"Status\" Type=\"string\" Cardinal=\"300\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Ticket-Roles']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , ""
                        , "DataCluster[@Name='M42UserInfo']"
                        , "<DataCluster Name=\"M42UserInfo\" Origin=\"M42Wpm\" InformationClass=\"User\" Description=\"User related data from Matrix42 Workplace Management\"/>"
                        , "DataCluster[@Name='M42Tickets']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42UserInfo']"
                        , "Table[@Name='M42Wpm-User-NewTicket']"
                        , "<Table Name=\"M42Wpm-User-NewTicket\" Type=\"Static\" Key=\"id\"><Table-Columns><Table-Column Name=\"id\" Type=\"sid\"/><Table-Column Name=\"Link\" Type=\"string\" Cardinal =\"1024\"/><Table-Column Name=\"SubjectParameter\" Type=\"string\" Cardinal =\"128\"/><Table-Column Name=\"DescriptionParameter\" Type=\"string\" Cardinal =\"128\"/></Table-Columns></Table>"
                        , "-first-"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                       , "DataCluster[@Name='M42UserInfo']"
                       , "Table[@Name='M42Wpm-Pickup-TicketStatus']"
                       , "<Table Name=\"M42Wpm-Pickup-TicketStatus\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SPSCommonPickupObjectStatus\" FilteredGroup=\"7\" Sorting=\"byPosition\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/></Table-Columns></Table>"
                       , "Table[@Name='M42Wpm-User-NewTicket']"
                       );

                    RetVal |= DoXmlInsertElement(XmlRoot
                       , "DataCluster[@Name='M42UserInfo']"
                       , "Table[@Name='M42Wpm-Pickup-IncidentNotificationMode']"
                       , "<Table Name=\"M42Wpm-Pickup-IncidentNotificationMode\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SVMIncidentPickupSendNotifications\" Sorting=\"byName\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/></Table-Columns></Table>"
                       , "Table[@Name='M42Wpm-Pickup-TicketStatus']"
                       );

                    RetVal |= DoXmlInsertElement(XmlRoot
                       , "DataCluster[@Name='M42UserInfo']"
                       , "Table[@Name='M42Wpm-Pickup-TicketTypes']"
                       , "<Table Name=\"M42Wpm-Pickup-TicketTypes\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SPSTicketPickupType\" Sorting=\"byPosition\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/></Table-Columns></Table>"
                       , "Table[@Name='M42Wpm-Pickup-IncidentNotificationMode']"
                       );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42UserInfo']"
                        , "Table[@Name='M42Wpm-Pickup-ActivityImpact']"
                        , "<Table Name=\"M42Wpm-Pickup-ActivityImpact\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SVMActivityPickupImpact\" Sorting=\"byPosition\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Pickup-TicketTypes']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42UserInfo']"
                        , "Table[@Name='M42Wpm-Pickup-IncidentEntry']"
                        , "<Table Name=\"M42Wpm-Pickup-IncidentEntry\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SPSActivityIncidentPickupEntry\" Sorting=\"byPosition\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Pickup-ActivityImpact']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42UserInfo']"
                        , "Table[@Name='M42Wpm-Pickup-ActivityUrgency']"
                        , "<Table Name=\"M42Wpm-Pickup-ActivityUrgency\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SVMActivityPickupUrgency\" Sorting=\"byPosition\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Pickup-IncidentEntry']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42UserInfo']"
                        , "Table[@Name='M42Wpm-Pickup-ActivityErrorType']"
                        , "<Table Name=\"M42Wpm-Pickup-ActivityErrorType\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SVMActivityPickupErrorType\" Sorting=\"byPosition\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Pickup-ActivityUrgency']"
                        );

                    RetVal |= DoXmlInsertElement(XmlRoot
                        , "DataCluster[@Name='M42UserInfo']"
                        , "Table[@Name='M42Wpm-Pickup-ObjectStateReason']"
                        , "<Table Name=\"M42Wpm-Pickup-ObjectStateReason\" Type=\"Static\" Key=\"id\"><Matrix42-Pickup-Template PickupName=\"SPSCommonPickupObjectStateReason\" Sorting=\"byPosition\"/><Table-Columns><Table-Column Name=\"id\" SourceName=\"Value\" Type=\"int\"/><Table-Column Name=\"name\" SourceName=\"DisplayString\" Type=\"string\" Cardinal =\"150\"/><Table-Column Name=\"hidden\" SourceName =\"Hidden\" Type=\"boolean\"/><Table-Column Name=\"position\" SourceName =\"Position\" Type=\"int\"/><Table-Column Name=\"state\" SourceName =\"State\" Type=\"int\"/><Table-Column Name=\"stategroup\" SourceName =\"StateGroup\" Type=\"int\"/></Table-Columns></Table>"
                        , "Table[@Name='M42Wpm-Pickup-ActivityErrorType']"
                        );

                }

                // update table 'M42Wpm-Tickets' columns
                RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "<Table-Column Name=\"Urgency\" Type=\"string\" Cardinal=\"64\"/>", "AffectedUser");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "<Table-Column Name=\"UrgencyId\" Type=\"int\"/>", "Urgency");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "<Table-Column Name=\"Impact\" Type=\"string\" Cardinal=\"64\"/>", "UrgencyId");
                RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Tickets", "<Table-Column Name=\"ImpactId\" Type=\"int\"/>", "Impact");

                RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Ticket-QuickCalls", "<Table-Column Name=\"Category\" SourceName=\"Category_Value\" Type=\"guid\" />", "ResponsibleRole");

                var quickCallsCategoryColumn = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-QuickCalls']/Table-Columns/Table-Column[@Name='Category']") as XmlElement;
                if (quickCallsCategoryColumn != null)
                {
                    if (!string.Equals(quickCallsCategoryColumn.GetAttribute("SourceName"), "Category_Value", StringComparison.OrdinalIgnoreCase))
                    {
                        quickCallsCategoryColumn.SetAttribute("SourceName", "Category_Value");
                        RetVal = true;
                    }

                    if (!string.Equals(quickCallsCategoryColumn.GetAttribute("Type"), "guid", StringComparison.OrdinalIgnoreCase))
                    {
                        quickCallsCategoryColumn.SetAttribute("Type", "guid");
                        RetVal = true;
                    }
                }

                var quickCallsTemplate = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-QuickCalls']/Matrix42-DataQueryItems-Template") as XmlElement;
                if (quickCallsTemplate != null)
                {
                    if (!string.Equals(quickCallsTemplate.GetAttribute("DataQueryName"), "C4IT - F4SD - Quick Calls", StringComparison.OrdinalIgnoreCase))
                    {
                        quickCallsTemplate.SetAttribute("DataQueryName", "C4IT - F4SD - Quick Calls");
                        RetVal = true;
                    }
                }

                var categoriesColumns = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-Categories']/Table-Columns") as XmlElement;
                if (categoriesColumns != null)
                {
                    RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Ticket-Categories", "<Table-Column Name=\"parentValue\" SourceName=\"Parent_Value\" Type=\"guid\" />", "Name");
                    RetVal |= DoXmlInsertTableRow(XmlRoot, "M42Tickets", "M42Wpm-Ticket-Categories", "<Table-Column Name=\"parent\" SourceName=\"Parent\" Type=\"string\" Cardinal=\"300\" />", "parentValue");

                    var parentValueColumn = categoriesColumns.SelectSingleNode("Table-Column[@Name='parentValue']") as XmlElement;
                    if (parentValueColumn != null)
                    {
                        if (!string.Equals(parentValueColumn.GetAttribute("SourceName"), "Parent_Value", StringComparison.OrdinalIgnoreCase))
                        {
                            parentValueColumn.SetAttribute("SourceName", "Parent_Value");
                            RetVal = true;
                        }

                        if (!string.Equals(parentValueColumn.GetAttribute("Type"), "guid", StringComparison.OrdinalIgnoreCase))
                        {
                            parentValueColumn.SetAttribute("Type", "guid");
                            RetVal = true;
                        }
                    }

                    var parentColumn = categoriesColumns.SelectSingleNode("Table-Column[@Name='parent']") as XmlElement;
                    if (parentColumn != null)
                    {
                        if (!string.Equals(parentColumn.GetAttribute("SourceName"), "Parent", StringComparison.OrdinalIgnoreCase))
                        {
                            parentColumn.SetAttribute("SourceName", "Parent");
                            RetVal = true;
                        }

                        if (!string.Equals(parentColumn.GetAttribute("Type"), "string", StringComparison.OrdinalIgnoreCase))
                        {
                            parentColumn.SetAttribute("Type", "string");
                            RetVal = true;
                        }

                        if (!string.Equals(parentColumn.GetAttribute("Cardinal"), "300", StringComparison.OrdinalIgnoreCase))
                        {
                            parentColumn.SetAttribute("Cardinal", "300");
                            RetVal = true;
                        }
                    }
                }

                var categoriesTemplate = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-Categories']/Matrix42-DataQueryItems-Template") as XmlElement;
                if (categoriesTemplate != null)
                {
                    var whereExpression = categoriesTemplate.GetAttribute("WhereExpression");
                    var normalizedWhereExpression = Regex.Replace(whereExpression ?? string.Empty, @"\s+", string.Empty);
                    if (string.IsNullOrWhiteSpace(whereExpression)
                        || string.Equals(normalizedWhereExpression, "Hidden=false", StringComparison.OrdinalIgnoreCase))
                    {
                        categoriesTemplate.SetAttribute("WhereExpression", "Hidden = 0");
                        RetVal = true;
                    }
                }

                // correct the type attribute for the M42Wpm-Ticket-History table to Events                
                var M42JournalItemNode = XmlRoot.SelectSingleNode("DataCluster[@Name='M42Tickets']/Table[@Name='M42Wpm-Ticket-History' and @Type!='HistoryEvents']") as XmlElement;
                if (M42JournalItemNode != null)
                {
                    M42JournalItemNode.SetAttribute("Type", "HistoryEvents");
                    RetVal = true;
                }
            }

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

            return RetVal;
        }

        public static enumFasdValueType ResultingAggregationType(enumFasdValueType vt, eDataHistoryAggregationType at)
        {
            switch (at)
            {
                case eDataHistoryAggregationType.count:
                case eDataHistoryAggregationType.valuecount:
                    return enumFasdValueType.INT;
                case eDataHistoryAggregationType.latestTime:
                    return enumFasdValueType.DATETIME;
                case eDataHistoryAggregationType.sum:
                    if (vt == enumFasdValueType.VERSION)
                        return enumFasdValueType.STRING;
                    return vt;
            }
            return vt;
        }

        public static string GetSqlDataTypeFromHistoryType(enumFasdValueType type, int Cardinal, bool isBinaryType = false)
        {
            try
            {
                if (isBinaryType)
                {
                    switch (type)
                    {
                        case enumFasdValueType.VERSION:
                            return "[varbinary](10)";
                        case enumFasdValueType.MD5:
                            return "[binary](16)";
                        case enumFasdValueType.SID:
                            return "[varbinary](68)";
                        case enumFasdValueType.IPV4:
                            return "[binary](4)";
                        case enumFasdValueType.IPV6:
                            return "[binary](16)";
                    }
                    return null;
                }
                switch (type)
                {
                    case enumFasdValueType.STRING:
                        if (Cardinal <= 0 || Cardinal >= 4000)
                            return string.Format("[nvarchar](max)", Cardinal);
                        return string.Format("[nvarchar]({0})", Cardinal);
                    case enumFasdValueType.GUID:
                        return "[uniqueidentifier]";
                    case enumFasdValueType.INT:
                        return "[int]";
                    case enumFasdValueType.BIGINT:
                        return "[bigint]";
                    case enumFasdValueType.FLOAT:
                        return "[float]";
                    case enumFasdValueType.DATETIME:
                        return "[datetime]";
                    case enumFasdValueType.VERSION:
                        return "[nvarchar](50)";
                    case enumFasdValueType.MD5:
                        return "[nvarchar](50)";
                    case enumFasdValueType.SID:
                        return "[nvarchar](200)";
                    case enumFasdValueType.IPV4:
                        return "[nvarchar](50)";
                    case enumFasdValueType.IPV6:
                        return "[nvarchar](50)";
                    case enumFasdValueType.TEXT:
                        return "[ntext]";
                    case enumFasdValueType.BOOLEAN:
                        return "[tinyint]";
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

        public static bool CheckColumnCompatibility(cDataHistoryConfigColumnBase C1, cDataHistoryConfigColumnBase C2)
        {
            try
            {
                var strC1 = C1.SqlType;
                var strC2 = C2.SqlType;
                if (confUseBinValuesAsPrimaryKey && C1.SqlTypeBin != null && C2.SqlTypeBin != null)
                {
                    strC1 = C1.SqlTypeBin;
                    strC2 = C2.SqlTypeBin;
                }


                return strC1 == strC2;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return false;
        }

    }


    public class cDataHistoryConfigCluster : IConfigNodeNamed
    {
        public bool IsValid { get; protected set; } = false;
        public string Name { get; protected set; } = null;
        public string Description { get; protected set; } = null;

        [JsonIgnore]
        public cDataHistoryConfigClusters ParentConfig { get; protected set; } = null;

        public enumDataHistoryOrigin Origin { get; protected set; } = enumDataHistoryOrigin.Unknown;

        public enumFasdInformationClass InformationClass { get; protected set; } = enumFasdInformationClass.Unknown;

        public Dictionary<string, cDataHistoryConfigTable> Tables { get; protected set; } = null;

        internal cDataHistoryConfigCluster()
        {
        }

        internal cDataHistoryConfigCluster(XmlElement XNode, cDataHistoryConfigClusters Config, cXmlParser Parser)
        {
            try
            {
                if (Config == null)
                {
                    var E = new Exception("No valid configuration");
                    LogException(E, LogLevels.Fatal);
                    return;
                }

                ParentConfig = Config;

                Name = cXmlParser.GetStringFromXmlAttribute(XNode, "Name");
                if (string.IsNullOrEmpty(Name))
                {
                    Parser.AddMessage(XNode, "The <DataCluster> element has no valid Name attribute.", LogLevels.Warning);
                    return;
                }

                Origin = cXmlParser.GetEnumFromAttribute<enumDataHistoryOrigin>(XNode, "Origin", enumDataHistoryOrigin.Unknown);
                if (Origin == enumDataHistoryOrigin.Unknown)
                {
                    Parser.AddMessage(XNode, $"The <DataCluster> element with Name='{Name}' has no valid 'Type' attribute.", LogLevels.Error);
                    return;
                }

                InformationClass = cXmlParser.GetEnumFromAttribute<enumFasdInformationClass>(XNode, "InformationClass", enumFasdInformationClass.Unknown);
                if (InformationClass == enumFasdInformationClass.Unknown)
                {
                    Parser.AddMessage(XNode, $"The <DataCluster> element with Name='{Name}' has no valid 'InformationClass' attribute.", LogLevels.Error);
                    return;
                }

                Description = cXmlParser.GetStringFromXmlAttribute(XNode, "Description");

                Tables = cDataHistoryConfigTable.LoadFromXml(XNode, this, Parser);
                if (Tables == null)
                {
                    Parser.AddMessage(XNode, $"No valid tables found in <DataCluster> element with Name='{Name}'.", LogLevels.Warning);
                    return;
                }

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        internal static Dictionary<string, cDataHistoryConfigCluster> LoadFromXml(XmlElement XNode, cDataHistoryConfigClusters Config, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var XDataClusters = XNode.SelectNodes("DataCluster");
                if (XDataClusters == null || XDataClusters.Count <= 0)
                {
                    Parser.AddMessage(XNode, "The <DataCluster> element could not be found in the configuration.", LogLevels.Fatal);
                    return null;
                }

                Parser.EnterElement("DataCluster");
                var RetVal = new Dictionary<string, cDataHistoryConfigCluster>(XDataClusters.Count);
                try
                {
                    foreach (XmlNode XNode2 in XDataClusters)
                    {
                        if (!(XNode2 is XmlElement XDataCluster))
                            continue;

                        var DataCluster = new cDataHistoryConfigCluster(XDataCluster, Config, Parser);
                        if ((DataCluster != null) && (DataCluster.IsValid))
                        {
                            if (!RetVal.ContainsKey(DataCluster.Name))
                                RetVal.Add(DataCluster.Name, DataCluster);
                            else
                                Parser.AddMessage(XDataCluster, $"There's already an <DataCluster> element with Name='{DataCluster.Name}'", LogLevels.Warning);
                        }
                        else
                        {
                            var strN = "";
                            if (DataCluster != null)
                                strN = $" with Name='{DataCluster.Name}'";
                            Parser.AddMessage(XDataCluster, $"The <DataCluster> element{strN} is not valid.", LogLevels.Error);
                        }
                        Parser.SelectElementNext();
                    }
                }
                catch (Exception E)
                {
                    LogException(E);
                }
                finally
                {
                    Parser.LeaveElement("DataCluster");
                }

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

            return null;
        }
    }

    public class cDataHistoryConfigClusterMain : cDataHistoryConfigCluster
    {
        public readonly Dictionary<enumFasdInformationClass, cDataHistoryConfigMainTable> MainTables = null;

        internal cDataHistoryConfigClusterMain(XmlElement XRoot, cDataHistoryConfigClusters Config, cXmlParser Parser)
        {
            try
            {
                if (Config == null)
                {
                    var E = new Exception("No valid configuration");
                    LogException(E, LogLevels.Fatal);
                    return;
                }

                IsValid = true;
                Name = "Main";
                Origin = enumDataHistoryOrigin.Main;
                InformationClass = enumFasdInformationClass.Main;

                ParentConfig = Config;

                var XNode = XRoot.SelectSingleNode("InformationClasses") as XmlElement;
                if (XNode == null)
                {
                    Parser.AddMessage(XNode, $"No valid <InformationClasses> element found.", LogLevels.Error);
                    return;
                }

                Parser.EnterElement("InformationClasses");
                try
                {
                    Tables = cDataHistoryConfigTable.LoadFromXml(XNode, this, Parser);
                    if (Tables == null)
                    {
                        Parser.AddMessage(XNode, $"No valid tables found in <DataCluster> element with Name='{Name}'.", LogLevels.Warning);
                        return;
                    }
                }
                finally
                {
                    Parser.LeaveElement("InformationClasses");
                }

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

    }

    public class cDataHistoryConfigTable : IConfigNodeNamed
    {
        public bool IsValid { get; protected set; } = false;
        public string Name { get; protected set; } = null;
        public string SourceName { get; protected set; } = null;
        public string Description { get; protected set; } = null;

        public bool LateDelivery { get; set; } = false;
        public bool HasLateDelivery { get; private set; } = false;

        public string AutoCreateHistoryTableName { get; private set; } = null;

        public bool IsBuildInTable { get; private set; } = false;

        public eDataHistoryTableCached Cached { get; set; } = eDataHistoryTableCached.Default;

        public bool IsVirtual { get; set; } = false;

        [JsonIgnore]
        public cDataHistoryConfigCluster ParentCluster { get; private set; } = null;

        public eDataHistoryTableType Type { get; private set; } = eDataHistoryTableType.Unknown;

        [JsonIgnore]
        public readonly List<cDataHistoryConfigColumnBase> KeyColumns = null;
        public readonly List<string> KeyColumnNames = null;

        [JsonIgnore]
        public cDataHistoryConfigColumnBase EventTimeColumn { get; private set; } = null;
        public string EventTimeColumnName { get; private set; } = null;


        public readonly Dictionary<string, cDataHistoryConfigSubTable> SubTables = null;

        public readonly Dictionary<string, cDataHistoryConfigColumnBase> Columns = new Dictionary<string, cDataHistoryConfigColumnBase>();

        public readonly Dictionary<string, cDataHistoryConfigTableIndex> Indexes = new Dictionary<string, cDataHistoryConfigTableIndex>();

        public readonly Dictionary<string, cDataHistoryConfigTableReferenceInfo> References = new Dictionary<string, cDataHistoryConfigTableReferenceInfo>();

        public readonly bool NoTimeRestriction = false;

        public List<string> AlternateNames = null;

        [JsonIgnore]
        public bool HasScanId = false;

        internal cDataHistoryConfigTable()
        {
            KeyColumns = new List<cDataHistoryConfigColumnBase>();
            KeyColumnNames = new List<string>();
            SubTables = new Dictionary<string, cDataHistoryConfigSubTable>();
            Columns = new Dictionary<string, cDataHistoryConfigColumnBase>();
        }

        internal cDataHistoryConfigTable(XmlElement XNode, cDataHistoryConfigCluster Cluster, cXmlParser Parser)
        {
            try
            {
                if (Cluster == null)
                {
                    LogException(new Exception("no valid Cluster information delivered."));
                    return;
                }


                ParentCluster = Cluster;

                if (Cluster.Origin == enumDataHistoryOrigin.Main && XNode.Name == "InformationClass")
                    IsBuildInTable = true;

                if (Cluster.Origin == enumDataHistoryOrigin.F4sdAgent || Cluster.Origin == enumDataHistoryOrigin.M42Wpm || Cluster.Origin == enumDataHistoryOrigin.Intune || Cluster.Origin == enumDataHistoryOrigin.Citrix)
                    IsVirtual = true;

                Name = cXmlParser.GetStringFromXmlAttribute(XNode, "Name");
                if (string.IsNullOrEmpty(Name))
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> has no valid 'Name' attribute.", LogLevels.Warning);
                    return;
                }
                if (IsBuildInTable)
                    Name = "main-" + Name.ToLowerInvariant();

                SourceName = cXmlParser.GetStringFromXmlAttribute(XNode, "SourceName");
                if (string.IsNullOrEmpty(SourceName))
                    SourceName = Name;

                Cached = cXmlParser.GetEnumFromAttribute<eDataHistoryTableCached>(XNode, "Cached", eDataHistoryTableCached.Default);

                string strKeyColumnNames = cXmlParser.GetStringFromXmlAttribute(XNode, "Key");
                if (IsBuildInTable)
                    strKeyColumnNames = "id";
                else
                    strKeyColumnNames = cXmlParser.GetStringFromXmlAttribute(XNode, "Key");
                if (string.IsNullOrEmpty(strKeyColumnNames))
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid 'Key' attribute.", LogLevels.Warning);
                    return;
                }

                var arrKeyColumnNames = strKeyColumnNames.Split(' ');
                KeyColumnNames = new List<string>();
                foreach (var Entry in arrKeyColumnNames)
                {
                    var strName = Entry.Trim();
                    if (!string.IsNullOrEmpty(strName))
                        KeyColumnNames.Add(strName);
                }
                if (KeyColumnNames.Count == 0)
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' is not valid because of an empty 'Key' attribute.", LogLevels.Warning);
                    return;
                }

                if (IsBuildInTable)
                    Type = eDataHistoryTableType.Static;
                else
                    Type = cXmlParser.GetEnumFromAttribute<eDataHistoryTableType>(XNode, "Type", eDataHistoryTableType.Unknown);

                if (Type == eDataHistoryTableType.Unknown)
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid 'Type' attribute.", LogLevels.Warning);
                    return;
                }

                EventTimeColumnName = cXmlParser.GetStringFromXmlAttribute(XNode, "EventTimeCol");

                Description = cXmlParser.GetStringFromXmlAttribute(XNode, "Description");

                LateDelivery = cXmlParser.GetBoolFromXmlAttribute(XNode, "LateDelivery");

                var XNode2 = XNode.SelectSingleNode("AutoCreate-Historic-Table");
                if (XNode2 != null)
                {
                    Parser.EnterElement("AutoCreate-Historic-Table");
                    AutoCreateHistoryTableName = cXmlParser.GetStringFromXmlAttribute(XNode2, "Name");
                    Parser.LeaveElement("AutoCreate-Historic-Table");
                }
                if (XNode.Name == "JoinedTable")
                {
                    // we have a table definition with several subtables
                    var XSubTables = XNode.SelectNodes("SubTable");
                    if ((XSubTables == null) || XSubTables.Count == 0)
                    {
                        Parser.AddMessage(XNode, $"The element <{XNode.Name}> has no valid subtables.", LogLevels.Warning);
                        return;
                    }

                    SubTables = new Dictionary<string, cDataHistoryConfigSubTable>(XSubTables.Count);
                    Parser.EnterElement("SubTable");
                    try
                    {
                        foreach (XmlNode XEntry in XSubTables)
                        {
                            if (!(XEntry is XmlElement XSubTable))
                                continue;

                            var SubTable = new cDataHistoryConfigSubTable(XSubTable, this, Parser);
                            if ((SubTable == null) || !SubTable.IsValid || SubTables.ContainsKey(SubTable.Name))
                            {
                                Parser.AddMessage(XNode, $"The element <{XSubTable.Name}> is not valid.", LogLevels.Warning);
                            }
                            else
                                SubTables.Add(SubTable.Name, SubTable);

                            Parser.SelectElementNext();
                        }
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }
                    finally
                    {
                        Parser.LeaveElement("SubTable");
                    }

                }
                else
                {
                    // we have no explicit subtable definitions
                    var SubTable = new cDataHistoryConfigSubTable(XNode, this, Parser);
                    if ((SubTable == null) || !SubTable.IsValid)
                    {
                        Parser.AddMessage(XNode, $"The element <{XNode.Name}> is not valid.", LogLevels.Warning);
                        return;
                    }

                    SubTables = new Dictionary<string, cDataHistoryConfigSubTable>(1)
                    {
                        { SubTable.Name, SubTable }
                    };
                }
                if (SubTables.Count == 0)
                {
                    Parser.AddMessage(XNode, $"The table <{XNode.Name}> has no valid subtables.", LogLevels.Warning);
                    return;
                }

                // get the key columns
                KeyColumns = new List<cDataHistoryConfigColumnBase>();
                foreach (var ColName in KeyColumnNames)
                {
                    if (!Columns.TryGetValue(ColName, out var KeyColumn))
                    {
                        Parser.AddMessage(XNode, $"The key column name '{ColName}' within the element <{XNode.Name}> could not be found.", LogLevels.Error);
                        return;
                    }
                    KeyColumns.Add(KeyColumn);
                }

                // check if we have the key columns in each subtable with the same type
                foreach (var SubTable in SubTables.Values)
                {
                    HasLateDelivery |= SubTable.HasLateDelivery;

                    foreach (var KeyColumn in KeyColumns)
                    {
                        if (!SubTable.Columns.TryGetValue(KeyColumn.Name, out var Col))
                        {
                            Parser.AddMessage(XNode, $"The key column '{KeyColumn.Name}' within the element <{XNode.Name}> could not be found in subtable '{SubTable.Name}'.", LogLevels.Warning);
                            return;
                        }
                        if (Col.ValueType != KeyColumn.ValueType)
                        {
                            Parser.AddMessage(XNode, $"The value type of the key column '{KeyColumn.Name}' within the element <{XNode.Name}> is not compatible to the subtable '{SubTable.Name}'.", LogLevels.Warning);
                            return;
                        }

                        if (Col.Cardinal != KeyColumn.Cardinal)
                        {
                            Parser.AddMessage(XNode, $"The value cardinal of the key column '{KeyColumn.Name}' within the element <{XNode.Name}> is not compatible to the subtable '{SubTable.Name}'.", LogLevels.Warning);
                            return;
                        }
                    }
                }

                // get the event time column for event tables
                if (!string.IsNullOrEmpty(EventTimeColumnName))
                    if (Columns.TryGetValue(EventTimeColumnName, out var _ec) && _ec.ValueType == enumFasdValueType.DATETIME)
                        EventTimeColumn = _ec;

                if (Type == eDataHistoryTableType.Events || Type == eDataHistoryTableType.HistoryEvents)
                {
                    if (EventTimeColumn == null)
                    {
                        Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid 'EventTimeCol' attribute.", LogLevels.Warning);
                        return;
                    }

                }

                // get the alternatetable names
                var XAlternates = XNode.SelectNodes("AdditionalTableName");
                if (XAlternates != null && XAlternates.Count > 0)
                {
                    Parser.EnterElement("AdditionalTableName");
                    foreach (XmlNode XEntry in XAlternates)
                    {
                        if (!(XEntry is XmlElement XAlternate))
                            continue;

                        try
                        {
                            var _an = cXmlParser.GetStringFromXmlAttribute(XAlternate, "Name");
                            if (string.IsNullOrWhiteSpace(_an))
                                continue;

                            if (AlternateNames == null)
                                AlternateNames = new List<string>(XAlternates.Count);
                            if (!AlternateNames.Contains(_an))
                                AlternateNames.Add(_an);
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                        finally
                        {
                            Parser.SelectElementNext();
                        }
                    }
                    Parser.LeaveElement("AdditionalTableName");
                }

                // get the indexes
                var XIndexes = XNode.SelectNodes("Table-Index");
                if (XIndexes != null && XIndexes.Count > 0)
                {
                    Parser.EnterElement("Table-Index");
                    foreach (XmlNode XEntry in XIndexes)
                    {
                        if (!(XEntry is XmlElement XIndex))
                            continue;

                        try
                        {
                            var Index = new cDataHistoryConfigTableIndex
                            {
                                Name = XIndex.GetAttribute("Name")
                            };
                            if (string.IsNullOrEmpty(Index.Name))
                            {
                                Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> has no valid 'Name' attribute.", LogLevels.Warning);
                                continue;
                            }

                            Index.Unique = cXmlParser.GetBoolFromXmlAttribute(XIndex, "Unique");

                            var strColumns = XIndex.GetAttribute("Columns");
                            if (string.IsNullOrEmpty(strColumns))
                            {
                                Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' has no valid 'Columns' attribute.", LogLevels.Warning);
                                continue;
                            }

                            var arrCols = strColumns.Split(' ');
                            foreach (var strCol in arrCols)
                            {
                                var strCol2 = strCol.Trim();
                                if (strCol2 == "")
                                    continue;

                                if (!Columns.TryGetValue(strCol2, out var Col))
                                {
                                    Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' contains a non existing element '{strCol2}' within the 'Columns' attribute.", LogLevels.Warning);
                                    Index.Columns.Clear();
                                    break;
                                }

                                if (Index.Columns.Contains(Col))
                                {
                                    Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' contains a duplicated element '{strCol2}' within the 'Columns' attribute.", LogLevels.Warning);
                                    Index.Columns.Clear();
                                    break;
                                }

                                Index.Columns.Add(Col);
                            }

                            if (Columns.Count == 0)
                            {
                                Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' has an illegal or no valid entries in the 'Columns' attribute.", LogLevels.Warning);
                                continue;
                            }

                            if (Indexes.ContainsKey(Index.Name))
                            {
                                Parser.AddMessage(XIndex, $"The element <{XIndex.Name}> with Name='{Index.Name}' is duplicated.", LogLevels.Warning);
                                continue;
                            }

                            Indexes.Add(Index.Name, Index);
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                        finally
                        {
                            Parser.SelectElementNext();
                        }
                    }

                    Parser.LeaveElement("Table-Index");
                }

                // get the table references
                var XJoins = XNode.SelectNodes("Table-Reference");
                if (XJoins != null && XJoins.Count > 0)
                {
                    Parser.EnterElement("Table-Reference");
                    foreach (XmlNode XEntry in XJoins)
                    {
                        if (!(XEntry is XmlElement XJoin))
                            continue;

                        try
                        {
                            var Join = new cDataHistoryConfigTableReferenceInfo(XJoin, this, Parser);
                            if (!Join.IsValid)
                            {
                                var strWithName = "";
                                if (Join.ForeignTableName != null)
                                    strWithName = $"with Name='{Join.ForeignTableName}'";
                                Parser.AddMessage(XJoin, $"The <Table-Reference> element{strWithName} is not valid.", LogLevels.Warning);
                                continue;
                            }

                            if (References.ContainsKey(Join.ForeignTableName))
                            {
                                Parser.AddMessage(XJoin, $"The element <{XJoin.Name}> with Table='{Join.ForeignTableName}' within table '{Name}' is duplicated.", LogLevels.Warning);
                                continue;
                            }

                            References.Add(Join.ForeignTableName, Join);
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                        finally
                        {
                            Parser.SelectElementNext();
                        }
                    }

                    Parser.LeaveElement("Table-Reference");
                }

                // resolve the columns of the computations
                bool _IsValid = true;
                foreach (var Column in Columns.Values)
                {
                    if (!(Column is cDataHistoryConfigColumnComputation CP))
                        continue;

                    if (CP.Computation != null)
                        _IsValid &= CP.Computation.Resolve(Columns, Parser);
                }

                IsValid = _IsValid;
            }
            catch (Exception E)
            {
                IsValid = false;
                LogException(E);
            }
            finally
            {
            }
        }

        internal static Dictionary<string, cDataHistoryConfigTable> LoadFromXml(XmlElement XNode, cDataHistoryConfigCluster Cluster, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var XTableList = new List<XmlNodeList>(3);
                var XList = XNode.SelectNodes("Table");
                if (XList != null && XList.Count > 0)
                    XTableList.Add(XList);
                XList = XNode.SelectNodes("JoinedTable");
                if (XList != null && XList.Count > 0)
                    XTableList.Add(XList);
                XList = XNode.SelectNodes("InformationClass");
                if (XList != null && XList.Count > 0)
                    XTableList.Add(XList);

                if (XTableList.Count == 0)
                    return null;

                var RetVal = new Dictionary<string, cDataHistoryConfigTable>();
                foreach (XmlNodeList XTables in XTableList)
                {
                    Parser.EnterElement(XTables[0].Name);
                    foreach (XmlNode XNode2 in XTables)
                    {
                        if (!(XNode2 is XmlElement XTable))
                            continue;

                        var Table = new cDataHistoryConfigTable(XTable, Cluster, Parser);
                        if ((Table != null) && Table.IsValid)
                        {
                            if (!RetVal.ContainsKey(Table.Name))
                            {
                                if (!Cluster.ParentConfig.Tables.ContainsKey(Table.Name))
                                {
                                    RetVal.Add(Table.Name, Table);
                                    if (!Cluster.ParentConfig.Tables.ContainsKey(Table.Name))
                                        Cluster.ParentConfig.Tables.Add(Table.Name, Table);
                                }
                                else
                                {
                                    Parser.AddMessage(XTable, $"There's already an <{XTable.Name}> element with Name='{Table.Name}'", LogLevels.Warning);
                                }
                            }
                            else
                            {
                                Parser.AddMessage(XTable, $"There's already an <{XTable.Name}> element with Name='{Table.Name}'", LogLevels.Warning);
                            }

                            if (Table.AlternateNames != null)
                            {
                                foreach (var _an in Table.AlternateNames)
                                    Cluster.ParentConfig.AlternateTableNames[_an] = Table.Name;
                            }
                        }
                        else
                        {
                            var strN = "";
                            if (Table != null)
                                strN = $" with Name='{Table.Name}'";
                            Parser.AddMessage(XTable, $"The <{XTable.Name}> element{strN} is not valid.", LogLevels.Error);
                        }
                        Parser.SelectElementNext();
                    }
                    Parser.LeaveElement(XTables[0].Name);
                }

                if (RetVal.Count > 0)
                {
                    var _lstAuto = new List<cDataHistoryConfigTable>();
                    foreach (var Entry in RetVal.Values)
                    {
                        if (!string.IsNullOrEmpty(Entry.AutoCreateHistoryTableName))
                            _lstAuto.Add(Entry);
                    }
                    foreach (var Entry in _lstAuto)
                    {
                        var _tbl = cDataHistoryConfigTable.CreateHistoricTableFromDetails(Entry);
                        if (_tbl != null)
                        {
                            if (!RetVal.ContainsKey(_tbl.Name))
                            {
                                RetVal.Add(_tbl.Name, _tbl);
                                if (!Cluster.ParentConfig.Tables.ContainsKey(_tbl.Name))
                                    Cluster.ParentConfig.Tables.Add(_tbl.Name, _tbl);
                            }
                        }
                    }
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return null;
        }

        public override string ToString()
        {
            return Name;
        }

        public bool IsUniqueColumn(cDataHistoryConfigColumnBase Column)
        {
            if (KeyColumns.Contains(Column))
                return true;
            if (EventTimeColumn == Column)
                return true;

            foreach (var Index in Indexes.Values)
            {
                if (!Index.Unique)
                    continue;

                if (Index.Columns.Contains(Column))
                    return true;
            }

            return false;
        }

        public bool IsNullable(cDataHistoryConfigColumnBase Column)
        {
            return !IsUniqueColumn(Column);
        }

        internal bool ResolveReferences(Dictionary<string, cDataHistoryConfigTable> Tables, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var RetVal = true;
                var delList = new List<cDataHistoryConfigTableReferenceInfo>();
                foreach (var Ref in this.References.Values)
                {
                    RetVal &= Ref.Resolve(this, Tables, Parser);
                    if (!Ref.IsValid)
                        delList.Add(Ref);
                }

                foreach (var Ref in delList)
                    this.References.Remove(Ref.ForeignTableName);

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

            return false;
        }

        internal static cDataHistoryConfigTable CreateHistoricTableFromDetails(cDataHistoryConfigTable SourceTable)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var _newTable = new cDataHistoryConfigTable()
                {
                    IsValid = true,
                    Name = SourceTable.AutoCreateHistoryTableName,
                    SourceName = SourceTable.SourceName,
                    Description = SourceTable.Description,
                    LateDelivery = SourceTable.LateDelivery,
                    IsBuildInTable = SourceTable.IsBuildInTable,
                    Cached = SourceTable.Cached,
                    IsVirtual = SourceTable.IsVirtual,
                    ParentCluster = SourceTable.ParentCluster,
                    Type = eDataHistoryTableType.History
                };

                var _newSubTable = new cDataHistoryConfigSubTable(_newTable)
                {
                    IsValid = true,
                    Name = SourceTable.AutoCreateHistoryTableName,
                    Description = SourceTable.Description
                };
                _newTable.SubTables.Add(_newSubTable.Name, _newSubTable);

                foreach (var Entry in SourceTable.KeyColumns)
                {
                    var _colId = new cDataHistoryConfigColumn(Entry.Name, _newSubTable, Entry.ValueType, Entry.Cardinal)
                    {
                        SourceName = Entry.SourceName,
                        AggregationType = eDataHistoryAggregationType.first
                    };
                    _newSubTable.Columns.Add(_colId.Name, _colId);
                    _newTable.Columns.Add(_colId.Name, _colId);
                    _newTable.KeyColumns.Add(_colId);
                    _newTable.KeyColumnNames.Add(_colId.Name);
                }

                var _colTime = new cDataHistoryConfigColumn(SourceTable.EventTimeColumn.Name, _newSubTable, SourceTable.EventTimeColumn.ValueType, SourceTable.EventTimeColumn.Cardinal)
                {
                    SourceName = SourceTable.EventTimeColumn.SourceName,
                    AggregationType = eDataHistoryAggregationType.count
                };
                _newSubTable.Columns.Add(_colTime.Name, _colTime);
                _newTable.Columns.Add(_colTime.Name, _colTime);
                _newTable.EventTimeColumn = _colTime;
                _newTable.EventTimeColumnName = _colTime.Name;

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

            return null;
        }
    }

    public class cDataHistoryConfigSubTable : IConfigNodeNamed
    {
        public bool IsValid { get; internal set; } = false;
        public string Name { get; set; } = null;
        public string Description { get; internal set; } = null;
        public bool Aggregation { get; private set; } = false;
        public bool HasLateDelivery { get; private set; } = false;

        [JsonIgnore]
        public readonly cDataHistoryConfigTable ParentTable = null;

        public readonly cDataHistoryConfigQueryTemplate Template = null;

        public readonly Dictionary<string, cDataHistoryConfigColumnBase> Columns = null;

        internal cDataHistoryConfigSubTable(cDataHistoryConfigTable Table)
        {
            ParentTable = Table;
            Columns = new Dictionary<string, cDataHistoryConfigColumnBase>();
        }

        internal cDataHistoryConfigSubTable(XmlElement XNode, cDataHistoryConfigTable Table, cXmlParser Parser)
        {
            try
            {
                if (Table == null)
                {
                    LogException(new Exception("No valid Tabel parameter used."), LogLevels.Fatal);
                    return;
                }

                ParentTable = Table;

                Name = cXmlParser.GetStringFromXmlAttribute(XNode, "Name");
                if (string.IsNullOrEmpty(Name))
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> has no valid 'Name' attribute", LogLevels.Warning);
                    return;
                }

                Description = cXmlParser.GetStringFromXmlAttribute(XNode, "Description");

                Template = cDataHistoryConfigQueryTemplate.LoadFromXml(XNode, Parser);

                // ToDo: Check, if we need a template
                /*
                if (Template == null)
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid '<Template>' element", LogLevels.Warning);
                    return;
                }
                */

                Columns = cDataHistoryConfigColumnBase.LoadFromXml(XNode, this, Parser);
                if (Columns == null || Columns.Count == 0)
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{Name}' has no valid columns", LogLevels.Warning);
                    return;
                }

                if (Table.Name == "agnt-computer-event-string")
                {

                }
                foreach (var Col in Columns.Values)
                {
                    HasLateDelivery |= Col.LateDelivery;

                    if (Col is cDataHistoryConfigColumn _C && _C.AggregationType != eDataHistoryAggregationType.Unknown)
                        Aggregation = true;
                }
                if (Table.IsBuildInTable && !Columns.ContainsKey("id"))
                {
                    var _colId = new cDataHistoryConfigColumn("id", this, enumFasdValueType.GUID);
                    Columns.Add("id", _colId);
                    if (!Table.Columns.ContainsKey("id"))
                        Table.Columns.Add("id", _colId);
                }

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
            }
        }

    }

    public abstract class cDataHistoryConfigColumnBase : cConfigNodeNamed
    {
        public readonly enumFasdValueType ValueType = enumFasdValueType.Unknown;
        
        public readonly int Cardinal = -1;

        public string SqlType = null;
        public string SqlTypeBin = null;
        public string NameBin = null;

        public string SourceName = null;

        public readonly bool LateDelivery = false;

        public string SourceTable = null;

        public bool IsWritable { get; private set; } = false;

        [JsonIgnore]
        public readonly cDataHistoryConfigSubTable ParentTable = null;

        internal cDataHistoryConfigColumnBase(string Name, cDataHistoryConfigSubTable SubTable, enumFasdValueType Type, int Cardinal = -1) : base(Name)
        {
            ParentTable = SubTable;
            SourceName = Name;
            ValueType = Type;
            SqlType = cDataHistoryConfigClusters.GetSqlDataTypeFromHistoryType(ValueType, Cardinal, false);
            SqlTypeBin = cDataHistoryConfigClusters.GetSqlDataTypeFromHistoryType(ValueType, Cardinal, true);
        }

        internal cDataHistoryConfigColumnBase(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                IsValid = false;

                if (ParentTable == null)
                {
                    LogException(new Exception("No valid ParentTable delivered."));
                    return;
                }

                this.ParentTable = ParentTable;

                NameBin = Name;

                ValueType = cXmlParser.GetEnumFromAttribute<enumFasdValueType>(XNode, "Type", enumFasdValueType.Unknown);
                if (ValueType == enumFasdValueType.Unknown)
                {
                    Parser.AddInvalidAttribute(XNode, Name, "Type");
                    return;
                }

                Cardinal = cXmlParser.GetIntegerFromXmlAttribute(XNode, "Cardinal", -1);

                LateDelivery = cXmlParser.GetBoolFromXmlAttribute(XNode, "LateDelivery");

                IsWritable = cXmlParser.GetBoolFromXmlAttribute(XNode, "IsWritable");

                SqlType = cDataHistoryConfigClusters.GetSqlDataTypeFromHistoryType(ValueType, Cardinal, false);
                if (SqlType == null)
                {
                    Parser.AddMessage(XNode, $"The type '{ValueType}' at element <{XNode.Name}> with Name='{Name}' has no valid corresponding sql type.", LogLevels.Fatal);
                    return;
                }

                SqlTypeBin = cDataHistoryConfigClusters.GetSqlDataTypeFromHistoryType(ValueType, Cardinal, true);
                if (!string.IsNullOrEmpty(SqlTypeBin))
                    NameBin = Name + "_bin";


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

        static internal Dictionary<string, cDataHistoryConfigColumnBase> LoadFromXml(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                XmlElement XElements = XNode;

                if (!ParentTable.ParentTable.IsBuildInTable)
                {
                    var XList2 = XNode.SelectSingleNode("Table-Columns");

                    if (!(XList2 is XmlElement XList))
                    {
                        Parser.AddMessage(XNode, $"Illegal XML node 'Table-Columns' within element <{XNode.Name}> in table '{ParentTable.Name}' found.", LogLevels.Warning);
                        return null;
                    }
                    XElements = XList;

                    Parser.EnterElement("Table-Columns");
                }

                var RetVal = new Dictionary<string, cDataHistoryConfigColumnBase>();
                try
                {
                    var XColumns = XElements.ChildNodes;
                    if (XColumns == null || XColumns.Count == 0)
                    {
                        Parser.AddMessage(XElements, $"No valid elements found within element <{XElements.Name}> in table '{ParentTable.Name}'.", LogLevels.Warning);
                        return null;
                    }



                    var ColumnGroups = XColumns.Cast<XmlNode>().Where(node => node is XmlElement).GroupBy(node => node.Name);

                    foreach (var ColGroup in ColumnGroups)
                    {
                        Parser.EnterElement(ColGroup.Key);
                        foreach (XmlElement XColumn in ColGroup)
                        {
                            cDataHistoryConfigColumnBase Column = null;
                            switch (ColGroup.Key)
                            {
                                case "Table-Column":
                                case "InformationClass-Column":
                                    Column = new cDataHistoryConfigColumn(XColumn, ParentTable, Parser);
                                    break;
                                case "Table-Column-NxtCompute":
                                    Column = new cDataHistoryConfigColumnNxtCompute(XColumn, ParentTable, Parser);
                                    break;
                                case "Table-Column-NxtCategory":
                                    Column = new cDataHistoryConfigColumnNxtCategory(XColumn, ParentTable, Parser);
                                    break;
                                case "Table-Column-NxtScore":
                                    Column = new cDataHistoryConfigColumnNxtScore(XColumn, ParentTable, Parser);
                                    break;
                                case "Table-Column-NxtAction":
                                    Column = new cDataHistoryConfigColumnNxtAction(XColumn, ParentTable, Parser);
                                    break;
                                case "Table-Column-NxtCampaign":
                                    Column = new cDataHistoryConfigColumnNxtCampaign(XColumn, ParentTable, Parser);
                                    break;
                                case "Table-Column-Computation":
                                    Column = new cDataHistoryConfigColumnComputation(XColumn, ParentTable, Parser);
                                    break;
                                default:
                                    continue;
                            }

                            var pTable = ParentTable.ParentTable;
                            if ((Column != null) && Column.IsValid)
                            {
                                if (!RetVal.ContainsKey(Column.Name))
                                {
                                    if (pTable.KeyColumnNames.Contains(Column.Name) || !pTable.Columns.ContainsKey(Column.Name))
                                    {
                                        RetVal.Add(Column.Name, Column);
                                        if (!pTable.Columns.ContainsKey(Column.Name))
                                            pTable.Columns.Add(Column.Name, Column);
                                    }
                                    else
                                    {
                                        Parser.AddMessage(XColumn, $"There's already an <{XColumn.Name}> element with Name='{Column.Name}'", LogLevels.Warning);
                                    }
                                }
                                else
                                {
                                    Parser.AddMessage(XColumn, $"There's already an <{XColumn.Name}> element with Name='{Column.Name}'", LogLevels.Warning);
                                }
                            }
                            else
                            {
                                var strN = "";
                                if (Column != null)
                                    strN = $" with Name='{Column.Name}'";
                                Parser.AddMessage(XColumn, $"The <{XColumn.Name}> element{strN} is not valid.", LogLevels.Error);
                            }
                            Parser.SelectElementNext();
                        }
                        Parser.LeaveElement(ColGroup.Key);
                    }

                }
                catch (Exception E)
                {
                    LogException(E);
                }
                finally
                {
                    if (!ParentTable.ParentTable.IsBuildInTable)
                        Parser.LeaveElement("Table-Columns");
                }

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

            return null;
        }
    }

    public class cDataHistoryConfigColumn : cDataHistoryConfigColumnBase
    {
        public eDataHistoryQueryType SourceType = eDataHistoryQueryType.Query;

        public string SourceJsonField = null;
        public int SourceJsonColumn = -1;

        public eDataHistoryAggregationType AggregationType = eDataHistoryAggregationType.Unknown;

        public readonly bool FillEmptyEntries = false;

        internal cDataHistoryConfigColumn(string Name, cDataHistoryConfigSubTable SubTable, enumFasdValueType Type, int Cardinal = -1) : base(Name, SubTable, Type, Cardinal)
        {
        }

        internal cDataHistoryConfigColumn(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, ParentTable, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                SourceName = cXmlParser.GetStringFromXmlAttribute(XNode, "SourceName", Name);
                SourceTable = cXmlParser.GetStringFromXmlAttribute(XNode, "SourceTable", null);
                SourceType = cXmlParser.GetEnumFromAttribute<eDataHistoryQueryType>(XNode, "SourceType", eDataHistoryQueryType.Query);
                SourceJsonField = cXmlParser.GetStringFromXmlAttribute(XNode, "SourceJsonField", null);
                SourceJsonColumn = cXmlParser.GetIntegerFromXmlAttribute(XNode, "SourceJsonColumn", -1);
                AggregationType = cXmlParser.GetEnumFromAttribute<eDataHistoryAggregationType>(XNode, "Aggregation", eDataHistoryAggregationType.Unknown);
                FillEmptyEntries = cXmlParser.GetBoolFromXmlAttribute(XNode, "FillEmptyEntries");

                if (AggregationType != eDataHistoryAggregationType.Unknown)
                {
                    var valType = cDataHistoryConfigClusters.ResultingAggregationType(this.ValueType, this.AggregationType);
                    SqlType = cDataHistoryConfigClusters.GetSqlDataTypeFromHistoryType(valType, Cardinal, false);
                    if (SqlType == null)
                    {
                        Parser.AddMessage(XNode, $"The type '{ValueType}' at element <{XNode.Name}> with Name='{Name}' has no valid corresponding sql type.", LogLevels.Fatal);
                        return;
                    }

                    SqlTypeBin = cDataHistoryConfigClusters.GetSqlDataTypeFromHistoryType(valType, Cardinal, true);
                    if (!string.IsNullOrEmpty(SqlTypeBin))
                        NameBin = Name + "_bin";

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

        }
    }

    public class cDataHistoryConfigColumnNxtCompute : cDataHistoryConfigColumn
    {
        internal cDataHistoryConfigColumnNxtCompute(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, ParentTable, Parser)
        {
        }
    }

    public class cDataHistoryConfigColumnNxtCategory : cDataHistoryConfigColumn
    {
        internal cDataHistoryConfigColumnNxtCategory(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, ParentTable, Parser)
        {
        }
    }

    public class cDataHistoryConfigColumnNxtScore : cDataHistoryConfigColumnBase
    {
        public readonly string ScoreName = null;
        public readonly string ValueName = null;
        public readonly Guid ScoreID = Guid.Empty;
        public readonly Guid ValueID = Guid.Empty;
        public readonly bool IsPayload = false;

        internal cDataHistoryConfigColumnNxtScore(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, ParentTable, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                IsValid = false;
                ScoreName = cXmlParser.GetStringFromXmlAttribute(XNode, "ScoreName");
                if (string.IsNullOrEmpty(ScoreName))
                {
                    Parser.AddInvalidAttribute(XNode, Name, "ScoreName");
                    return;
                }

                ValueName = cXmlParser.GetStringFromXmlAttribute(XNode, "ValueName");
                if (string.IsNullOrEmpty(ValueName))
                {
                    Parser.AddInvalidAttribute(XNode, Name, "ValueName");
                    return;
                }

                ScoreID = cXmlParser.GetGuidFromXmlAttribute(XNode, "ScoreID");
                ValueID = cXmlParser.GetGuidFromXmlAttribute(XNode, "ValueID");
                IsPayload = cXmlParser.GetBoolFromXmlAttribute(XNode, "Payload");

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

    public class cDataHistoryConfigColumnNxtAction : cDataHistoryConfigColumnBase
    {
        public readonly string ActionName = null;
        public readonly string ValueName = null;
        public readonly Guid ActionID = Guid.Empty;
        public readonly Guid ValueID = Guid.Empty;

        internal cDataHistoryConfigColumnNxtAction(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, ParentTable, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                IsValid = false;
                ActionName = cXmlParser.GetStringFromXmlAttribute(XNode, "ActionName");
                if (string.IsNullOrEmpty(ActionName))
                {
                    Parser.AddInvalidAttribute(XNode, Name, "ActionName");
                    return;
                }

                ValueName = cXmlParser.GetStringFromXmlAttribute(XNode, "ValueName");
                if (string.IsNullOrEmpty(ValueName))
                {
                    Parser.AddInvalidAttribute(XNode, Name, "ValueName");
                    return;
                }

                ActionID = cXmlParser.GetGuidFromXmlAttribute(XNode, "ActionID");
                ValueID = cXmlParser.GetGuidFromXmlAttribute(XNode, "ValueID");

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

        }
    }

    public class cDataHistoryConfigColumnNxtCampaign : cDataHistoryConfigColumnBase
    {
        public readonly string CampaignName = null;
        public readonly string ValueName = null;
        public readonly Guid CampaignID = Guid.Empty;
        public readonly int ValueID = -1;
        public readonly bool AsText = false;

        internal cDataHistoryConfigColumnNxtCampaign(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, ParentTable, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                IsValid = false;
                CampaignName = cXmlParser.GetStringFromXmlAttribute(XNode, "CampaignName");
                if (string.IsNullOrEmpty(CampaignName))
                {
                    Parser.AddInvalidAttribute(XNode, Name, "CampaignName");
                    return;
                }

                ValueName = cXmlParser.GetStringFromXmlAttribute(XNode, "ValueName");
                if (string.IsNullOrEmpty(ValueName))
                {
                    Parser.AddInvalidAttribute(XNode, Name, "ValueName");
                    return;
                }

                CampaignID = cXmlParser.GetGuidFromXmlAttribute(XNode, "CampaignID");
                ValueID = cXmlParser.GetIntegerFromXmlAttribute(XNode, "ValueID");
                AsText = cXmlParser.GetBoolFromXmlAttribute(XNode, "AsText");

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

        }
    }

    public class cDataHistoryConfigColumnComputation : cDataHistoryConfigColumnBase
    {
        public cDataHistoryConfigComputationBase Computation { get; set; } = null;

        internal cDataHistoryConfigColumnComputation(XmlElement XNode, cDataHistoryConfigSubTable ParentTable, cXmlParser Parser) :
            base(XNode, ParentTable, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                IsValid = false;

                var XList = XNode.SelectNodes("*");
                foreach (XmlNode XNode2 in XList)
                {
                    if (!(XNode2 is XmlElement XEntry))
                        continue;

                    cDataHistoryConfigComputationBase Co = null;
                    switch (XNode2.Name)
                    {
                        case "Computation-LinearTransform":
                            Parser.EnterElement("Computation-LinearTransform");
                            Co = new cDataHistoryConfigComputationLinearTransform(XEntry, Parser);
                            Parser.LeaveElement("Computation-LinearTransform");
                            break;
                        case "Computation-RegEx":
                            Parser.EnterElement("Computation-RegEx");
                            Co = new cDataHistoryConfigComputationRegEx(XEntry, Parser);
                            Parser.LeaveElement("Computation-RegEx");
                            break;
                    }
                    if (Co != null && Co.IsValid)
                    {
                        if (Computation != null)
                        {
                            Parser.AddMessage(XNode, $"The <{XNode.Name}> element with Name='{Name}' has more than one valid computation sub-node.", LogLevels.Error);
                            return;
                        }
                        Computation = Co;
                    }
                }
                if (Computation == null)
                {
                    Parser.AddMessage(XNode, $"The <{XNode.Name}> element with Name='{Name}' has no valid computation sub-node.", LogLevels.Error);
                    return;
                }

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

        }
    }

    public abstract class cDataHistoryConfigComputationBase : IConfigNodeValidation
    {
        public bool IsValid { get; internal set; } = false;

        public List<string> ColumnNames { get; internal set; } = new List<string>();

        [JsonIgnore]
        public List<cDataHistoryConfigColumnBase> Columns = new List<cDataHistoryConfigColumnBase>();

        [JsonIgnore]
        private readonly int XLine = 0;
        [JsonIgnore]
        private readonly int XCol = 0;
        [JsonIgnore]
        private readonly string NodeName;

        internal cDataHistoryConfigComputationBase(XmlElement XNode, cXmlParser Parser)
        {
            XLine = Parser.currentNode.Line;
            XCol = Parser.currentNode.Column;
            NodeName = XNode.Name;
        }

        internal bool Resolve(Dictionary<string, cDataHistoryConfigColumnBase> TableColumns, cXmlParser Parser)
        {
            try
            {
                bool _IsValid = true;
                foreach (var Entry in ColumnNames)
                {
                    if (!TableColumns.TryGetValue(Entry, out var Column))
                    {
                        Parser.AddMessage(XLine, XCol, $"The column name '{Entry}' of the element '<{NodeName}>' could not be resolved.", LogLevels.Error);
                        _IsValid = false;
                        continue;
                    }
                    Columns.Add(Column);
                }
                return _IsValid;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return false;
        }

        public abstract object Compute(object[] Values);

        public static double? GetDouble(object o, double? Default)
        {
            double? RetVal = Default;
            try
            {
                if (o is int @int)
                    RetVal = Convert.ToDouble(@int);
                else if (o is double @double)
                    RetVal = @double;
                else if (o is string @string)
                {
                    if (double.TryParse(@string, NumberStyles.Any, CultureInfo.GetCultureInfo("en-US").NumberFormat, out double R))
                        RetVal = R;
                    else
                        RetVal = Default;
                }
            }
            catch { }

            return RetVal;
        }

    }

    public class cDataHistoryConfigComputationLinearTransform : cDataHistoryConfigComputationBase
    {
        public double m { get; internal set; } = 1.0f;
        public double b { get; internal set; } = 0.0f;

        internal cDataHistoryConfigComputationLinearTransform(XmlElement XNode, cXmlParser Parser) :
            base(XNode, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            IsValid = false;
            try
            {
                var Column = cXmlParser.GetStringFromXmlAttribute(XNode, "Column");
                if (string.IsNullOrWhiteSpace(Column))
                {
                    Parser.AddMessage(XNode, $"The <{XNode.Name}> element has no valid 'column' attribute", LogLevels.Warning);
                    return;
                }
                ColumnNames.Add(Column);

                m = cXmlParser.GetDoubleFromXmlAttribute(XNode, "m", 1.0);
                b = cXmlParser.GetDoubleFromXmlAttribute(XNode, "b", 0.0);

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

        public override object Compute(object[] Values)
        {
            if (Values.Length != 1)
                return null;

            double? V = GetDouble(Values[0], null);
            if (V == null)
                return null;

            return m * V + b;
        }
    }

    public class cDataHistoryConfigComputationRegEx : cDataHistoryConfigComputationBase
    {
        public string RegExFilter { get; internal set; } = null;
        public string Replacement { get; internal set; } = null;
        public bool AutoTrim { get; internal set; } = false;

        internal cDataHistoryConfigComputationRegEx(XmlElement XNode, cXmlParser Parser) :
            base(XNode, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            IsValid = false;
            try
            {
                var Column = cXmlParser.GetStringFromXmlAttribute(XNode, "Column");
                if (string.IsNullOrWhiteSpace(Column))
                {
                    Parser.AddMessage(XNode, $"The <{XNode.Name}> element has no valid 'column' attribute", LogLevels.Warning);
                    return;
                }
                ColumnNames.Add(Column);

                RegExFilter = cXmlParser.GetStringFromXmlAttribute(XNode, "RegExFilter");
                if (string.IsNullOrWhiteSpace(RegExFilter))
                {
                    Parser.AddInvalidAttribute(XNode, null, "RegExFilter");
                    return;
                }

                Replacement = cXmlParser.GetStringFromXmlAttribute(XNode, "Replacement");

                AutoTrim = cXmlParser.GetBoolFromXmlAttribute(XNode, "AutoTrim");

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

        public override object Compute(object[] Values)
        {
            try
            {
                if (Values.Length != 1)
                    return null;

                if (Values[0] == null)
                    return null;

                var RetVal = Values[0].ToString();
                if (string.IsNullOrEmpty(RetVal))
                    return RetVal;

                if (!string.IsNullOrEmpty(RegExFilter))
                {
                    if (!string.IsNullOrEmpty(Replacement))
                        try
                        {
                            RetVal = Regex.Replace(RetVal, RegExFilter, Replacement, RegexOptions.CultureInvariant);
                        }
                        catch (Exception E)
                        {
                            cLogManager.DefaultLogger.LogException(E);
                        }
                    else
                        try
                        {
                            var m = Regex.Match(RetVal, RegExFilter, RegexOptions.CultureInvariant);
                            RetVal = m.Value;
                        }
                        catch (Exception E)
                        {
                            cLogManager.DefaultLogger.LogException(E);
                        }

                }


                if (AutoTrim)
                    RetVal = RetVal.Trim();

                return RetVal;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                return null;
            }

        }

    }


    public abstract class cDataHistoryConfigQueryTemplate : IConfigNodeValidation
    {
        public bool IsValid { get; protected set; } = false;

        public string Query { get; private set; } = null;

        internal cDataHistoryConfigQueryTemplate(XmlElement XNode, bool Compress, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var XContent = XNode.SelectSingleNode("text()")?.Value;
                if (!string.IsNullOrEmpty(XContent))
                {
                    Query = XContent;
                    if (Compress)
                    {
                        Query = Query.Replace("\r\n", " ");
                        Query = Query.Replace("\r", " ");
                        Query = Query.Replace("\n", " ");
                        Query = Query.Replace("\t", " ");
                        while (true)
                        {
                            var Q = Query.Replace("  ", " ");
                            if (Q == Query)
                                break;
                            Query = Q;
                        }
                        Query = Query.Trim();
                    }
                }
                IsValid = true;
            }
            catch (Exception E)
            {
                Parser.AddMessage(XNode, $"The text content for element <{XNode.Name} is invalid.>", LogLevels.Warning);
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        internal static cDataHistoryConfigQueryTemplate LoadFromXml(XmlElement XNode, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                cDataHistoryConfigQueryTemplate Template = cDataHistoryConfigQueryTemplateNxql.LoadFromXml(XNode, Parser);
                if (Template?.IsValid == true)
                    return Template;

                Template = new cDataHistoryConfigQueryTemplateM42Pickup(XNode, Parser);
                if (Template?.IsValid == true)
                    return Template;

                Template = new cDataHistoryConfigQueryTemplateM42DataQueryItems(XNode, Parser);
                if (Template?.IsValid == true)
                    return Template;

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

            return null;
        }
    }

    public class cDataHistoryConfigQueryTemplateNxql : cDataHistoryConfigQueryTemplate
    {
        public readonly uint Limit = 4294967295;

        internal cDataHistoryConfigQueryTemplateNxql(XmlElement XNode, cXmlParser Parser) :
            base(XNode, true, Parser)
        {
            if (!IsValid)
                return;

            Limit = cXmlParser.GetUnsignedIntegerFromXmlAttribute(XNode, "Limit", Limit);
            IsValid = true;
        }

        new internal static cDataHistoryConfigQueryTemplateNxql LoadFromXml(XmlElement XNode, cXmlParser Parser)
        {

            try
            {
                if (XNode.SelectSingleNode("Nexthink-NXQL-Template") is XmlElement XNode2)
                {
                    Parser.EnterElement("Nexthink-NXQL-Template");
                    var RetVal = new cDataHistoryConfigQueryTemplateNxql(XNode2, Parser);
                    Parser.LeaveElement("Nexthink-NXQL-Template");
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
            }
            return null;
        }
    }

    public class cDataHistoryConfigQueryTemplateM42Pickup : cDataHistoryConfigQueryTemplate
    {
        public enum enumSorting { unknown = 0, byPosition, byName };

        public string PickupName { get; private set; } = null;
        public int FilteredGroup { get; private set; } = -1;
        public enumSorting Sorting { get; private set; } = enumSorting.unknown;

        internal cDataHistoryConfigQueryTemplateM42Pickup(XmlElement XNode, cXmlParser Parser) :
            base(XNode, true, Parser)
        {
            if (!IsValid)
                return;
            IsValid = false;

            if (XNode.SelectSingleNode("Matrix42-Pickup-Template") is XmlElement XNode2)
            {
                Parser.EnterElement("Matrix42-Pickup-Template");

                PickupName = cXmlParser.GetStringFromXmlAttribute(XNode2, "PickupName");
                if (string.IsNullOrEmpty(PickupName))
                    return;

                FilteredGroup = cXmlParser.GetIntegerFromXmlAttribute(XNode2, "FilteredGroup", -1);
                Sorting = cXmlParser.GetEnumFromAttribute<enumSorting>(XNode2, "Sorting", enumSorting.unknown);

                Parser.LeaveElement("Matrix42-Pickup-Template");

                IsValid = true;
            }
        }

    }

    public class cDataHistoryConfigQueryTemplateM42DataQueryItems : cDataHistoryConfigQueryTemplate
    {
        public string EntityClassName { get; private set; } = null;
        public List<string> EntityTypeNames { get; private set; } = null;
        public string OrderBy { get; private set; } = null;
        public string WhereExpression { get; private set; } = null;
        public string DataQueryName { get; private set; } = null;


        internal cDataHistoryConfigQueryTemplateM42DataQueryItems(XmlElement XNode, cXmlParser Parser) :
            base(XNode, true, Parser)
        {
            if (!IsValid)
                return;
            IsValid = false;

            if (XNode.SelectSingleNode("Matrix42-DataQueryItems-Template") is XmlElement XNode2)
            {
                Parser.EnterElement("Matrix42-DataQueryItems-Template");

                EntityClassName = cXmlParser.GetStringFromXmlAttribute(XNode2, "EntityClassName");
                if (string.IsNullOrEmpty(EntityClassName))
                    return;

                var strEntityTypeNames = cXmlParser.GetStringFromXmlAttribute(XNode2, "EntityTypeNames");
                if (string.IsNullOrEmpty(EntityClassName))
                    return;
                var arrEntityTypeNames = strEntityTypeNames.Split(' ');
                if (arrEntityTypeNames.Length <= 0)
                    return;
                EntityTypeNames = new List<string>(arrEntityTypeNames.Length);
                foreach (var _e in arrEntityTypeNames)
                {
                    var _e2 = _e.Trim();
                    if (!string.IsNullOrEmpty(_e2))
                        EntityTypeNames.Add(_e2);
                }

                OrderBy = cXmlParser.GetStringFromXmlAttribute(XNode2, "OrderBy");
                WhereExpression = cXmlParser.GetStringFromXmlAttribute(XNode2, "WhereExpression");
                DataQueryName = cXmlParser.GetStringFromXmlAttribute(XNode2, "DataQueryName");

                Parser.LeaveElement("Matrix42-DataQueryItems-Template");

                IsValid = true;
            }
        }

    }


    public class cDataHistoryConfigTableIndex
    {
        public string Name;
        public bool Unique = false;
        public List<cDataHistoryConfigColumnBase> Columns = new List<cDataHistoryConfigColumnBase>();
    }

    public class cDataHistoryConfigTableReferenceInfo : IConfigNodeValidation
    {
        public bool IsValid { get; private set; } = false;
        public string ForeignTableName { get; private set; } = null;
        public cDataHistoryConfigTable ForeignTable { get; private set; } = null;

        public string ForeignIndexName { get; private set; } = null;
        public cDataHistoryConfigTableIndex ReferencedIndex { get; private set; } = null;

        [JsonIgnore]
        private readonly int XLine = 0;
        [JsonIgnore]
        private readonly int XCol = 0;

        public List<cDataHistoryConfigColumnBase> Columns { get; private set; } = new List<cDataHistoryConfigColumnBase>();
        public List<cDataHistoryConfigColumnBase> ReferencedColumns { get; private set; } = new List<cDataHistoryConfigColumnBase>();

        internal cDataHistoryConfigTableReferenceInfo(XmlElement XNode, cDataHistoryConfigTable Table, cXmlParser Parser)
        {
            try
            {
                XLine = Parser.currentNode.Line;
                XCol = Parser.currentNode.Column;
                ForeignTableName = XNode.GetAttribute("ForeignTable");
                if (string.IsNullOrEmpty(ForeignTableName))
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> has no valid 'Table' attribute.", LogLevels.Warning);
                    return;
                }

                var strReference = XNode.GetAttribute("Columns");
                if (string.IsNullOrEmpty(strReference))
                {
                    Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{ForeignTableName}' has no valid 'Reference' attribute.", LogLevels.Warning);
                    return;
                }

                var arrReference = strReference.Split(' ');
                Columns.Clear();
                foreach (var Entry in arrReference)
                {
                    var strRef = Entry.Trim();
                    if (string.IsNullOrWhiteSpace(strRef))
                        continue;

                    if (!Table.Columns.TryGetValue(strRef, out var Col))
                    {
                        Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{ForeignTableName}' contains a non existing element '{strRef}' within the 'Reference' attribute.", LogLevels.Warning);
                        Columns.Clear();
                        break;
                    }

                    if (Columns.Contains(Col))
                    {
                        Parser.AddMessage(XNode, $"The element <{XNode.Name}> with Name='{ForeignTableName}' contains a duplicated element '{strRef}' within the 'Reference' attribute.", LogLevels.Warning);
                        Columns.Clear();
                        break;
                    }

                    Columns.Add(Col);
                }

                ForeignIndexName = XNode.GetAttribute("ForeignIndex");

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
            }
        }

        internal bool Resolve(cDataHistoryConfigTable Table, Dictionary<string, cDataHistoryConfigTable> AllTables, cXmlParser Parser)
        {
            IsValid = false;
            try
            {
                // get the foreign table
                if (!AllTables.TryGetValue(ForeignTableName, out var _ForeignTable))
                    return false;

                // get the foreign table index
                cDataHistoryConfigTableIndex _ForeignIndex = null;
                if (!string.IsNullOrEmpty(ForeignIndexName))
                    if (!_ForeignTable.Indexes.TryGetValue(ForeignIndexName, out _ForeignIndex))
                        return false;

                // get the foreign column list
                var _ForeignColumns = new List<cDataHistoryConfigColumnBase>();
                if (_ForeignIndex == null)
                    _ForeignColumns.AddRange(_ForeignTable.KeyColumns);
                else
                    _ForeignColumns.AddRange(_ForeignIndex.Columns);

                // check for column compatibilty
                if (_ForeignColumns.Count != Columns.Count)
                {
                    Parser.AddMessage(this.XLine, this.XCol, string.Format("The number of referenced columns of table '{0}' does not match the number of referenced foreign columns of table '{1}'.", Table.Name, _ForeignTable.Name), LogLevels.Error);
                    return false;
                }

                var ColsTo = _ForeignColumns.GetEnumerator();
                foreach (var ColFrom in Columns)
                {
                    if (!ColsTo.MoveNext())
                        return false;

                    var ColTo = ColsTo.Current;

                    if (!cDataHistoryConfigClusters.CheckColumnCompatibility(ColFrom, ColTo))
                    {
                        Parser.AddMessage(this.XLine, this.XCol, string.Format("The column type or cardinal of column '{0}' in table '{1}' does not match the referenced foreign column '{2}' of table '{3}'.", ColFrom.Name, Table.Name, ColTo.Name, _ForeignTable.Name), LogLevels.Error);
                        return false;
                    }

                }

                ForeignTable = _ForeignTable;
                ReferencedIndex = _ForeignIndex;
                ReferencedColumns = _ForeignColumns;

                this.IsValid = true;
                return true;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return false;
        }
    }

    public class cDataHistoryConfigMainTable : cDataHistoryConfigTable
    {
        public cDataHistoryConfigMainTable()
        {
        }

    }
}
