/// <summary>
 /// Initializes a new instance of the <see cref="FileBackedProcessingPipelineStageSetting{T}"/> class.
 /// </summary>
 /// <param name="rawSetting">The corresponding raw setting in the configuration.</param>
 /// <param name="valueToStringConverter">Delegate that converts a string to the setting value.</param>
 /// <param name="stringToValueConverter">Delegate that converts the setting value to a string.</param>
 internal FileBackedProcessingPipelineStageSetting(
     FileBackedProcessingPipelineStageRawSetting rawSetting,
     ObjectToStringConversionDelegate <T> valueToStringConverter,
     StringToObjectConversionDelegate <T> stringToValueConverter)
 {
     mRawSetting             = rawSetting;
     mValueToStringConverter = valueToStringConverter;
     mStringToValueConverter = stringToValueConverter;
 }
        /// <summary>
        /// Sets the setting with the specified name (supports custom types using the specified converters).
        /// Creates a new setting, if it does not exist, yet.
        /// </summary>
        /// <typeparam name="T">Type of the setting.</typeparam>
        /// <param name="name">
        /// Name of the setting. The following characters are allowed:
        /// - alphanumeric characters ( a-z, A-Z, 0-9 )
        /// - square brackets ( [] )
        /// - Period (.)
        /// </param>
        /// <param name="value">New value of the setting.</param>
        /// <param name="valueToStringConverter">Delegate that converts the object to its string representation.</param>
        /// <param name="stringToValueConverter">Delegate that converts the string representation to an object of the type <typeparamref name="T"/>.</param>
        /// <returns>The setting.</returns>
        /// <exception cref="ArgumentNullException">
        /// The argument <paramref name="name"/>, <paramref name="valueToStringConverter"/> and/or <paramref name="stringToValueConverter"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The setting exists already, but the specified type differs from the value type of the existing setting.
        /// </exception>
        /// <exception cref="FormatException">
        /// The <paramref name="name"/> is not a valid setting name.
        /// </exception>
        public override IProcessingPipelineStageSetting <T> SetSetting <T>(
            string name,
            T value,
            ObjectToStringConversionDelegate <T> valueToStringConverter,
            StringToObjectConversionDelegate <T> stringToValueConverter)
        {
            // check arguments
            if (valueToStringConverter == null)
            {
                throw new ArgumentNullException(nameof(valueToStringConverter));
            }
            if (stringToValueConverter == null)
            {
                throw new ArgumentNullException(nameof(stringToValueConverter));
            }
            CheckSettingName(name);

            lock (Sync)
            {
                if (!mSettings.TryGetValue(name, out var setting))
                {
                    // the setting was not requested before
                    // => create a setting
                    var rawSetting = new FileBackedProcessingPipelineStageRawSetting(this, name);
                    setting = new FileBackedProcessingPipelineStageSetting <T>(rawSetting, valueToStringConverter, stringToValueConverter);
                    mSettings.Add(name, setting);
                }

                // setting with the name exists

                // ensure that the setting value types are the same
                if (setting.ValueType != typeof(T))
                {
                    string message = $"The setting exists already, but the specified types ({typeof(T).FullName}) differs from the value type of the existing setting ({setting.ValueType.FullName}).";
                    throw new ArgumentException(message);
                }

                // set the value
                setting.Value = value;

                return((IProcessingPipelineStageSetting <T>)setting);
            }
        }
        /// <summary>
        /// Registers the setting with the specified name (supports custom types using the specified converters).
        /// Creates a new setting with the specified value, if the setting does not exist.
        /// </summary>
        /// <typeparam name="T">Type of the setting.</typeparam>
        /// <param name="name">
        /// Name of the setting. The following characters are allowed:
        /// - alphanumeric characters ( a-z, A-Z, 0-9 )
        /// - square brackets ( [] )
        /// - Period (.)
        /// </param>
        /// <param name="defaultValue">Value of the setting, if the setting does not exist, yet.</param>
        /// <param name="valueToStringConverter">Delegate that converts a setting value to its string representation.</param>
        /// <param name="stringToValueConverter">Delegate that converts the string representation of a setting value to an object of the specified type.</param>
        /// <returns>The setting.</returns>
        /// <exception cref="ArgumentNullException">
        /// The argument <paramref name="name"/>, <paramref name="valueToStringConverter"/> and/or <paramref name="stringToValueConverter"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The setting exists already, but the specified type differs from the value type of the existing setting.
        /// </exception>
        /// <exception cref="FormatException">
        /// The <paramref name="name"/> is not a valid setting name.
        /// </exception>
        public override IProcessingPipelineStageSetting <T> RegisterSetting <T>(
            string name,
            T defaultValue,
            ObjectToStringConversionDelegate <T> valueToStringConverter,
            StringToObjectConversionDelegate <T> stringToValueConverter)
        {
            // check arguments
            if (valueToStringConverter == null)
            {
                throw new ArgumentNullException(nameof(valueToStringConverter));
            }
            if (stringToValueConverter == null)
            {
                throw new ArgumentNullException(nameof(stringToValueConverter));
            }
            CheckSettingName(name);

            lock (Sync)
            {
                FileBackedProcessingPipelineStageRawSetting rawSetting;
                bool isNewSetting = false;
                if (!mSettings.TryGetValue(name, out var setting))
                {
                    // the setting was not requested before
                    // => create a setting
                    isNewSetting = true;
                    rawSetting   = new FileBackedProcessingPipelineStageRawSetting(this, name, valueToStringConverter(defaultValue, CultureInfo.InvariantCulture));
                    setting      = new FileBackedProcessingPipelineStageSetting <T>(rawSetting, valueToStringConverter, stringToValueConverter);
                    mSettings.Add(name, setting);
                }

                // setting with the name exists

                // ensure that the setting value types are the same
                if (setting.ValueType != typeof(T))
                {
                    string message = $"The setting exists already, but the specified types ({typeof(T).FullName}) differs from the value type of the existing setting ({setting.ValueType.FullName}).";
                    throw new ArgumentException(message);
                }

                // set the default value of the raw item, if it is not already set
                // (this can happen, if a setting has been created without a default value before)
                rawSetting = ((FileBackedProcessingPipelineStageSetting <T>)setting).Raw;
                if (!rawSetting.HasDefaultValue)
                {
                    rawSetting.DefaultValue = valueToStringConverter(defaultValue, CultureInfo.InvariantCulture);
                }

                // ensure that the setting default values are the same
                string settingDefaultValueAsString = valueToStringConverter(((FileBackedProcessingPipelineStageSetting <T>)setting).DefaultValue, CultureInfo.InvariantCulture);
                string defaultValueAsString        = valueToStringConverter(defaultValue, CultureInfo.InvariantCulture);
                if (settingDefaultValueAsString != defaultValueAsString)
                {
                    string message = $"The setting exists already, but the specified default value ({defaultValueAsString}) does not match the default value of the existing setting ({settingDefaultValueAsString}).";
                    throw new ArgumentException(message);
                }

                // the setting with the specified name has been registered successfully
                // => notify, if a new setting was added (changes are handled differently)
                if (isNewSetting)
                {
                    LogConfiguration.OnChanged();
                }

                return((IProcessingPipelineStageSetting <T>)setting);
            }
        }