// Copyright (C) Stichting Deltares 2016. All rights reserved.
//
// This file is part of Ringtoets.
//
// Ringtoets is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
// All names, logos, and references to "Deltares" are registered trademarks of
// Stichting Deltares and remain full property of Stichting Deltares at all times.
// All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security;
using Core.Common.Base.Data;
using Core.Common.Base.IO;
using Core.Common.IO.Exceptions;
using log4net;
using Ringtoets.Common.IO.HydraRing;
using Ringtoets.Common.Service;
using Ringtoets.HydraRing.Calculation.Calculator;
using Ringtoets.HydraRing.Calculation.Calculator.Factory;
using Ringtoets.HydraRing.Calculation.Data.Input;
using Ringtoets.HydraRing.Calculation.Data.Input.WaveConditions;
using Ringtoets.HydraRing.Calculation.Exceptions;
using Ringtoets.Revetment.Data;
using Ringtoets.Revetment.Service.Properties;
using RingtoetsCommonFormsResources = Ringtoets.Common.Forms.Properties.Resources;
using RingtoetsCommonServiceResources = Ringtoets.Common.Service.Properties.Resources;
namespace Ringtoets.Revetment.Service
{
///
/// Base class for calculating wave conditions for failure mechanisms.
///
public abstract class WaveConditionsCalculationServiceBase
{
private static readonly ILog log = LogManager.GetLogger(typeof(WaveConditionsCalculationServiceBase));
public OnProgressChanged OnProgress;
protected int TotalWaterLevelCalculations;
private int currentStep = 1;
private IWaveConditionsCosineCalculator calculator;
///
/// Cancels any currently running wave conditions calculation.
///
public void Cancel()
{
calculator?.Cancel();
Canceled = true;
}
///
/// Gets whether the calculation is canceled or not.
///
protected bool Canceled { get; private set; }
///
/// Performs validation over the values on the given .
/// Error and status information is logged during the execution of the operation.
///
/// The input of the calculation.
/// The name of the calculation.
/// The directory of the HLCD file that should be used for performing the calculation.
/// The name of the design water level property.
/// True if has no validation errors; False otherwise.
/// Thrown when
/// or is null.
protected static bool ValidateWaveConditionsInput(WaveConditionsInput waveConditionsInput,
string name,
string hydraulicBoundaryDatabaseFilePath,
string designWaterLevelName)
{
if (waveConditionsInput == null)
{
throw new ArgumentNullException(nameof(waveConditionsInput));
}
if (designWaterLevelName == null)
{
throw new ArgumentNullException(nameof(designWaterLevelName));
}
CalculationServiceHelper.LogValidationBeginTime(name);
string[] messages = ValidateInput(hydraulicBoundaryDatabaseFilePath, waveConditionsInput, designWaterLevelName);
CalculationServiceHelper.LogMessagesAsError(RingtoetsCommonServiceResources.Error_in_validation_0, messages);
CalculationServiceHelper.LogValidationEndTime(name);
return !messages.Any();
}
///
/// Performs a wave conditions calculation based on the supplied and returns the output.
/// Error and status information is logged during the execution of the operation.
///
/// The name of the calculation.
/// The that holds all the information required to perform the calculation.
/// The 'a' factor decided on failure mechanism level.
/// The 'b' factor decided on failure mechanism level.
/// The 'c' factor decided on failure mechanism level.
/// The norm to use as the target.
/// The id of the assessment section for which calculations are performed.
/// The filepath of the hydraulic boundary database.
/// An of .
/// Thrown when is null.
/// Thrown when the
/// contains invalid characters.
/// Thrown when:
///
/// - No settings database file could be found at the location of
/// with the same name.
/// - Unable to open settings database file.
/// - Unable to read required data from database file.
///
///
/// Thrown when the target probability or
/// calculated probability falls outside the [0.0, 1.0] range and is not .
/// Thrown when an error occurs during parsing of the Hydra-Ring output.
/// Thrown when an error occurs during the calculation.
protected IEnumerable CalculateWaveConditions(string calculationName,
WaveConditionsInput waveConditionsInput,
RoundedDouble a,
RoundedDouble b,
RoundedDouble c,
double norm,
string ringId,
string hrdFilePath)
{
if (waveConditionsInput == null)
{
throw new ArgumentNullException(nameof(waveConditionsInput));
}
var outputs = new List();
var waterLevels = waveConditionsInput.WaterLevels.ToArray();
foreach (var waterLevel in waterLevels.TakeWhile(waterLevel => !Canceled))
{
try
{
log.Info(string.Format(Resources.WaveConditionsCalculationService_OnRun_Subject_0_for_waterlevel_1_started,
calculationName,
waterLevel));
NotifyProgress(waterLevel, currentStep++, TotalWaterLevelCalculations);
var output = CalculateWaterLevel(waterLevel,
a,
b,
c,
norm,
waveConditionsInput,
hrdFilePath,
ringId,
calculationName);
if (output != null)
{
outputs.Add(output);
}
}
finally
{
log.Info(string.Format(Resources.WaveConditionsCalculationService_OnRun_Subject_0_for_waterlevel_1_ended,
calculationName,
waterLevel));
}
}
return outputs;
}
private static string[] ValidateInput(string hydraulicBoundaryDatabaseFilePath,
WaveConditionsInput input,
string designWaterLevelName)
{
var validationResults = new List();
string validationProblem = HydraulicDatabaseHelper.ValidatePathForCalculation(hydraulicBoundaryDatabaseFilePath);
if (!string.IsNullOrEmpty(validationProblem))
{
validationResults.Add(validationProblem);
}
else
{
string message = ValidateWaveConditionsInput(input, designWaterLevelName);
if (!string.IsNullOrEmpty(message))
{
validationResults.Add(message);
}
}
return validationResults.ToArray();
}
private void NotifyProgress(RoundedDouble waterLevel, int currentStepNumber, int totalStepsNumber)
{
var message = string.Format(Resources.WaveConditionsCalculationService_OnRun_Calculate_waterlevel_0_, waterLevel);
OnProgress?.Invoke(message, currentStepNumber, totalStepsNumber);
}
///
/// Calculates values for a single water level.
///
/// The level of the water.
/// The 'a' factor decided on failure mechanism level.
/// The 'b' factor decided on failure mechanism level.
/// The 'c' factor decided on failure mechanism level.
/// The norm to use as the target.
/// The input that is different per calculation.
/// The path which points to the hydraulic boundary database file.
/// The id of the assessment section for which calculations are performed.
/// The name used for logging.
/// A if the calculation was successful; or null if it was canceled.
/// Thrown when the
/// contains invalid characters.
/// Thrown when:
///
/// - No settings database file could be found at the location of
/// with the same name.
/// - Unable to open settings database file.
/// - Unable to read required data from database file.
///
/// Thrown when an error occurs while performing the calculation.
private WaveConditionsOutput CalculateWaterLevel(RoundedDouble waterLevel,
RoundedDouble a,
RoundedDouble b,
RoundedDouble c,
double norm,
WaveConditionsInput input,
string hydraulicBoundaryDatabaseFilePath,
string ringId,
string name)
{
string hlcdDirectory = Path.GetDirectoryName(hydraulicBoundaryDatabaseFilePath);
calculator = HydraRingCalculatorFactory.Instance.CreateWaveConditionsCosineCalculator(hlcdDirectory, ringId);
WaveConditionsCosineCalculationInput calculationInput = CreateInput(waterLevel, a, b, c, norm, input, hydraulicBoundaryDatabaseFilePath);
var exceptionThrown = false;
try
{
calculator.Calculate(calculationInput);
WaveConditionsOutput output = WaveConditionsService.Calculate(waterLevel,
calculator.WaveHeight,
calculator.WavePeakPeriod,
calculator.WaveAngle,
calculator.WaveDirection,
norm,
calculator.ReliabilityIndex);
return output;
}
catch (HydraRingCalculationException)
{
if (!Canceled)
{
var lastErrorContent = calculator.LastErrorFileContent;
if (string.IsNullOrEmpty(lastErrorContent))
{
log.ErrorFormat(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_0_for_waterlevel_1_no_error_report,
name,
waterLevel);
}
else
{
log.ErrorFormat(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_0_for_waterlevel_1_click_details_for_last_error_report_2,
name,
waterLevel,
lastErrorContent);
}
exceptionThrown = true;
throw;
}
return null;
}
finally
{
var lastErrorFileContent = calculator.LastErrorFileContent;
bool errorOccurred = CalculationServiceHelper.HasErrorOccurred(Canceled, exceptionThrown, lastErrorFileContent);
if (errorOccurred)
{
log.ErrorFormat(CultureInfo.CurrentCulture,
Resources.WaveConditionsCalculationService_CalculateWaterLevel_Error_in_wave_conditions_calculation_0_for_waterlevel_1_click_details_for_last_error_report_2,
name,
waterLevel,
lastErrorFileContent);
}
log.InfoFormat(Resources.WaveConditionsCalculationService_CalculateWaterLevel_Calculation_temporary_directory_can_be_found_on_location_0, calculator.OutputDirectory);
if (errorOccurred)
{
throw new HydraRingCalculationException(lastErrorFileContent);
}
}
}
///
/// Creates the input for a calculation for the given .
///
/// The level of the water.
/// The 'a' factor decided on failure mechanism level.
/// The 'b' factor decided on failure mechanism level.
/// The 'c' factor decided on failure mechanism level.
/// The norm to use as the target.
/// The input that is different per calculation.
/// The path to the hydraulic boundary database file.
/// A .
/// Thrown when the
/// contains invalid characters.
/// Thrown when:
///
/// - No settings database file could be found at the location of
/// with the same name.
/// - Unable to open settings database file.
/// - Unable to read required data from database file.
///
///
private static WaveConditionsCosineCalculationInput CreateInput(RoundedDouble waterLevel,
RoundedDouble a,
RoundedDouble b,
RoundedDouble c,
double norm,
WaveConditionsInput input,
string hydraulicBoundaryDatabaseFilePath)
{
var waveConditionsCosineCalculationInput = new WaveConditionsCosineCalculationInput(
1,
input.Orientation,
input.HydraulicBoundaryLocation.Id,
norm,
HydraRingInputParser.ParseForeshore(input),
HydraRingInputParser.ParseBreakWater(input),
waterLevel,
a,
b,
c);
HydraRingSettingsDatabaseHelper.AssignSettingsFromDatabase(waveConditionsCosineCalculationInput, hydraulicBoundaryDatabaseFilePath);
return waveConditionsCosineCalculationInput;
}
private static string ValidateWaveConditionsInput(WaveConditionsInput input, string designWaterLevelName)
{
if (input.HydraulicBoundaryLocation == null)
{
return Resources.WaveConditionsCalculationService_ValidateInput_No_HydraulicBoundaryLocation_selected;
}
if (double.IsNaN(input.HydraulicBoundaryLocation.DesignWaterLevel))
{
return string.Format(Resources.WaveConditionsCalculationService_ValidateInput_No_0_DesignWaterLevel_calculated, designWaterLevelName);
}
if (!input.WaterLevels.Any())
{
return Resources.WaveConditionsCalculationService_ValidateInput_No_derived_WaterLevels;
}
if (input.UseBreakWater)
{
if (double.IsInfinity(input.BreakWater.Height) || double.IsNaN(input.BreakWater.Height))
{
return RingtoetsCommonServiceResources.Validation_Invalid_BreakWaterHeight_value;
}
}
if (double.IsNaN(input.Orientation))
{
return string.Format(RingtoetsCommonServiceResources.Validation_ValidateInput_No_concrete_value_entered_for_ParameterName_0_,
ParameterNameExtractor.GetFromDisplayName(RingtoetsCommonFormsResources.Orientation_DisplayName));
}
return null;
}
}
}