/// <summary>
        /// Calculates values for a single water level.
        /// </summary>
        /// <param name="waterLevel">The level of the water.</param>
        /// <param name="a">The 'a' factor decided on failure mechanism level.</param>
        /// <param name="b">The 'b' factor decided on failure mechanism level.</param>
        /// <param name="c">The 'c' factor decided on failure mechanism level.</param>
        /// <param name="targetProbability">The target probability to use.</param>
        /// <param name="input">The input that is different per calculation.</param>
        /// <param name="calculationSettings">The <see cref="HydraulicBoundaryCalculationSettings"/> containing all data
        /// to perform a hydraulic boundary calculation.</param>
        /// <returns>A <see cref="WaveConditionsOutput"/> if the calculation was successful; or <c>null</c> if it was canceled.</returns>
        /// <remarks>Preprocessing is disabled when the preprocessor directory equals <see cref="string.Empty"/>.</remarks>
        /// <exception cref="ArgumentException">Thrown when the hydraulic boundary database file path
        /// contains invalid characters.</exception>
        /// <exception cref="CriticalFileReadException">Thrown when:
        /// <list type="bullet">
        /// <item>No settings database file could be found at the location of the hydraulic boundary database file path
        /// with the same name.</item>
        /// <item>Unable to open settings database file.</item>
        /// <item>Unable to read required data from database file.</item>
        /// </list>
        /// </exception>
        private WaveConditionsOutput CalculateWaterLevel(RoundedDouble waterLevel,
                                                         RoundedDouble a,
                                                         RoundedDouble b,
                                                         RoundedDouble c,
                                                         double targetProbability,
                                                         WaveConditionsInput input,
                                                         HydraulicBoundaryCalculationSettings calculationSettings)
        {
            HydraRingCalculationSettings settings = HydraRingCalculationSettingsFactory.CreateSettings(calculationSettings);

            calculator = HydraRingCalculatorFactory.Instance.CreateWaveConditionsCosineCalculator(settings);
            WaveConditionsCosineCalculationInput calculationInput = CreateInput(waterLevel, a, b, c, targetProbability, input, calculationSettings);

            WaveConditionsOutput output;
            var exceptionThrown = false;

            try
            {
                calculator.Calculate(calculationInput);

                output = WaveConditionsOutputFactory.CreateOutput(waterLevel,
                                                                  calculator.WaveHeight,
                                                                  calculator.WavePeakPeriod,
                                                                  calculator.WaveAngle,
                                                                  calculator.WaveDirection,
                                                                  targetProbability,
                                                                  calculator.ReliabilityIndex,
                                                                  calculator.Converged);
            }
            catch (Exception e) when(e is HydraRingCalculationException || e is ArgumentOutOfRangeException)
            {
                if (!Canceled)
                {
                    string lastErrorContent = calculator.LastErrorFileContent;
                    if (string.IsNullOrEmpty(lastErrorContent))
                    {
                        log.ErrorFormat(CultureInfo.CurrentCulture,
                                        Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_waterlevel_0_no_error_report,
                                        waterLevel);
                    }
                    else
                    {
                        log.ErrorFormat(CultureInfo.CurrentCulture,
                                        Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_waterlevel_0_click_details_for_last_error_report_1,
                                        waterLevel,
                                        lastErrorContent);
                    }

                    exceptionThrown = true;
                }

                output = null;
            }
            finally
            {
                string lastErrorFileContent = calculator.LastErrorFileContent;
                bool   errorOccurred        = CalculationServiceHelper.HasErrorOccurred(Canceled, exceptionThrown, lastErrorFileContent);
                if (errorOccurred)
                {
                    log.ErrorFormat(CultureInfo.CurrentCulture,
                                    Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_waterlevel_0_click_details_for_last_error_report_1,
                                    waterLevel,
                                    lastErrorFileContent);
                }

                log.InfoFormat(CultureInfo.CurrentCulture,
                               Resources.WaveConditionsCalculationService_CalculateWaterLevel_Calculation_temporary_directory_can_be_found_on_location_0,
                               calculator.OutputDirectory);

                if (errorOccurred)
                {
                    output = null;
                }
            }

            return(output);
        }
        /// <summary>
        /// Performs a wave conditions calculation based on the supplied <see cref="WaveConditionsInput"/>
        /// and returns the output. Error and status information is logged during the execution
        /// of the operation.
        /// </summary>
        /// <param name="waveConditionsInput">The <see cref="WaveConditionsInput"/> that holds all the information
        /// required to perform the calculation.</param>
        /// <param name="assessmentLevel">The assessment level to use for determining water levels.</param>
        /// <param name="a">The 'a' factor decided on failure mechanism level.</param>
        /// <param name="b">The 'b' factor decided on failure mechanism level.</param>
        /// <param name="c">The 'c' factor decided on failure mechanism level.</param>
        /// <param name="targetProbability">The target probability to use.</param>
        /// <param name="hydraulicBoundaryDatabase">The hydraulic boundary database to perform the calculations with.</param>
        /// <returns>An <see cref="IEnumerable{T}"/> of <see cref="WaveConditionsOutput"/>.</returns>
        /// <remarks>Preprocessing is disabled when the preprocessor directory equals <see cref="string.Empty"/>.</remarks>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="waveConditionsInput"/> or
        /// <paramref name="hydraulicBoundaryDatabase"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">Thrown when the hydraulic boundary database file path
        /// contains invalid characters.</exception>
        /// <exception cref="CriticalFileReadException">Thrown when:
        /// <list type="bullet">
        /// <item>No settings database file could be found at the location of the hydraulic boundary database file path
        /// with the same name.</item>
        /// <item>Unable to open settings database file.</item>
        /// <item>Unable to read required data from database file.</item>
        /// </list>
        /// </exception>
        /// <exception cref="HydraRingCalculationException">Thrown when an error occurs during
        /// the calculations.</exception>
        protected IEnumerable <WaveConditionsOutput> CalculateWaveConditions(WaveConditionsInput waveConditionsInput,
                                                                             RoundedDouble assessmentLevel,
                                                                             RoundedDouble a,
                                                                             RoundedDouble b,
                                                                             RoundedDouble c,
                                                                             double targetProbability,
                                                                             HydraulicBoundaryDatabase hydraulicBoundaryDatabase)
        {
            if (waveConditionsInput == null)
            {
                throw new ArgumentNullException(nameof(waveConditionsInput));
            }

            if (hydraulicBoundaryDatabase == null)
            {
                throw new ArgumentNullException(nameof(hydraulicBoundaryDatabase));
            }

            var calculationsFailed = 0;
            var outputs            = new List <WaveConditionsOutput>();

            RoundedDouble[] waterLevels = waveConditionsInput.GetWaterLevels(assessmentLevel).ToArray();
            foreach (RoundedDouble waterLevel in waterLevels.TakeWhile(waterLevel => !Canceled))
            {
                try
                {
                    log.Info(string.Format(CultureInfo.CurrentCulture,
                                           Resources.WaveConditionsCalculationService_OnRun_Calculation_for_waterlevel_0_started,
                                           waterLevel));

                    NotifyProgress(waterLevel, currentStep++, TotalWaterLevelCalculations);

                    WaveConditionsOutput output = CalculateWaterLevel(waterLevel,
                                                                      a, b, c, targetProbability,
                                                                      waveConditionsInput,
                                                                      HydraulicBoundaryCalculationSettingsFactory.CreateSettings(hydraulicBoundaryDatabase));

                    if (output != null)
                    {
                        outputs.Add(output);
                    }
                    else
                    {
                        calculationsFailed++;
                        outputs.Add(WaveConditionsOutputFactory.CreateFailedOutput(waterLevel, targetProbability));
                    }
                }
                finally
                {
                    log.Info(string.Format(CultureInfo.CurrentCulture,
                                           Resources.WaveConditionsCalculationService_OnRun_Calculation_for_waterlevel_0_ended,
                                           waterLevel));
                }
            }

            if (calculationsFailed == waterLevels.Length)
            {
                string message = string.Format(CultureInfo.CurrentCulture,
                                               Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_for_all_waterlevels);
                log.Error(message);
                throw new HydraRingCalculationException(message);
            }

            return(outputs);
        }