#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more 
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership. 
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with 
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

using log4net.Config;
using log4net.Util;
using log4net.Repository;

namespace log4net.Core;

/// <summary>
/// The default implementation of the <see cref="IRepositorySelector"/> interface.
/// </summary>
/// <remarks>
/// <para>
/// Uses attributes defined on the calling assembly to determine how to
/// configure the hierarchy for the repository.
/// </para>
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
public class DefaultRepositorySelector : IRepositorySelector
{
  /// <summary>
  /// Event to notify that a logger repository has been created.
  /// </summary>
  /// <value>
  /// Event to notify that a logger repository has been created.
  /// </value>
  /// <remarks>
  /// <para>
  /// Event raised when a new repository is created.
  /// The event source will be this selector. The event args will
  /// be a <see cref="LoggerRepositoryCreationEventArgs"/> which
  /// holds the newly created <see cref="ILoggerRepository"/>.
  /// </para>
  /// </remarks>
  public event LoggerRepositoryCreationEventHandler? LoggerRepositoryCreatedEvent;

  /// <summary>
  /// Creates a new repository selector.
  /// </summary>
  /// <param name="defaultRepositoryType">The type of the repositories to create, must implement <see cref="ILoggerRepository"/></param>
  /// <remarks>
  /// <para>
  /// Create a new repository selector.
  /// The default type for repositories must be specified,
  /// an appropriate value would be <see cref="Repository.Hierarchy.Hierarchy"/>.
  /// </para>
  /// </remarks>
  /// <exception cref="ArgumentNullException"><paramref name="defaultRepositoryType"/> is <see langword="null" />.</exception>
  /// <exception cref="ArgumentOutOfRangeException"><paramref name="defaultRepositoryType"/> does not implement <see cref="ILoggerRepository"/>.</exception>
  public DefaultRepositorySelector([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type defaultRepositoryType)
  {
    // Check that the type is a repository
    if (!typeof(ILoggerRepository).IsAssignableFrom(defaultRepositoryType.EnsureNotNull()))
    {
      throw SystemInfo.CreateArgumentOutOfRangeException("defaultRepositoryType", defaultRepositoryType, 
        $"Parameter: defaultRepositoryType, Value: [{defaultRepositoryType}] out of range. Argument must implement the ILoggerRepository interface");
    }

    this._defaultRepositoryType = defaultRepositoryType;

    LogLog.Debug(_declaringType, $"defaultRepositoryType [{this._defaultRepositoryType}]");
  }

  /// <summary>
  /// Gets the <see cref="ILoggerRepository"/> for the specified assembly.
  /// </summary>
  /// <param name="assembly">The assembly use to look up the <see cref="ILoggerRepository"/>.</param>
  /// <remarks>
  /// <para>
  /// The type of the <see cref="ILoggerRepository"/> created and the repository 
  /// to create can be overridden by specifying the <see cref="RepositoryAttribute"/> 
  /// attribute on the <paramref name="assembly"/>.
  /// </para>
  /// <para>
  /// The default values are to use the <see cref="Repository.Hierarchy.Hierarchy"/> 
  /// implementation of the <see cref="ILoggerRepository"/> interface and to use the
  /// <see cref="AssemblyName.Name"/> as the name of the repository.
  /// </para>
  /// <para>
  /// The <see cref="ILoggerRepository"/> created will be automatically configured using 
  /// any <see cref="ConfiguratorAttribute"/> attributes defined on
  /// the <paramref name="assembly"/>.
  /// </para>
  /// </remarks>
  /// <returns>The <see cref="ILoggerRepository"/> for the assembly</returns>
  /// <exception cref="ArgumentNullException"><paramref name="assembly"/> is <see langword="null" />.</exception>
  public ILoggerRepository GetRepository(Assembly assembly)
    => CreateRepository(assembly.EnsureNotNull(), _defaultRepositoryType);

  /// <summary>
  /// Gets the <see cref="ILoggerRepository"/> for the specified repository.
  /// </summary>
  /// <param name="repositoryName">The repository to use to look up the <see cref="ILoggerRepository"/>.</param>
  /// <returns>The <see cref="ILoggerRepository"/> for the specified repository.</returns>
  /// <remarks>
  /// <para>
  /// Returns the named repository. If <paramref name="repositoryName"/> is <see langword="null"/>
  /// a <see cref="ArgumentNullException"/> is thrown. If the repository 
  /// does not exist a <see cref="LogException"/> is thrown.
  /// </para>
  /// <para>
  /// Use <see cref="CreateRepository(string, Type)"/> to create a repository.
  /// </para>
  /// </remarks>
  /// <exception cref="ArgumentNullException"><paramref name="repositoryName"/> is <see langword="null" />.</exception>
  /// <exception cref="LogException"><paramref name="repositoryName"/> does not exist.</exception>
  public ILoggerRepository GetRepository(string repositoryName)
  {
    lock (_syncRoot)
    {
      if (_name2Repository.TryGetValue(repositoryName.EnsureNotNull(), out ILoggerRepository? rep))
      {
        return rep;
      }
      throw new LogException($"Repository [{repositoryName}] is NOT defined.");
    }
  }

  /// <summary>
  /// Creates a new repository for the assembly specified 
  /// </summary>
  /// <param name="assembly">the assembly to use to create the repository to associate with the <see cref="ILoggerRepository"/>.</param>
  /// <param name="repositoryType">The type of repository to create, must implement <see cref="ILoggerRepository"/>.</param>
  /// <returns>The repository created.</returns>
  /// <remarks>
  /// <para>
  /// The <see cref="ILoggerRepository"/> created will be associated with the repository
  /// specified such that a call to <see cref="GetRepository(Assembly)"/> with the
  /// same assembly specified will return the same repository instance.
  /// </para>
  /// <para>
  /// The type of the <see cref="ILoggerRepository"/> created and
  /// the repository to create can be overridden by specifying the
  /// <see cref="RepositoryAttribute"/> attribute on the 
  /// <paramref name="assembly"/>.  The default values are to use the 
  /// <paramref name="repositoryType"/> implementation of the 
  /// <see cref="ILoggerRepository"/> interface and to use the
  /// <see cref="AssemblyName.Name"/> as the name of the repository.
  /// </para>
  /// <para>
  /// The <see cref="ILoggerRepository"/> created will be automatically
  /// configured using any <see cref="ConfiguratorAttribute"/> 
  /// attributes defined on the <paramref name="assembly"/>.
  /// </para>
  /// <para>
  /// If a repository for the <paramref name="assembly"/> already exists
  /// that repository will be returned. An error will not be raised and that 
  /// repository may be of a different type to that specified in <paramref name="repositoryType"/>.
  /// Also the <see cref="RepositoryAttribute"/> attribute on the
  /// assembly may be used to override the repository type specified in 
  /// <paramref name="repositoryType"/>.
  /// </para>
  /// </remarks>
  /// <exception cref="ArgumentNullException"><paramref name="assembly"/> is <see langword="null" />.</exception>
  public ILoggerRepository CreateRepository(Assembly assembly, 
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type repositoryType) 
    => CreateRepository(assembly, repositoryType, DefaultRepositoryName, true);

  /// <summary>
  /// Creates a new repository for the assembly specified.
  /// </summary>
  /// <param name="repositoryAssembly">the assembly to use to create the repository to associate with the <see cref="ILoggerRepository"/>.</param>
  /// <param name="repositoryType">The type of repository to create, must implement <see cref="ILoggerRepository"/>.</param>
  /// <param name="repositoryName">The name to assign to the created repository</param>
  /// <param name="readAssemblyAttributes">Set to <see langword="true"/> to read and apply the assembly attributes</param>
  /// <returns>The repository created.</returns>
  /// <remarks>
  /// <para>
  /// The <see cref="ILoggerRepository"/> created will be associated with the repository
  /// specified such that a call to <see cref="GetRepository(Assembly)"/> with the
  /// same assembly specified will return the same repository instance.
  /// </para>
  /// <para>
  /// The type of the <see cref="ILoggerRepository"/> created and
  /// the repository to create can be overridden by specifying the
  /// <see cref="RepositoryAttribute"/> attribute on the 
  /// <paramref name="repositoryAssembly"/>.  The default values are to use the 
  /// <paramref name="repositoryType"/> implementation of the 
  /// <see cref="ILoggerRepository"/> interface and to use the
  /// <see cref="AssemblyName.Name"/> as the name of the repository.
  /// </para>
  /// <para>
  /// The <see cref="ILoggerRepository"/> created will be automatically
  /// configured using any <see cref="ConfiguratorAttribute"/> 
  /// attributes defined on the <paramref name="repositoryAssembly"/>.
  /// </para>
  /// <para>
  /// If a repository for the <paramref name="repositoryAssembly"/> already exists
  /// that repository will be returned. An error will not be raised and that 
  /// repository may be of a different type to that specified in <paramref name="repositoryType"/>.
  /// Also the <see cref="RepositoryAttribute"/> attribute on the
  /// assembly may be used to override the repository type specified in 
  /// <paramref name="repositoryType"/>.
  /// </para>
  /// </remarks>
  /// <exception cref="ArgumentNullException"><paramref name="repositoryAssembly"/> is <see langword="null" />.</exception>
  public ILoggerRepository CreateRepository(Assembly repositoryAssembly, 
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? repositoryType, 
    string repositoryName, bool readAssemblyAttributes)
  {
    repositoryAssembly.EnsureNotNull();

    repositoryType ??= _defaultRepositoryType;

    lock (_syncRoot)
    {
      if (!_assembly2Repository.TryGetValue(repositoryAssembly, out ILoggerRepository? rep))
      {
        // Not found, therefore create
        LogLog.Debug(_declaringType, $"Creating repository for assembly [{repositoryAssembly}]");

        // Must specify defaults
        string actualRepositoryName = repositoryName;
        Type actualRepositoryType = repositoryType;

        if (readAssemblyAttributes)
        {
          // Get the repository and type from the assembly attributes
          GetInfoForAssembly(repositoryAssembly, ref actualRepositoryName, ref actualRepositoryType);
        }

        LogLog.Debug(_declaringType, $"Assembly [{repositoryAssembly}] using repository [{actualRepositoryName}] and repository type [{actualRepositoryType}]");

        // Lookup the repository in the map (as this may already be defined)
        if (!_name2Repository.TryGetValue(actualRepositoryName, out rep))
        {
          // Create the repository
          rep = CreateRepository(actualRepositoryName, actualRepositoryType);

          if (readAssemblyAttributes)
          {
            try
            {
              // Look for aliasing attributes
              LoadAliases(repositoryAssembly, rep);

              // Look for plugins defined on the assembly
              LoadPlugins(repositoryAssembly, rep);

              // Configure the repository using the assembly attributes
              ConfigureRepository(repositoryAssembly, rep);
            }
            catch (Exception e) when (!e.IsFatal())
            {
              LogLog.Error(_declaringType, $"Failed to configure repository [{actualRepositoryName}] from assembly attributes.", e);
            }
          }
        }
        else
        {
          LogLog.Debug(_declaringType, $"repository [{actualRepositoryName}] already exists, using repository type [{rep.GetType().FullName}]");

          if (readAssemblyAttributes)
          {
            try
            {
              // Look for plugins defined on the assembly
              LoadPlugins(repositoryAssembly, rep);
            }
            catch (Exception e) when (!e.IsFatal())
            {
              LogLog.Error(_declaringType, $"Failed to configure repository [{actualRepositoryName}] from assembly attributes.", e);
            }
          }
        }
        _assembly2Repository[repositoryAssembly] = rep;
      }
      return rep;
    }
  }

  /// <summary>
  /// Creates a new repository for the specified repository.
  /// </summary>
  /// <param name="repositoryName">The repository to associate with the <see cref="ILoggerRepository"/>.</param>
  /// <param name="repositoryType">The type of repository to create, must implement <see cref="ILoggerRepository"/>.
  /// If this param is <see langword="null" /> then the default repository type is used.</param>
  /// <returns>The new repository.</returns>
  /// <remarks>
  /// <para>
  /// The <see cref="ILoggerRepository"/> created will be associated with the repository
  /// specified such that a call to <see cref="GetRepository(string)"/> with the
  /// same repository specified will return the same repository instance.
  /// </para>
  /// </remarks>
  /// <exception cref="ArgumentNullException"><paramref name="repositoryName"/> is <see langword="null" />.</exception>
  /// <exception cref="LogException"><paramref name="repositoryName"/> already exists.</exception>
  public ILoggerRepository CreateRepository(string repositoryName, 
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? repositoryType)
  {
    repositoryName.EnsureNotNull();

    // If the type is not set then use the default type
    repositoryType ??= _defaultRepositoryType;

    lock (_syncRoot)
    {
      ILoggerRepository? rep = null;

      // First check that the repository does not exist
      if (_name2Repository.ContainsKey(repositoryName))
      {
        throw new LogException($"Repository [{repositoryName}] is already defined. Repositories cannot be redefined.");
      }

      // Look up an alias before trying to create the new repository
      if (_alias2Repository.TryGetValue(repositoryName, out ILoggerRepository? aliasedRepository))
      {
        // Found an alias

        // Check repository type
        if (aliasedRepository.GetType() == repositoryType)
        {
          // Repository type is compatible
          LogLog.Debug(_declaringType, $"Aliasing repository [{repositoryName}] to existing repository [{aliasedRepository.Name}]");
          rep = aliasedRepository;

          // Store in map
          _name2Repository[repositoryName] = rep;
        }
        else
        {
          // Invalid repository type for alias
          LogLog.Error(_declaringType, $"Failed to alias repository [{repositoryName}] to existing repository [{aliasedRepository.Name}]. Requested repository type [{repositoryType.FullName}] is not compatible with existing type [{aliasedRepository.GetType().FullName}]");

          // We now drop through to create the repository without aliasing
        }
      }

      // If we could not find an alias
      if (rep is null)
      {
        LogLog.Debug(_declaringType, $"Creating repository [{repositoryName}] using type [{repositoryType}]");

        // Call the no arg constructor for the repositoryType
        rep = Activator.CreateInstance(repositoryType).EnsureIs<ILoggerRepository>();

        // Set the name of the repository
        rep.Name = repositoryName;

        // Store in map
        _name2Repository[repositoryName] = rep;

        // Notify listeners that the repository has been created
        OnLoggerRepositoryCreatedEvent(rep);
      }

      return rep;
    }
  }

  /// <summary>
  /// Test if a named repository exists
  /// </summary>
  /// <param name="repositoryName">the named repository to check</param>
  /// <returns><see langword="true"/> if the repository exists</returns>
  /// <remarks>
  /// <para>
  /// Test if a named repository exists. Use <see cref="CreateRepository(string, Type)"/>
  /// to create a new repository and <see cref="GetRepository(string)"/> to retrieve 
  /// a repository.
  /// </para>
  /// </remarks>
  public bool ExistsRepository(string repositoryName)
  {
    lock (_syncRoot)
    {
      return _name2Repository.ContainsKey(repositoryName);
    }
  }

  /// <summary>
  /// Gets a list of <see cref="ILoggerRepository"/> objects
  /// </summary>
  /// <returns>an array of all known <see cref="ILoggerRepository"/> objects</returns>
  /// <remarks>
  /// <para>
  /// Gets an array of all repositories created by this selector.
  /// </para>
  /// </remarks>
  public ILoggerRepository[] GetAllRepositories()
  {
    lock (_syncRoot)
    {
      return _name2Repository.Values.ToArray();
    }
  }

  /// <summary>
  /// Aliases a repository to an existing repository.
  /// </summary>
  /// <param name="repositoryAlias">The repository to alias.</param>
  /// <param name="repositoryTarget">The repository that the repository is aliased to.</param>
  /// <remarks>
  /// <para>
  /// The repository specified will be aliased to the repository when created. 
  /// The repository must not already exist.
  /// </para>
  /// <para>
  /// When the repository is created it must utilize the same repository type as 
  /// the repository it is aliased to, otherwise the aliasing will fail.
  /// </para>
  /// </remarks>
  /// <exception cref="ArgumentNullException">
  ///  <para><paramref name="repositoryAlias" /> is <see langword="null" />.</para>
  ///  <para>-or-</para>
  ///  <para><paramref name="repositoryTarget" /> is <see langword="null" />.</para>
  /// </exception>
  public void AliasRepository(string repositoryAlias, ILoggerRepository repositoryTarget)
  {
    repositoryAlias.EnsureNotNull();
    repositoryTarget.EnsureNotNull();

    lock (_syncRoot)
    {
      // Check if the alias is already set
      if (_alias2Repository.TryGetValue(repositoryAlias, out ILoggerRepository? existingTarget))
      {
        // Check if this is a duplicate of the current alias
        if (repositoryTarget != existingTarget)
        {
          // Cannot redefine existing alias
          throw new InvalidOperationException($"Repository [{repositoryAlias}] is already aliased to repository [{existingTarget.Name}]. Aliases cannot be redefined.");
        }
      }
      // Check if the alias is already mapped to a repository
      else if (_name2Repository.TryGetValue(repositoryAlias, out existingTarget))
      {
        // Check if this is a duplicate of the current mapping
        if (repositoryTarget != existingTarget)
        {
          // Cannot define alias for already mapped repository
          throw new InvalidOperationException($"Repository [{repositoryAlias}] already exists and cannot be aliased to repository [{repositoryTarget.Name}].");
        }
      }
      else
      {
        // Set the alias
        _alias2Repository[repositoryAlias] = repositoryTarget;
      }
    }
  }

  /// <summary>
  /// Notifies the registered listeners that the repository has been created.
  /// </summary>
  /// <param name="repository">The repository that has been created.</param>
  /// <remarks>
  /// <para>
  /// Raises the <see cref="LoggerRepositoryCreatedEvent"/> event.
  /// </para>
  /// </remarks>
  protected virtual void OnLoggerRepositoryCreatedEvent(ILoggerRepository repository) 
    => LoggerRepositoryCreatedEvent?.Invoke(this, new(repository));

  /// <summary>
  /// Gets the repository name and repository type for the specified assembly.
  /// </summary>
  /// <param name="assembly">The assembly that has a <see cref="RepositoryAttribute"/>.</param>
  /// <param name="repositoryName">in/out param to hold the repository name to use for the assembly, caller should set this to the default value before calling.</param>
  /// <param name="repositoryType">in/out param to hold the type of the repository to create for the assembly, caller should set this to the default value before calling.</param>
  /// <exception cref="ArgumentNullException"><paramref name="assembly" /> is <see langword="null" />.</exception>
  private void GetInfoForAssembly(Assembly assembly, ref string repositoryName,
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] ref Type repositoryType)
  {
    assembly.EnsureNotNull();

    try
    {
      LogLog.Debug(_declaringType, $"Assembly [{assembly.FullName}] Loaded From [{SystemInfo.AssemblyLocationInfo(assembly)}]");
    }
    catch (Exception e) when (!e.IsFatal())
    {
      // Ignore exception from debug call
    }

    try
    {
      // Look for the RepositoryAttribute on the assembly 
      object[] repositoryAttributes = Attribute.GetCustomAttributes(assembly, typeof(RepositoryAttribute), false);
      if (repositoryAttributes.Length == 0)
      {
        // This is not a problem, but it's nice to know what is going on.
        LogLog.Debug(_declaringType, $"Assembly [{assembly}] does not have a {nameof(RepositoryAttribute)} specified.");
      }
      else
      {
        if (repositoryAttributes.Length > 1)
        {
          LogLog.Error(_declaringType, $"Assembly [{assembly}] has multiple log4net.Config.RepositoryAttribute assembly attributes. Only using first occurrence.");
        }

        RepositoryAttribute domAttr = (RepositoryAttribute)repositoryAttributes[0];
        // If the Name property is set then override the default
        if (domAttr.Name is not null)
        {
          repositoryName = domAttr.Name;
        }

        // If the RepositoryType property is set then override the default
        if (domAttr.RepositoryType is not null)
        {
          // Check that the type is a repository
          if (typeof(ILoggerRepository).IsAssignableFrom(domAttr.RepositoryType))
          {
            repositoryType = domAttr.RepositoryType;
          }
          else
          {
            LogLog.Error(_declaringType, $"DefaultRepositorySelector: Repository Type [{domAttr.RepositoryType}] must implement the ILoggerRepository interface.");
          }
        }
      }
    }
    catch (Exception e) when (!e.IsFatal())
    {
      LogLog.Error(_declaringType, "Unhandled exception in GetInfoForAssembly", e);
    }
  }

  /// <summary>
  /// Configures the repository using information from the assembly.
  /// </summary>
  /// <param name="assembly">The assembly containing <see cref="ConfiguratorAttribute"/>
  /// attributes which define the configuration for the repository.</param>
  /// <param name="repository">The repository to configure.</param>
  /// <exception cref="ArgumentNullException">
  ///  <para><paramref name="assembly" /> is <see langword="null" />.</para>
  ///  <para>-or-</para>
  ///  <para><paramref name="repository" /> is <see langword="null" />.</para>
  /// </exception>
  private static void ConfigureRepository(Assembly assembly, ILoggerRepository repository)
  {
    repository.EnsureNotNull();

    // Look for the Configurator attributes (e.g. XmlConfiguratorAttribute) on the assembly
    ConfiguratorAttribute[] configAttributes = Attribute.GetCustomAttributes(assembly.EnsureNotNull(), typeof(ConfiguratorAttribute), false)
      .EnsureIs<ConfiguratorAttribute[]>();
    if (configAttributes.Length > 0)
    {
      // Sort the ConfiguratorAttributes in priority order
      Array.Sort(configAttributes);

      // Delegate to the attribute the job of configuring the repository
      foreach (ConfiguratorAttribute configAttr in configAttributes)
      {
        try
        {
          configAttr.Configure(assembly, repository);
        }
        catch (Exception e) when (!e.IsFatal())
        {
          LogLog.Error(_declaringType, $"Exception calling [{configAttr.GetType().FullName}] .Configure method.", e);
        }
      }
    }

    if (repository.Name == DefaultRepositoryName)
    {
      // Try to configure the default repository using an AppSettings specified config file
      // Do this even if the repository has been configured (or claims to be), this allows overriding
      // of the default config files etc., if that is required.

      string? repositoryConfigFile = SystemInfo.GetAppSetting("log4net.Config");
      if (repositoryConfigFile is not null && repositoryConfigFile.Length > 0)
      {
        string? applicationBaseDirectory = null;
        try
        {
          applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
        }
        catch (Exception e) when (!e.IsFatal())
        {
          LogLog.Warn(_declaringType, $"Exception getting ApplicationBaseDirectory. appSettings log4net.Config path [{repositoryConfigFile}] will be treated as an absolute URI", e);
        }

        string repositoryConfigFilePath = repositoryConfigFile;
        if (applicationBaseDirectory is not null)
        {
          repositoryConfigFilePath = Path.Combine(applicationBaseDirectory, repositoryConfigFile);
        }

        // Determine whether to watch the file or not based on an app setting value:
        bool.TryParse(SystemInfo.GetAppSetting("log4net.Config.Watch"), out bool watchRepositoryConfigFile);

        if (watchRepositoryConfigFile)
        {
          // As we are going to watch the config file it is required to resolve it as a 
          // physical file system path pass that in a FileInfo object to the Configurator
          FileInfo? repositoryConfigFileInfo = null;
          try
          {
            repositoryConfigFileInfo = new FileInfo(repositoryConfigFilePath);
          }
          catch (Exception e) when (!e.IsFatal())
          {
            LogLog.Error(_declaringType, $"DefaultRepositorySelector: Exception while parsing log4net.Config file physical path [{repositoryConfigFilePath}]", e);
          }

          if (repositoryConfigFileInfo is not null)
          {
            try
            {
              LogLog.Debug(_declaringType, $"Loading and watching configuration for default repository from AppSettings specified Config path [{repositoryConfigFilePath}]");

              XmlConfigurator.ConfigureAndWatch(repository, repositoryConfigFileInfo);
            }
            catch (Exception e) when (!e.IsFatal())
            {
              LogLog.Error(_declaringType, $"DefaultRepositorySelector: Exception calling XmlConfigurator.ConfigureAndWatch method with ConfigFilePath [{repositoryConfigFilePath}]", e);
            }
          }
        }
        else
        {
          // As we are not going to watch the config file it is easiest to just resolve it as a 
          // URI and pass that to the Configurator
          Uri? repositoryConfigUri = null;
          try
          {
            repositoryConfigUri = new Uri(repositoryConfigFilePath);
          }
          catch (Exception e) when (!e.IsFatal())
          {
            LogLog.Error(_declaringType, $"Exception while parsing log4net.Config file path [{repositoryConfigFile}]", e);
          }

          if (repositoryConfigUri is not null)
          {
            LogLog.Debug(_declaringType, $"Loading configuration for default repository from AppSettings specified Config URI [{repositoryConfigUri}]");

            try
            {
              // TODO: Support other types of configurator
              XmlConfigurator.Configure(repository, repositoryConfigUri);
            }
            catch (Exception e) when (!e.IsFatal())
            {
              LogLog.Error(_declaringType, $"Exception calling XmlConfigurator.Configure method with ConfigUri [{repositoryConfigUri}]", e);
            }
          }
        }
      }
    }
  }

  /// <summary>
  /// Loads the attribute defined plugins on the assembly.
  /// </summary>
  /// <param name="assembly">The assembly that contains the attributes.</param>
  /// <param name="repository">The repository to add the plugins to.</param>
  /// <exception cref="ArgumentNullException">
  ///  <para><paramref name="assembly" /> is <see langword="null" />.</para>
  ///  <para>-or-</para>
  ///  <para><paramref name="repository" /> is <see langword="null" />.</para>
  /// </exception>
  private static void LoadPlugins(Assembly assembly, ILoggerRepository repository)
  {
    // Look for the PluginAttribute on the assembly
    PluginAttribute[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(PluginAttribute), false)
      .EnsureIs<PluginAttribute[]>();
    foreach (PluginAttribute configAttr in configAttributes)
    {
      try
      {
        // Create the plugin and add it to the repository
        repository.PluginMap.Add(configAttr.CreatePlugin());
      }
      catch (Exception e) when (!e.IsFatal())
      {
        LogLog.Error(_declaringType, $"Failed to create plugin. Attribute [{configAttr}]", e);
      }
    }
  }

  /// <summary>
  /// Loads the attribute defined aliases on the assembly.
  /// </summary>
  /// <param name="assembly">The assembly that contains the attributes.</param>
  /// <param name="repository">The repository to alias to.</param>
  /// <exception cref="ArgumentNullException">
  ///  <para><paramref name="assembly" /> is <see langword="null" />.</para>
  ///  <para>-or-</para>
  ///  <para><paramref name="repository" /> is <see langword="null" />.</para>
  /// </exception>
  private void LoadAliases(Assembly assembly, ILoggerRepository repository)
  {
    // Look for the AliasRepositoryAttribute on the assembly
    AliasRepositoryAttribute[] configAttributes = Attribute
      .GetCustomAttributes(assembly, typeof(AliasRepositoryAttribute), false)
      .EnsureIs<AliasRepositoryAttribute[]>();
    foreach (AliasRepositoryAttribute configAttr in configAttributes)
    {
      try
      {
        AliasRepository(configAttr.Name, repository);
      }
      catch (Exception e) when (!e.IsFatal())
      {
        LogLog.Error(_declaringType, $"Failed to alias repository [{configAttr.Name}]", e);
      }
    }
  }

  /// <summary>
  /// The fully qualified type of the DefaultRepositorySelector class.
  /// </summary>
  /// <remarks>
  /// Used by the internal logger to record the Type of the
  /// log message.
  /// </remarks>
  private static readonly Type _declaringType = typeof(DefaultRepositorySelector);

  private const string DefaultRepositoryName = "log4net-default-repository";

  private readonly object _syncRoot = new();
  private readonly Dictionary<string, ILoggerRepository> _name2Repository = new(StringComparer.Ordinal);
  private readonly Dictionary<Assembly, ILoggerRepository> _assembly2Repository = [];
  private readonly Dictionary<string, ILoggerRepository> _alias2Repository = new(StringComparer.Ordinal);

  [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] 
  private readonly Type _defaultRepositoryType;
}