public SuperTileGenerator(IConfigSource config, RDBMap rdbmap)
        {
            var tileInfo = config.Configs["MapTileInfo"];

            _tilePixelSize = tileInfo?.GetInt("PixelScale", Constants.PixelScale) ?? Constants.PixelScale;

            var zoomInfo = config.Configs["TileZooming"];

            _maxZoomLevel = zoomInfo?.GetInt("HighestZoomLevel", Constants.HighestZoomLevel) ?? Constants.HighestZoomLevel;

            _imageWriter = new TileImageWriter(config);

            _rdbMap = rdbmap;

            var _tileInfo = config.Configs["MapTileInfo"];

            _oceanColor = Color.FromArgb(
                _tileInfo?.GetInt("OceanColorRed", Constants.OceanColor.R) ?? Constants.OceanColor.R,
                _tileInfo?.GetInt("OceanColorGreen", Constants.OceanColor.G) ?? Constants.OceanColor.G,
                _tileInfo?.GetInt("OceanColorBlue", Constants.OceanColor.B) ?? Constants.OceanColor.B
                );

            _serverMode = config.Configs["Startup"].GetBoolean("ServerMode", Constants.KeepRunningDefault);
        }
        public static int Main(string[] args)
        {
            // First line, hook the appdomain to the crash reporter
#pragma warning disable RECS0164 // Explicit delegate creation expression is redundant
            // Analysis disable once RedundantDelegateCreation // The "new" is required.
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
#pragma warning restore RECS0164 // Explicit delegate creation expression is redundant

            var watch = System.Diagnostics.Stopwatch.StartNew();

            // Add the arguments supplied when running the application to the configuration
            var configSource = new ArgvConfigSource(args);
            _configSource = configSource;

            // Commandline switches
            configSource.AddSwitch("Startup", "inifile");
            configSource.AddSwitch("Startup", "logconfig");
            configSource.AddSwitch("Startup", "MaxParallelism", "p");
            configSource.AddSwitch("Startup", "ServerMode");

            var startupConfig = _configSource.Configs["Startup"];

            // TODO: var pidFileManager = new PIDFileManager(startupConfig.GetString("pidfile", string.Empty));

            // Configure Log4Net
            {
                var logConfigFile = startupConfig.GetString("logconfig", string.Empty);
                if (string.IsNullOrEmpty(logConfigFile))
                {
                    XmlConfigurator.Configure();
                    LogBootMessage();
                    LOG.Info("Configured log4net using ./Anaximander.exe.config as the default.");
                }
                else
                {
                    XmlConfigurator.Configure(new FileInfo(logConfigFile));
                    LogBootMessage();
                    LOG.Info($"Configured log4net using \"{logConfigFile}\" as configuration file.");
                }
            }

            // Configure nIni aliases and localles
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US", true);

            configSource.Alias.AddAlias("On", true);
            configSource.Alias.AddAlias("Off", false);
            configSource.Alias.AddAlias("True", true);
            configSource.Alias.AddAlias("False", false);
            configSource.Alias.AddAlias("Yes", true);
            configSource.Alias.AddAlias("No", false);

            // Read in the ini file
            ReadConfigurationFromINI(configSource);

            var configRead        = configSource.Configs["AssetsRead"];
            var serversRead       = GetServers(configSource, configRead, _assetServersByName);
            var chattelConfigRead = GetConfig(configRead, serversRead);

            // Create an IPC wait handle with a unique identifier.
            var serverMode  = startupConfig.GetBoolean("ServerMode", Constants.KeepRunningDefault);
            var createdNew  = true;
            var waitHandle  = serverMode ? new EventWaitHandle(false, EventResetMode.AutoReset, "4d1ede7a-7f81-4934-bc59-f4fe10396408", out createdNew) : null;
            var serverState = serverMode ? ServerState.Starting : ServerState.Ignored;

            // If the handle was already there, inform the user and die.
            if (serverState == ServerState.Starting && !createdNew)
            {
                LOG.Error("Server process alredy started, please stop that server first.");
                return(2);
            }

            LOG.Info($"Configured for max degree of parallelism of {startupConfig.GetInt("MaxParallelism", Constants.MaxDegreeParallism)}");

            var readerLocalStorage = new AssetStorageSimpleFolderTree(chattelConfigRead);

            var chattelReader = new ChattelReader(chattelConfigRead, readerLocalStorage);             // TODO: add purge flag to CLI
            Texture.Initialize(chattelReader);

            watch.Stop();
            LOG.Info($"Read configuration in {watch.ElapsedMilliseconds} ms.");
            watch.Restart();

            // Load the RDB map
            try {
                _rdbMap = new RDBMap(configSource);
            }
            catch (DatabaseException e) {
                LOG.Error($"Unable to continue without database connection. Aborting.", e);

                return(1);
            }

            watch.Stop();
            LOG.Info($"Loaded region DB in {watch.ElapsedMilliseconds} ms for a total of {_rdbMap.GetRegionCount()} regions, resulting in an average of {(float)watch.ElapsedMilliseconds / _rdbMap.GetRegionCount()} ms / region.");
            watch.Restart();

            /* Issues to watch for:
             * Region delete - The DBA will need to actually remove the estate record to cause a map tile delete.
             * TODO: Tile image read during write - The web server could attempt to read a file while the file is being written.
             *  - Possible solution: write to a random filename then try { mv rndname to finalname with overwrite } catch { try again later for a max of N times }
             *    This should provide as much atomicity as possible, and allow anything that's blocking access to be bypassed via time delay. Needs to just fail under exceptions that indicate always-fail conditions.
             */

            {
                LOG.Debug("Initializing writer and generator.");
                _tileWriter    = new TileImageWriter(configSource);
                _tileGenerator = new TileGenerator(configSource);

                LOG.Debug("Writing ocean tile.");
                // Generate & replace ocean tile
                using (var ocean_tile = _tileGenerator.GenerateOceanTile()) {
                    _tileWriter.WriteOceanTile(ocean_tile.Bitmap);
                }

                LOG.Debug("Generating a full batch of region tiles.");
                // Generate region tiles - all existing are nearly guaranteed to be out of date.
                var options = new ParallelOptions {
                    MaxDegreeOfParallelism = startupConfig.GetInt("MaxParallelism", Constants.MaxDegreeParallism)
                };                                                                                                                                                   // -1 means full parallel.  1 means non-parallel.
                Parallel.ForEach(_rdbMap.GetRegionUUIDs(), options, (region_id) => {
                    var oldPriority = Thread.CurrentThread.Priority;

                    try {
                        Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;

                        UpdateRegionTile(region_id);
                    }
                    finally {
                        Thread.CurrentThread.Priority = oldPriority;
                    }
                });

                watch.Stop();
                LOG.Info($"Created full res map tiles in {watch.ElapsedMilliseconds} ms all regions with known locations, resulting in an average of {(float)watch.ElapsedMilliseconds / _rdbMap.GetRegionCount()} ms / region.");
                watch.Restart();


                // Generate zoom level tiles.
                // Just quickly build the tile tree so that lookups of the super tiles can be done.

                var superGen = new SuperTileGenerator(configSource, _rdbMap);

                superGen.PreloadTileTrees(_rdbMap.GetRegionUUIDs());

                watch.Stop();
                LOG.Info($"Preloaded tile tree in {watch.ElapsedMilliseconds} ms.");
                watch.Restart();


                // Remove all tiles that do not have a corresponding entry in the map.
                _tileWriter.RemoveDeadTiles(_rdbMap, superGen.AllNodesById);

                watch.Stop();
                LOG.Info($"Removed all old tiles in {watch.ElapsedMilliseconds} ms.");
                watch.Restart();


                // Actually generate the zoom level tiles.
                superGen.GeneratePreloadedTree();

                watch.Stop();
                LOG.Info($"Created all super tiles in {watch.ElapsedMilliseconds} ms.");
            }

            // Activate server process
            if (serverState == ServerState.Starting)
            {
                System.Console.CancelKeyPress += (sender, cargs) => {
                    cargs.Cancel = true;
                    waitHandle.Set();
                };

                serverState = ServerState.Running;

                var server_config = configSource.Configs["Server"];

                var domain = server_config?.GetString("UseSSL", Constants.ServerDomain) ?? Constants.ServerDomain;
                var port   = (uint)(server_config?.GetInt("UseSSL", Constants.ServerPort) ?? Constants.ServerPort);
                var useSSL = server_config?.GetBoolean("UseSSL", Constants.ServerUseSSL) ?? Constants.ServerUseSSL;

                var protocol = useSSL ? "https" : "http";
                LOG.Info($"Activating server on '{protocol}://{domain}:{port}', listening for region updates.");

                RestApi.RestAPI.StartHost(
                    UpdateRegionDelegate,
                    MapRulesDelegate,
                    CheckAPIKeyDelegate,
                    domain,
                    port,
                    useSSL
                    );

                waitHandle.WaitOne();
                serverState = ServerState.Stopping;
            }

            // I don't care what's still connected or keeping things running, it's time to die!
            Environment.Exit(0);
            return(0);
        }