// Constructor with just AppHost parameter
        public BaseServicesData(IAppHost appHost)
        {
            Log.Debug("Entering BaseServicesData .ctor");
            Container = appHost.GetContainer();

            // At this point, appHost.AppSettings has all of the data read in from the various Configuration sources (environment, command line, indirect file, direct file, compiled in)

            // If the Redis configuration key exists, register Redis as a name:value pair cache
            if (appHost.AppSettings.Exists(configKeyPrefix + configKeyRedisConnectionString))
            {
                var redisConnectionString = appHost.AppSettings.GetString(configKeyPrefix + configKeyRedisConnectionString);
                Container.Register <IRedisClientsManager>(c => new RedisManagerPool(redisConnectionString));
                Container.Register(c => c.Resolve <IRedisClientsManager>().GetCacheClient());
                cacheClient = Container.Resolve <ICacheClient>();
            }
            else
            {
                throw new NotImplementedException(RedisConnectionStringKeyNotFoundExceptionMessage);
            }

            // Ensure that the cacheClient is running
            try {
                var test = cacheClient.GetKeysStartingWith(configKeyPrefix);
            }
            catch (ServiceStack.Redis.RedisException Ex) {
                if (Ex.InnerException.Message.Contains(ListeningServiceNotRunningInnerExceptionMessage))
                {
                    throw new Exception($"In BaseServicesData .ctor: {RedisNotRunningExceptionMessage} on {Ex.Message}", Ex);
                }
            }

            // ToDo: Validate the cache and the appSettings Config keys/values for the BaseService align

            // Key names in the cache for this namespace's configuration settings consist of this namespace and the string .Config
            // This prefix is available as a static method on this class
            var cacheConfigKeysForThisNamespace = cacheClient.GetKeysStartingWith(configKeyPrefix);

            //var appSettingsConfigKeysForThisNamespace = appHost.AppSettings.GetAllKeys().Select(x => x.IndexOf(configKeyPrefix) >= 0? x: configKeyPrefix + x);
            //ToDo: record any discrepancies, and report them in a log, and also report to the GUI when BaseServices get initialized.

            ///Temp: This stores the values from the AppSettings into the CacheClient (Redis currently) via the properties setter for the following Config settings
            MySqlConnectionString = appHost.AppSettings
                                    .GetString(configKeyPrefix +
                                               configKeyMySqlConnectionString);
            RedisCacheConnectionString = appHost.AppSettings
                                         .GetString(configKeyPrefix +
                                                    configKeyRedisConnectionString);
            // ToDo: Instead of single keys, a magic function that reads the set of keys in each , recursively, and compares those sets, and the values of the keys present in both
            // At this point the Redis cache should match the current run's AppConfigurationSettings

            // Populate the ConfigurationData in the BaseServicesData instance
            ConstructConfigurationData();

            // Populate the ComputerInventory structure with localhost data
            ConstructComputerInventory();
            // Populate the UserData in the BaseServicesData instance
            ConstructUserData();
            // Populate the AuthenticationData structure
            ConstructAuthenticationData(appHost);
            // Populate the AuthorizationData structure
            ConstructAuthorizationData(appHost);

            // ToDo: Move this region to Agent.BaseServices.Data.Timers
            // Add a dictionary of timers to the IoC container
            #region create a dictionary of timers and register it in the IoC container
            var timers = new Dictionary <string, System.Timers.Timer>();
            Container.Register <Dictionary <string, System.Timers.Timer> >(c => timers);
            #endregion create a dictionary of timers and register it in the IoC container

            // Create a dictionary of tasks that is intended to hold "long running" tasks and register the dictionary in the IoC container
            ConstructLongRunningTasks();

            // Add a timer to check the status of long running tasks, and attach a callback to the timer
            #region create longRunningTasksCheckTimer, connect callback, and store in container's timers
            Log.Debug("In BaseServicesData .ctor: creating longRunningTasksCheckTimer");
            var longRunningTasksCheckTimer = new System.Timers.Timer(10000)
            {
                AutoReset = true
            };
            longRunningTasksCheckTimer.Elapsed += new ElapsedEventHandler(LongRunningTasksCheckTimer_Elapsed);
            timers.Add(LongRunningTasksCheckTimerName, longRunningTasksCheckTimer);
            #endregion create longRunningTasksCheckTimer, connect callback, and store in container's timers


            // Populate the application's Base Gateways
            // Location of the files will depend on running as LifeCycle Production/QA/Dev as well as Debug and Release settings
            Gateways = new MultiGatewaysBuilder()
                       // Command line flags have highest priority
                       // next in priority are  Environment variables
                       //.AddEnvironmentalVariables()
                       // next in priority are Configuration settings in a text file relative to the current working directory at the point in time when this method executes.
                       //.AddTextFile(pluginGatewaysTextFileName)
                       // Builtin (compiled in) have the lowest priority
                       //.AddDictionarySettings(DefaultGateways.Configuration())
                       .Build();

            // temporary manually populate the collection with one Gateway
            // Build a Gateway entry for Google Maps GeoCode and ReverseGeoCode REST endpoints
            var geb = new GatewayEntryBuilder();
            geb.AddName("ReverseGeoCode");
            geb.AddRUri("geocode/json");
            geb.AddReqDataPayloadType(typeof(Base_GoogleMapsGeoCoding_ReverseGeoCode_ReqDTO));
            geb.AddRspDataPayloadType(typeof(Base_GoogleMapsGeoCoding_ReverseGeoCode_RspDTO));
            var ge = geb.Build();

            var defaultPolicy = Policy.Handle <Exception>().RetryAsync(3, (exception, attempt) => {
                // This is the  exception handler for this defaultPolicy
                Log.Debug($"Policy logging: {exception.Message} : attempt = {attempt}");
                //retries++;
            });

            // Build a Gateway for Google Maps GeoCode Gateway
            // ToDo replace DefaultAPIKEy auth with a more robust and extendable solution
            string defaultAPIKey = appHost.AppSettings
                                   .GetString(configKeyPrefix +
                                              configKeyGoogleMapsAPIKey);
            var gb = new GatewayBuilder();
            gb.AddName("GoogleMaps");
            gb.AddBaseUri(new Uri("https://maps.googleapis.com/maps/api/"));
            //gb.AddDefaultPolicy(defaultPolicy);
            // ToDo replace DefaultAPIKEy auth with a more robust and extendable solution
            gb.AddDefaultAPIKey(defaultAPIKey);
            gb.AddGatewayEntry(ge);
            var gw = gb.Build();
            Gateways.Add("GoogleMaps", gw);
            //Gateways.Add("GoogleMapsGeoCoding", new GatewayBuilder().AddName("GoogleMapsGeoCoding").AddBaseUri(new Uri("https://maps.googleapis.com/maps/api").AddDefaultPolicy(new Polly.Policy()).Build());

            // Create a collection of GatewayMonitors for BaseServices based on the collection of Gateways defined by the Base services
            var gatewayMonitorsBuilder = new GatewayMonitorsBuilder("Base");
            //gatewayMonitorsBuilder.AddGatewayMonitor(new GatewayMonitor(Gateways.Get("GoogleMapsGeoCoding")));
            GatewayMonitors = gatewayMonitorsBuilder.Build();
            // temporary manually populate the collection with one GatewayMonitor

            var gemb = new GatewayEntryMonitorBuilder();
            gemb.AddName("GeoCode");
            var gem = gemb.Build();
            var gmb = new GatewayMonitorBuilder();
            gmb.AddName("GoogleMapsGeoCoding");
            gmb.AddGatewayEntryMonitor(gem);
            var gm = gmb.Build();
            GatewayMonitors.GatewayMonitorColl.Add("Manual", gm);

            // GatewayMonitors.GatewayMonitorColl.Add("Manual", new GatewayMonitorBuilder().AddName("GoogleMapsGeoCoding").AddGatewayEntryMonitor(new GatewayEntryMonitorBuilder().AddName("GeoCode").Build()).Build());

            // Store the collection of Gateway Monitor in the Base Data structure

            // Populate the specific per-user data instance for this user
            ConstructUserData();

            // ToDo: support AppSettings to control the enable/disable of Postman
            // Enable Postman integration
            // AppHost.Plugins.Add(new PostmanFeature());


            // ToDo: support AppSettings to control the enable/disable of CORS Feature
            // Enable CORS support
            appHost.Plugins.Add(new CorsFeature(allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
                                                allowedOrigins: "*",
                                                allowCredentials: true,
                                                allowedHeaders: "content-type, Authorization, Accept"));

            // ToDo: Document clearly the metadata feature cannot be allowed on /, because it messes up the default returning f the content of index.html
            appHost.Config.EnableFeatures = Feature.All.Remove(Feature.Metadata);



            // Turn debug mode for the ACEAgent depending if running in debug mode or release mode
#if Debug
            appHost.Config.DebugMode = true;
#else
            appHost.Config
            .DebugMode = false;
#endif


            Log.Debug("Leaving BaseServicesData ctor");
        }
        public void Configure(IAppHost appHost)
        {
            Log.Debug("starting RealEstateServicesPlugin.Configure");

            // Populate this Plugin's Application Configuration Settings
            // Location of the files will depend on running as LifeCycle Production/QA/Dev as well as Debug and Release settings
            var pluginAppSettings = new MultiAppSettingsBuilder()
                                    // Command line flags have highest priority
                                    // next in priority are  Environment variables
                                    //.AddEnvironmentalVariables()
                                    // next in priority are Configuration settings in a text file relative to the current working directory at the point in time when this method executes.
                                    .AddTextFile(pluginSettingsTextFileName)
                                    // Builtin (compiled in) have the lowest priority
                                    .AddDictionarySettings(DefaultConfiguration.Configuration())
                                    .Build();

            // Key names in the cache for Configuration settings for a Plugin consist of the namespace and the string .Config
            // Key names in the appSettings of a Plugin for Configuration settings may or may not have the prefix
            // Compare the two lists of Configuration setting keys...

            // create a copy of the keys from AppSettings ensuring all are prefixed with the namespace and .Config
            var configKeyPrefix           = myNamespace + ".Config.";
            var lengthConfigKeyPrefix     = configKeyPrefix.Length;
            var appSettingsConfigKeys     = pluginAppSettings.GetAllKeys();
            var fullAppSettingsConfigKeys = appSettingsConfigKeys.Select(x => x.IndexOf(configKeyPrefix) >= 0? x: configKeyPrefix + x);

            // Get this namespaces configuration settings from the cache
            var cacheClient     = appHost.GetContainer().Resolve <ICacheClient>();
            var cacheConfigKeys = cacheClient.GetKeysStartingWith(configKeyPrefix);

            // Compare the configuration retrieved from the cache with the configuration from the built-in defaults, the text files, and the environment variables
            // If the cache is missing a ConfigKey, update the cache with the ConfigKey and its value from the appSettings
            var excludedConfigKeys   = new HashSet <string>(cacheConfigKeys);
            var additionalConfigKeys = fullAppSettingsConfigKeys.Where(ck => !excludedConfigKeys.Contains(ck));

            foreach (var ck in additionalConfigKeys)
            {
                cacheClient.Set <string>(ck, pluginAppSettings.GetString(ck.Substring(lengthConfigKeyPrefix)));
            }
            // If both the cache and the appSettings have the same ConfigKey, and the same value, do nothing
            // If both the cache and the appSettings have the same ConfigKey, and the values are different,
            // If the cache has a ConfigKey and the appSettings does not have that ConfigKey
            // Set a flag indicating the need to dialog with the user to resolve the cache vs. appSettings discrepancies
            bool configurationsMatch = true; // (appSettingsConfigkeys.Count == cacheConfigkeys.Count) ;


            // Populate this Plugin's Gateways
            // Location of the files will depend on running as LifeCycle Production/QA/Dev as well as Debug and Release settings
            var pluginGateways = new MultiGatewaysBuilder()
                                 // Command line flags have highest priority
                                 // next in priority are  Environment variables
                                 //.AddEnvironmentalVariables()
                                 // next in priority are Configuration settings in a text file relative to the current working directory at the point in time when this method executes.
                                 //.AddTextFile(pluginGatewaysTextFileName)
                                 // Builtin (compiled in) have the lowest priority
                                 //.AddDictionarySettings(DefaultGateways.Configuration())
                                 .Build();



            // Create a Gateways collection from the txt file
            ConcurrentObservableDictionary <string, IGateway> gateways = new ConcurrentObservableDictionary <string, IGateway>();

            gateways.Add("GoogleMapsGeoCoding", new GatewayBuilder().Build());
            // Create the Plugin's data structure. There should only be a single instance.
            // Every Property matching a ConfigKey gets/sets the value of the matching ConfigKey in the cache
            // ConfigKey Properties do not have to be set in the constructor because the cache was setup before calling the constructor


            RealEstateServicesData RealEstateServicesData = new RealEstateServicesData(appHost, new ConcurrentObservableDictionary <string, decimal>(), onPluginRootCollectionChanged, onPluginRootPropertyChanged);

            // copy the most recent configuration settings to the Data
            // hmm should be a way to make sure the Data has a Property for each configuration setting, and to populate the initial Data with the cache value
            RealEstateServicesData.GoogleAPIKeyEncrypted   = cacheClient.Get <string>(configKeyPrefix + "GoogleAPIKeyEncrypted");
            RealEstateServicesData.HomeAwayAPIKeyEncrypted = cacheClient.Get <string>(configKeyPrefix + "HomeAwayAPIKeyEncrypted");
            RealEstateServicesData.HomeAway_API_URI        = cacheClient.Get <string>(configKeyPrefix + "HomeAway_API_URI");
            RealEstateServicesData.Google_API_URI          = cacheClient.Get <string>(configKeyPrefix + "Google_API_URI");
            RealEstateServicesData.UriHomeAway_API_URI     = cacheClient.Get <Uri>(configKeyPrefix + "UriHomeAway_API_URI");
            RealEstateServicesData.UriGoogle_API_URI       = cacheClient.Get <Uri>(configKeyPrefix + "UriGoogle_API_URI");

            // and pass the Plugin's data structure to the container so it will be available to every other module and services
            appHost.GetContainer()
            .Register <RealEstateServicesData>(x => RealEstateServicesData);



            // ToDo: enable the mechanisms that monitors each GUI-specific data sensor, and start them running
        }