/// <summary>Main entry-point for this application.</summary>
        /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception>
        /// <param name="outputDirectory">     Directory to write files.</param>
        /// <param name="dataDirectory">       Allow passing in of data directory.</param>
        /// <param name="outputFormat">        The output format, JSON or XML (default: JSON).</param>
        /// <param name="state">               State to restrict generation to (default: none).</param>
        /// <param name="postalCode">          Postal code to restrict generation to (default: none).</param>
        /// <param name="facilityCount">       Number of facilities to generate.</param>
        /// <param name="timeSteps">           Number of time-step updates to generate.</param>
        /// <param name="timePeriodHours">     Time-step period in hours (default: 24).</param>
        /// <param name="seed">                Starting seed to use in generation, 0 for none (default: 0).</param>
        /// <param name="recordsToSkip">       Number of records to skip before starting generation (default: 0).</param>
        /// <param name="orgSource">           Source for organization records: generate|csv|connectathon (default: connectathon).</param>
        /// <param name="prettyPrint">         If output files should be formatted for display.</param>
        /// <param name="bedTypes">            Bar separated bed types: ICU|ER... (default: ICU|ER|HU).</param>
        /// <param name="operationalStatuses"> Bar separated operational status: U|O|K (default: O|U).</param>
        /// <param name="minBedsPerOrg">       The minimum number of beds per hospital (default: 10).</param>
        /// <param name="maxBedsPerOrg">       The maximum number of beds per hospital (default: 1000).</param>
        /// <param name="changeFactor">        The amount of change in bed state per step (default 0.2).</param>
        /// <param name="minIcuPercent">       Minimum percentage of beds for an org which are ICU type.</param>
        /// <param name="maxIcuPercent">       Maximum percentage of beds for an org which are ICU type.</param>
        /// <param name="ventilatorsPerIcu">   Average number of ventilators per ICU bed.</param>
        /// <param name="initialOccupancy">    Initial occupancy of bed percentage.</param>
        /// <param name="positiveTestRate">    Rate of people being tested returning positive.</param>
        /// <param name="hospitalizationRate"> Rate of people testing positive requiring hospitalization.</param>
        /// <param name="patientToIcuRate">    Rate of people hospitalized requiring ICU.</param>
        /// <param name="icuToVentilatorRate"> Rate of people in ICU requiring ventilators.</param>
        /// <param name="recoveryRate">        Rate of people recovering during hospitalization.</param>
        /// <param name="deathRate">           Rate of people dying in hospitalization, when care is available.</param>
        /// <param name="outputBundles">       True to output Bundles, false to output raw resources.</param>
        /// <param name="outputFlat">          True to output into a single directory, false to nest.</param>
        /// <param name="measureMarkdown">    If specified, the filename of the Measure Markdown file.</param>
        public static void Main(
            string outputDirectory,
            string dataDirectory       = null,
            string outputFormat        = "JSON",
            string state               = null,
            string postalCode          = null,
            int facilityCount          = 10,
            int timeSteps              = 2,
            int timePeriodHours        = 24,
            int seed                   = 0,
            int recordsToSkip          = 0,
            string orgSource           = "connectathon",
            bool prettyPrint           = true,
            string bedTypes            = "ICU|ER|HU",
            string operationalStatuses = "O|U",
            int minBedsPerOrg          = 10,
            int maxBedsPerOrg          = 1000,
            double changeFactor        = 0.2,
            double minIcuPercent       = 0.05,
            double maxIcuPercent       = 0.20,
            double ventilatorsPerIcu   = 0.20,
            double initialOccupancy    = 0.20,
            double positiveTestRate    = 0.5,
            double hospitalizationRate = 0.30,
            double patientToIcuRate    = 0.30,
            double icuToVentilatorRate = 0.70,
            double recoveryRate        = 0.1,
            double deathRate           = 0.05,
            bool outputBundles         = true,
            bool outputFlat            = false,
            string measureMarkdown     = "")
        {
            // sanity checks
            if (string.IsNullOrEmpty(outputDirectory))
            {
                throw new ArgumentNullException(nameof(outputDirectory));
            }

            if (string.IsNullOrEmpty(outputFormat))
            {
                throw new ArgumentNullException(nameof(outputFormat));
            }

            if (!Directory.Exists(outputDirectory))
            {
                Directory.CreateDirectory(outputDirectory);
            }

            _changeFactor           = changeFactor;
            _minIcuPercent          = minIcuPercent;
            _maxIcuPercent          = maxIcuPercent;
            _ventilatorsPerIcu      = ventilatorsPerIcu;
            _initialOccupancy       = initialOccupancy;
            _positiveTestRate       = positiveTestRate;
            _hospitalizationRate    = hospitalizationRate;
            _patientToIcuRate       = patientToIcuRate;
            _icuToVentilatorRate    = icuToVentilatorRate;
            _recoveryRate           = recoveryRate;
            _noResourceRecoveryRate = recoveryRate * 0.1;
            _deathRate           = deathRate;
            _noResourceDeathRate = Math.Min(deathRate * 10, 1.0);
            _outputBundles       = outputBundles;
            _outputFlat          = outputFlat;

            _useJson = outputFormat.ToUpperInvariant().Equals("JSON", StringComparison.Ordinal);

            if (_useJson)
            {
                SerializerSettings settings = new SerializerSettings()
                {
                    Pretty = prettyPrint,
                };

                _jsonSerializer = new FhirJsonSerializer(settings);
                _extension      = ".json";
            }
            else
            {
                SerializerSettings settings = new SerializerSettings()
                {
                    Pretty = prettyPrint,
                };

                _xmlSerializer = new FhirXmlSerializer(settings);
                _extension     = ".xml";
            }

            _useLookup    = (!string.IsNullOrEmpty(orgSource)) || (orgSource.ToUpperInvariant() != "GENERATE");
            _connectathon = string.IsNullOrEmpty(orgSource) || (orgSource.ToUpperInvariant() == "CONNECTATHON");

            if (seed == 0)
            {
                _rand = new Random();
            }
            else
            {
                _rand = new Random(seed);
            }

            // always need the geo manager
            GeoManager.Init(seed, minBedsPerOrg, maxBedsPerOrg, dataDirectory);

            OrgWorkerData.Init(seed);

            // only need hospital manager if we are using lookup (avoid loading otherwise)
            if (_useLookup || _connectathon)
            {
                HospitalManager.Init(
                    seed,
                    minBedsPerOrg,
                    maxBedsPerOrg,
                    dataDirectory,
                    _connectathon);
            }

            string dir;

            // create our time step directories
            if (!_outputFlat)
            {
                for (int step = 0; step < timeSteps; step++)
                {
                    dir = Path.Combine(outputDirectory, $"t{step}");

                    if (!Directory.Exists(dir))
                    {
                        Directory.CreateDirectory(dir);
                    }
                }
            }

            // create our organization records
            CreateOrgs(facilityCount, state, postalCode, recordsToSkip);

            ExportAggregate(outputDirectory, timeSteps, timePeriodHours);

            if (!string.IsNullOrEmpty(measureMarkdown))
            {
                File.WriteAllText(measureMarkdown, SanerMeasure.GetMarkdown());
            }
        }
        /// <summary>Export aggregate.</summary>
        /// <param name="outputDirectory">Directory to write files.</param>
        /// <param name="timeSteps">      Number of time-step updates to generate.</param>
        /// <param name="timePeriodHours">Time-step period in hours (default: 24).</param>
        private static void ExportAggregate(
            string outputDirectory,
            int timeSteps,
            int timePeriodHours)
        {
            // write meta-data (canonical resources) only in t0
            string dir = _outputFlat
                ? outputDirectory
                : Path.Combine(outputDirectory, "t0");

            List <IReportingFormat> formats = FormatHelper.GetFormatList();

            foreach (IReportingFormat format in formats)
            {
                Bundle measureBundle = SanerMeasure.GetBundle(format);

                if (measureBundle != null)
                {
                    WriteBundle(
                        Path.Combine(
                            dir,
                            $"{_filenameBaseForMeasures}-{format.Name}{_extension}"),
                        measureBundle);
                }

                Bundle questionnaireBundle = SanerQuestionnaire.GetBundle(format);

                if (questionnaireBundle != null)
                {
                    WriteBundle(
                        Path.Combine(
                            dir,
                            $"{_filenameBaseForQuestionnaires}-{format.Name}{_extension}"),
                        questionnaireBundle);
                }
            }

            // iterate over the orgs generating their data
            foreach (string orgId in _orgById.Keys)
            {
                Console.WriteLine($"Processing org: {orgId}");

                dir = _outputFlat
                    ? outputDirectory
                    : Path.Combine(outputDirectory, "t0");

                WriteOrgBundle(orgId, dir);

                CreateAggregateData(
                    orgId,
                    out OrgDeviceData deviceData,
                    out OrgPatientData patientData,
                    out OrgTestData testData,
                    out OrgWorkerData workerData);

                // loop over timeSteps
                for (int step = 0; step < timeSteps; step++)
                {
                    dir = _outputFlat
                        ? outputDirectory
                        : Path.Combine(outputDirectory, $"t{step}");

                    if (step != 0)
                    {
                        UpdateAggregateDataForStep(
                            ref deviceData,
                            ref patientData,
                            ref testData,
                            ref workerData);
                    }

                    TimeSpan hoursToSubtract = new TimeSpan(timePeriodHours * (timeSteps - step), 0, 0);
                    DateTime dt = DateTime.UtcNow.Subtract(hoursToSubtract);

                    WriteOrgReportBundle(
                        orgId,
                        dir,
                        dt,
                        deviceData,
                        patientData,
                        testData,
                        workerData,
                        step);
                }
            }
        }