/// <summary>
        /// Generates pump telemetry data to send to IoT Central.
        /// </summary>
        /// <param name="sampleSize">The number of telemetry items to generate (excluding the number of gradually failed items, if applicable).</param>
        /// <param name="causeFailure">Whether to cause a pump failure.</param>
        /// <param name="failOverXIterations">If there is a pump failure, how gradual should it be? Enter 0 for immediate failure.</param>
        /// <returns></returns>
        public static IEnumerable <PumpTelemetryItem> GeneratePumpTelemetry(int sampleSize          = 500, bool causeFailure = true,
                                                                            int failOverXIterations = 250)
        {
            // If causing a failure, set the good sample size to 1/8.
            var normalSampleSize = causeFailure ? sampleSize / 8 : sampleSize;

            // Generate normal pump operation:
            var motorPowerkW   = Generate.Sinusoidal(normalSampleSize, PumpNormalState.MotorPowerkW.SamplingRate, PumpNormalState.MotorPowerkW.Frequency, PumpNormalState.MotorPowerkW.Amplitude, RandomizeInitialValue(PumpNormalState.MotorPowerkW.InitialValue));
            var motorSpeed     = Generate.Sinusoidal(normalSampleSize, PumpNormalState.MotorSpeed.SamplingRate, PumpNormalState.MotorSpeed.Frequency, PumpNormalState.MotorSpeed.Amplitude, RandomizeInitialValue(PumpNormalState.MotorSpeed.InitialValue));
            var pumpRate       = Generate.Normal(normalSampleSize, RandomizeInitialValue(PumpNormalState.PumpRate.InitialValue), PumpNormalState.PumpRate.StandardDeviation);
            var timePumpOn     = Generate.Periodic(normalSampleSize, PumpNormalState.TimePumpOn.SamplingRate, RandomizeInitialValue(PumpNormalState.TimePumpOn.Frequency), RandomizeInitialValue(PumpNormalState.TimePumpOn.Amplitude));
            var casingFriction = Generate.Normal(normalSampleSize, RandomizeInitialValue(PumpNormalState.CasingFriction.InitialValue), PumpNormalState.CasingFriction.StandardDeviation);

            var telemetry = new PumpTelemetry();

            if (causeFailure)
            {
                var failedSampleSize = sampleSize - normalSampleSize;

                // Generate failing pump operation:
                var motorPowerkWFailing   = Generate.Sinusoidal(failedSampleSize, PumpFailedState.MotorPowerkW.SamplingRate, PumpFailedState.MotorPowerkW.Frequency, PumpFailedState.MotorPowerkW.Amplitude, RandomizeInitialValue(PumpFailedState.MotorPowerkW.InitialValue));
                var motorSpeedFailing     = Generate.Sinusoidal(failedSampleSize, PumpFailedState.MotorSpeed.SamplingRate, PumpFailedState.MotorSpeed.Frequency, PumpFailedState.MotorSpeed.Amplitude, RandomizeInitialValue(PumpFailedState.MotorSpeed.InitialValue));
                var pumpRateFailing       = Generate.Normal(failedSampleSize, RandomizeInitialValue(PumpFailedState.PumpRate.InitialValue), PumpFailedState.PumpRate.StandardDeviation);
                var timePumpOnFailing     = Generate.Periodic(failedSampleSize, PumpFailedState.TimePumpOn.SamplingRate, RandomizeInitialValue(PumpFailedState.TimePumpOn.Frequency), RandomizeInitialValue(PumpFailedState.TimePumpOn.Amplitude));
                var casingFrictionFailing = Generate.Normal(failedSampleSize, RandomizeInitialValue(PumpFailedState.CasingFriction.InitialValue), PumpFailedState.CasingFriction.StandardDeviation);

                telemetry.GraduallyDeteriorateNormalToFailed(motorPowerkW, motorSpeed, pumpRate, timePumpOn,
                                                             casingFriction,
                                                             motorPowerkWFailing, motorSpeedFailing, pumpRateFailing, timePumpOnFailing, casingFrictionFailing,
                                                             failOverXIterations);
            }
            else
            {
                telemetry = new PumpTelemetry(motorPowerkW, motorSpeed, pumpRate, timePumpOn, casingFriction);
            }

            return(telemetry.ToPumpTelemetryItems());
        }
        /// <summary>
        /// Generates pump telemetry data for training an ML model and outputs the data to a console window and/or CSV files.
        /// </summary>
        /// <param name="sampleSize">The number of telemetry items to generate (excluding the number of gradually failed items, if applicable).</param>
        /// <param name="causeFailure">Whether to cause a pump failure.</param>
        /// <param name="failOverXIterations">If there is a pump failure, how gradual should it be? Enter 0 for immediate failure.</param>
        /// <param name="outputToConsole">If true, displays all of the generated telemetry in the console window.</param>
        /// <param name="writeFile">If true, writes the telemetry to a CSV file that can be used to train the model.</param>
        public static void GenerateModelTrainingData(int sampleSize          = 500, bool causeFailure    = true,
                                                     int failOverXIterations = 250, bool outputToConsole = true, bool writeFile = true)
        {
            // Cut the sample size in half if we are generating failure data sets.
            sampleSize = causeFailure ? sampleSize / 2 : sampleSize;

            // Generate normal pump operation:
            var motorPowerkW   = Generate.Sinusoidal(sampleSize, PumpNormalState.MotorPowerkW.SamplingRate, PumpNormalState.MotorPowerkW.Frequency, PumpNormalState.MotorPowerkW.Amplitude, PumpNormalState.MotorPowerkW.InitialValue);
            var motorSpeed     = Generate.Sinusoidal(sampleSize, PumpNormalState.MotorSpeed.SamplingRate, PumpNormalState.MotorSpeed.Frequency, PumpNormalState.MotorSpeed.Amplitude, PumpNormalState.MotorSpeed.InitialValue);
            var pumpRate       = Generate.Normal(sampleSize, PumpNormalState.PumpRate.InitialValue, PumpNormalState.PumpRate.StandardDeviation);
            var timePumpOn     = Generate.Periodic(sampleSize, PumpNormalState.TimePumpOn.SamplingRate, PumpNormalState.TimePumpOn.Frequency, PumpNormalState.TimePumpOn.Amplitude);
            var casingFriction = Generate.Normal(sampleSize, PumpNormalState.CasingFriction.InitialValue, PumpNormalState.CasingFriction.StandardDeviation);

            var telemetry = new PumpTelemetry();

            if (causeFailure)
            {
                // Generate failing pump operation:
                var motorPowerkWFailing   = Generate.Sinusoidal(sampleSize, PumpFailedState.MotorPowerkW.SamplingRate, PumpFailedState.MotorPowerkW.Frequency, PumpFailedState.MotorPowerkW.Amplitude, PumpFailedState.MotorPowerkW.InitialValue);
                var motorSpeedFailing     = Generate.Sinusoidal(sampleSize, PumpFailedState.MotorSpeed.SamplingRate, PumpFailedState.MotorSpeed.Frequency, PumpFailedState.MotorSpeed.Amplitude, PumpFailedState.MotorSpeed.InitialValue);
                var pumpRateFailing       = Generate.Normal(sampleSize, PumpFailedState.PumpRate.InitialValue, PumpFailedState.PumpRate.StandardDeviation);
                var timePumpOnFailing     = Generate.Periodic(sampleSize, PumpFailedState.TimePumpOn.SamplingRate, PumpFailedState.TimePumpOn.Frequency, PumpFailedState.TimePumpOn.Amplitude);
                var casingFrictionFailing = Generate.Normal(sampleSize, PumpFailedState.CasingFriction.InitialValue, PumpFailedState.CasingFriction.StandardDeviation);

                telemetry.GraduallyDeteriorateNormalToFailed(motorPowerkW, motorSpeed, pumpRate, timePumpOn,
                                                             casingFriction,
                                                             motorPowerkWFailing, motorSpeedFailing, pumpRateFailing, timePumpOnFailing, casingFrictionFailing,
                                                             failOverXIterations);
            }
            else
            {
                telemetry = new PumpTelemetry(motorPowerkW, motorSpeed, pumpRate, timePumpOn, casingFriction);
            }

            if (writeFile)
            {
                // Write to CSV file:
                var fileName = "TelemetryNormal.csv";
                if (causeFailure)
                {
                    fileName = failOverXIterations > 0
                        ? "TelemetryWithGradualFailures.csv"
                        : "TelemetryWithImmediateFailures.csv";
                }

                var telemetryItems = telemetry.ToPumpTelemetryItems();

                using (var writer = new StreamWriter(fileName))
                    using (var csv = new CsvWriter(writer))
                    {
                        csv.Configuration.RegisterClassMap <PumpTelemetryItemMap>();
                        csv.WriteRecords(telemetryItems);
                    }

                Console.WriteLine($"{fileName} successfully written.");
            }

            if (outputToConsole)
            {
                Console.WriteLine(JsonConvert.SerializeObject(telemetry));
            }
        }