public BulkImporter(CosmosDbConnectionString cosmosDbConnectionString)
 {
     _client = new DocumentClient(
         cosmosDbConnectionString.ServiceEndpoint,
         cosmosDbConnectionString.AuthKey,
         _connectionPolicy);
 }
        /// <summary>
        /// Seeds the Cosmos DB metadata container.
        /// </summary>
        private static async Task SeedDatabase(CosmosDbConnectionString cosmosDbConnectionString, CancellationToken cancellationToken)
        {
            // Check if data exists before seeding.
            var count     = 0;
            var query     = new QueryDefinition($"SELECT VALUE COUNT(1) FROM c");
            var container = await GetContainerIfExists(MetadataContainerName);

            var resultSetIterator = container.GetItemQueryIterator <int>(query, requestOptions: new QueryRequestOptions()
            {
                MaxItemCount = 1
            });

            if (resultSetIterator.HasMoreResults)
            {
                var result = await resultSetIterator.ReadNextAsync();

                if (result.Count > 0)
                {
                    count = result.FirstOrDefault();
                }
            }

            if (count == 0)
            {
                // Scale up the requested throughput (RU/s) for the metadata container prior to bulk import:
                WriteLineInColor($"No data currently exists in the {MetadataContainerName} container. Scaling up the container RU/s to 50,000 prior to bulk data insert...", ConsoleColor.Cyan);
                await ChangeContainerPerformance(container, 50000);

                WriteLineInColor("Container RU/s adjusted. Generating data to seed database...", ConsoleColor.Cyan);

                var bulkImporter = new BulkImporter(cosmosDbConnectionString);
                var vehicles     = DataGenerator.GenerateVehicles().ToList();
                var consignments = DataGenerator.GenerateConsignments(900).ToList();
                var packages     = DataGenerator.GeneratePackages(consignments.ToList()).ToList();
                var trips        = DataGenerator.GenerateTrips(consignments.ToList(), vehicles.ToList()).ToList();

                WriteLineInColor("Generated data to seed database. Saving metadata to Cosmos DB...", ConsoleColor.Cyan);

                // Save vehicles:
                WriteLineInColor($"Adding {vehicles.Count()} vehicles...", ConsoleColor.Green);
                await bulkImporter.BulkImport(vehicles, DatabaseName, MetadataContainerName, cancellationToken, 1);

                // Save consignments:
                WriteLineInColor($"Adding {consignments.Count()} consignments...", ConsoleColor.Green);
                await bulkImporter.BulkImport(consignments, DatabaseName, MetadataContainerName, cancellationToken, 1);

                // Save packages:
                WriteLineInColor($"Adding {packages.Count()} packages...", ConsoleColor.Green);
                await bulkImporter.BulkImport(packages, DatabaseName, MetadataContainerName, cancellationToken, 4);

                // Save trips:
                WriteLineInColor($"Adding {trips.Count()} trips...", ConsoleColor.Green);
                await bulkImporter.BulkImport(trips, DatabaseName, MetadataContainerName, cancellationToken, 1);

                WriteLineInColor("Finished seeding Cosmos DB.", ConsoleColor.Cyan);

                // Scale down the requested throughput (RU/s) for the metadata container:
                await ChangeContainerPerformance(container, 15000);
            }
            else
            {
                WriteLineInColor("\nCosmos DB already contains data. Skipping database seeding step...", ConsoleColor.Yellow);
            }
        }
        static async Task Main(string[] args)
        {
            // Setup configuration to either read from the appsettings.json file (if present) or environment variables.
            var builder = new ConfigurationBuilder()
                          .SetBasePath(Directory.GetCurrentDirectory())
                          .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                          .AddEnvironmentVariables();

            _configuration = builder.Build();

            var arguments = ParseArguments();
            var cosmosDbConnectionString = new CosmosDbConnectionString(arguments.CosmosDbConnectionString);

            // Set an optional timeout for the generator.
            _cancellationSource = arguments.MillisecondsToRun == 0 ? new CancellationTokenSource() : new CancellationTokenSource(arguments.MillisecondsToRun);
            var         cancellationToken = _cancellationSource.Token;
            var         statistics        = new Statistic[0];
            List <Trip> trips;

            // Set the Cosmos DB connection policy.
            var connectionPolicy = new CosmosClientOptions
            {
                ConnectionMode = ConnectionMode.Direct
            };

            var numberOfMillisecondsToLead = arguments.MillisecondsToLead;

            var taskWaitTime = 0;

            if (numberOfMillisecondsToLead > 0)
            {
                taskWaitTime = numberOfMillisecondsToLead;
            }

            var progress = new Progress <Progress>();

            progress.ProgressChanged += (sender, progressArgs) =>
            {
                foreach (var message in progressArgs.Messages)
                {
                    WriteLineInColor(message.Message, message.Color.ToConsoleColor());
                }
                statistics = progressArgs.Statistics;
            };

            WriteLineInColor("Fleet Data Generator", ConsoleColor.White);
            Console.WriteLine(string.Empty);
            WriteLineInColor("Press Ctrl+C or Ctrl+Break to cancel.", ConsoleColor.Cyan);
            Console.WriteLine("Statistics for generated vehicle and related telemetry data will be updated for every 50 messages sent");
            Console.WriteLine(string.Empty);
            Console.WriteLine("=============");
            WriteLineInColor("** Enter 1 to generate and send data for 1 vehicle.", ConsoleColor.Green);
            WriteLineInColor("** Enter 2 to generate and send data for 10 vehicles.", ConsoleColor.Green);
            WriteLineInColor("** Enter 3 to generate and send data for 50 vehicles.", ConsoleColor.Green);
            WriteLineInColor("** Enter 4 to generate and send data for 100 vehicles.", ConsoleColor.Green);
            WriteLineInColor("** Enter 5 to generate and send data for the number of vehicles defined in the application settings/environment variables.", ConsoleColor.Green);
            Console.WriteLine("=============");

            // Handle Control+C or Control+Break.
            Console.CancelKeyPress += (o, e) =>
            {
                WriteLineInColor("Stopped generator. No more events are being sent.", ConsoleColor.Yellow);
                CancelAll();

                // Allow the main thread to continue and exit...
                WaitHandle.Set();
            };

            var userInput             = "";
            var numberSimulatedTrucks = arguments.NumberSimulatedTrucks;
            var runGenerator          = true;

            while (true)
            {
                Console.Write("Enter the number of the operation you would like to perform > ");

                var input = Console.ReadLine();
                if (input != null && (input.Equals("1", StringComparison.InvariantCultureIgnoreCase) ||
                                      input.Equals("2", StringComparison.InvariantCultureIgnoreCase) ||
                                      input.Equals("3", StringComparison.InvariantCultureIgnoreCase) ||
                                      input.Equals("4", StringComparison.InvariantCultureIgnoreCase) ||
                                      input.Equals("5", StringComparison.InvariantCultureIgnoreCase)))
                {
                    userInput = input.Trim();
                    break;
                }

                Console.WriteLine("Invalid input entered. Please enter either 1, 2, 3, 4, or 5.");
            }

            switch (userInput)
            {
            case "1":
                numberSimulatedTrucks = 1;
                break;

            case "2":
                numberSimulatedTrucks = 10;
                break;

            case "3":
                numberSimulatedTrucks = 50;
                break;

            case "4":
                numberSimulatedTrucks = 100;
                break;

            case "5":
                numberSimulatedTrucks = arguments.NumberSimulatedTrucks;
                break;

            default:
                // Exit.
                runGenerator = false;
                break;
            }

            if (runGenerator)
            {
                // Instantiate Cosmos DB client and start sending messages:
                using (_cosmosDbClient = new CosmosClient(cosmosDbConnectionString.ServiceEndpoint.OriginalString,
                                                          cosmosDbConnectionString.AuthKey, connectionPolicy))
                {
                    await InitializeCosmosDb();

                    // Find and output the container details, including # of RU/s.
                    var container = _database.GetContainer(MetadataContainerName);

                    var offer = await container.ReadThroughputAsync(cancellationToken);

                    if (offer != null)
                    {
                        var currentCollectionThroughput = offer ?? 0;
                        WriteLineInColor(
                            $"Found collection `{MetadataContainerName}` with {currentCollectionThroughput} RU/s.",
                            ConsoleColor.Green);
                    }

                    // Ensure the telemetry container throughput is set to 15,000 RU/s.
                    var telemetryContainer = await GetContainerIfExists(TelemetryContainerName);
                    await ChangeContainerPerformance(telemetryContainer, 15000);

                    // Initially seed the Cosmos DB database with metadata if empty.
                    await SeedDatabase(cosmosDbConnectionString, cancellationToken);

                    trips = await GetTripsFromDatabase(numberSimulatedTrucks, container);
                }

                try
                {
                    // Start sending telemetry from simulated vehicles to Event Hubs:
                    _runningVehicleTasks = await SetupVehicleTelemetryRunTasks(numberSimulatedTrucks,
                                                                               trips, arguments.IoTHubConnectionString);

                    var tasks = _runningVehicleTasks.Select(t => t.Value).ToList();
                    while (tasks.Count > 0)
                    {
                        try
                        {
                            Task.WhenAll(tasks).Wait(cancellationToken);
                        }
                        catch (TaskCanceledException)
                        {
                            //expected
                        }

                        tasks = _runningVehicleTasks.Where(t => !t.Value.IsCompleted).Select(t => t.Value).ToList();
                    }
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("The vehicle telemetry operation was canceled.");
                    // No need to throw, as this was expected.
                }

                CancelAll();
                Console.WriteLine();
                WriteLineInColor("Done sending generated vehicle telemetry data", ConsoleColor.Cyan);
                Console.WriteLine();
                Console.WriteLine();
            }

            // Keep the console open.
            Console.ReadLine();
            WaitHandle.WaitOne();
        }