#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.Reflection;
using log4net.Core;

namespace log4net.Ext.EventID
{
  /// <summary>
  /// Custom Logging Class to support Event IDs.
  /// </summary>
  public sealed class EventIDLogManager
  {
    #region Static Member Variables

    /// <summary>
    /// The wrapper map to use to hold the <see cref="EventIDLogImpl"/> objects
    /// </summary>
    private static readonly WrapperMap wrapperMap = new(new(WrapperCreationHandler));

    #endregion

    #region Constructor

    /// <summary>
    /// Private constructor to prevent object creation
    /// </summary>
    private EventIDLogManager() { }

    #endregion

    #region Type Specific Manager Methods

    /// <summary>
    /// Returns the named logger if it exists
    /// </summary>
    /// <remarks>
    /// <para>If the named logger exists (in the default hierarchy) then it
    /// returns a reference to the logger, otherwise it returns
    /// <see langword="null"/>.</para>
    /// </remarks>
    /// <param name="name">The fully qualified logger name to look for</param>
    /// <returns>The logger found, or null</returns>
    public static IEventIDLog? Exists(string name) => Exists(Assembly.GetCallingAssembly(), name);

    /// <summary>
    /// Returns the named logger if it exists
    /// </summary>
    /// <remarks>
    /// <para>If the named logger exists (in the specified domain) then it
    /// returns a reference to the logger, otherwise it returns
    /// <see langword="null"/>.</para>
    /// </remarks>
    /// <param name="domain">the domain to lookup in</param>
    /// <param name="name">The fully qualified logger name to look for</param>
    /// <returns>The logger found, or null</returns>
    public static IEventIDLog? Exists(string domain, string name)
      => WrapLogger(LoggerManager.Exists(domain, name));

    /// <summary>
    /// Returns the named logger if it exists
    /// </summary>
    /// <remarks>
    /// <para>If the named logger exists (in the specified assembly's domain) then it
    /// returns a reference to the logger, otherwise it returns
    /// <see langword="null"/>.</para>
    /// </remarks>
    /// <param name="assembly">the assembly to use to lookup the domain</param>
    /// <param name="name">The fully qualified logger name to look for</param>
    /// <returns>The logger found, or null</returns>
    public static IEventIDLog? Exists(Assembly assembly, string name)
      => WrapLogger(LoggerManager.Exists(assembly, name));

    /// <summary>
    /// Returns all the currently defined loggers in the default domain.
    /// </summary>
    /// <remarks>
    /// <para>The root logger is <b>not</b> included in the returned array.</para>
    /// </remarks>
    /// <returns>All the defined loggers</returns>
    public static IEventIDLog[] GetCurrentLoggers()
      => GetCurrentLoggers(Assembly.GetCallingAssembly());

    /// <summary>
    /// Returns all the currently defined loggers in the specified domain.
    /// </summary>
    /// <param name="domain">the domain to lookup in</param>
    /// <remarks>
    /// The root logger is <b>not</b> included in the returned array.
    /// </remarks>
    /// <returns>All the defined loggers</returns>
    public static IEventIDLog[] GetCurrentLoggers(string domain)
      => WrapLoggers(LoggerManager.GetCurrentLoggers(domain));

    /// <summary>
    /// Returns all the currently defined loggers in the specified assembly's domain.
    /// </summary>
    /// <param name="assembly">the assembly to use to lookup the domain</param>
    /// <remarks>
    /// The root logger is <b>not</b> included in the returned array.
    /// </remarks>
    /// <returns>All the defined loggers</returns>
    public static IEventIDLog[] GetCurrentLoggers(Assembly assembly)
      => WrapLoggers(LoggerManager.GetCurrentLoggers(assembly));

    /// <summary>
    /// Retrieve or create a named logger.
    /// </summary>
    /// <remarks>
    /// <para>Retrieve a logger named as the <paramref name="name"/>
    /// parameter. If the named logger already exists, then the
    /// existing instance will be returned. Otherwise, a new instance is
    /// created.</para>
    /// 
    /// <para>By default, loggers do not have a set level but inherit
    /// it from the hierarchy. This is one of the central features of
    /// log4net.</para>
    /// </remarks>
    /// <param name="name">The name of the logger to retrieve.</param>
    /// <returns>the logger with the name specified</returns>
    public static IEventIDLog? GetLogger(string name)
      => GetLogger(Assembly.GetCallingAssembly(), name);

    /// <summary>
    /// Retrieve or create a named logger.
    /// </summary>
    /// <remarks>
    /// <para>Retrieve a logger named as the <paramref name="name"/>
    /// parameter. If the named logger already exists, then the
    /// existing instance will be returned. Otherwise, a new instance is
    /// created.</para>
    /// 
    /// <para>By default, loggers do not have a set level but inherit
    /// it from the hierarchy. This is one of the central features of
    /// log4net.</para>
    /// </remarks>
    /// <param name="domain">the domain to lookup in</param>
    /// <param name="name">The name of the logger to retrieve.</param>
    /// <returns>the logger with the name specified</returns>
    public static IEventIDLog? GetLogger(string domain, string name)
      => WrapLogger(LoggerManager.GetLogger(domain, name));

    /// <summary>
    /// Retrieve or create a named logger.
    /// </summary>
    /// <remarks>
    /// <para>Retrieve a logger named as the <paramref name="name"/>
    /// parameter. If the named logger already exists, then the
    /// existing instance will be returned. Otherwise, a new instance is
    /// created.</para>
    /// 
    /// <para>By default, loggers do not have a set level but inherit
    /// it from the hierarchy. This is one of the central features of
    /// log4net.</para>
    /// </remarks>
    /// <param name="assembly">the assembly to use to lookup the domain</param>
    /// <param name="name">The name of the logger to retrieve.</param>
    /// <returns>the logger with the name specified</returns>
    public static IEventIDLog? GetLogger(Assembly assembly, string name)
      => WrapLogger(LoggerManager.GetLogger(assembly, name));

    /// <summary>
    /// Shorthand for <see cref="LogManager.GetLogger(string)"/>.
    /// </summary>
    /// <remarks>
    /// Get the logger for the fully qualified name of the type specified.
    /// </remarks>
    /// <param name="type">The full name of <paramref name="type"/> will 
    /// be used as the name of the logger to retrieve.</param>
    /// <returns>the logger with the name specified</returns>
    public static IEventIDLog? GetLogger(Type type)
    {
      ArgumentNullException.ThrowIfNull(type);
      return GetLogger(Assembly.GetCallingAssembly(), type.FullName ?? string.Empty);
    }

    /// <summary>
    /// Shorthand for <see cref="LogManager.GetLogger(string)"/>.
    /// </summary>
    /// <remarks>
    /// Get the logger for the fully qualified name of the type specified.
    /// </remarks>
    /// <param name="domain">the domain to lookup in</param>
    /// <param name="type">The full name of <paramref name="type"/> will 
    /// be used as the name of the logger to retrieve.</param>
    /// <returns>the logger with the name specified</returns>
    public static IEventIDLog? GetLogger(string domain, Type type)
      => WrapLogger(LoggerManager.GetLogger(domain, type));

    /// <summary>
    /// Shorthand for <see cref="LogManager.GetLogger(string)"/>.
    /// </summary>
    /// <remarks>
    /// Get the logger for the fully qualified name of the type specified.
    /// </remarks>
    /// <param name="assembly">the assembly to use to lookup the domain</param>
    /// <param name="type">The full name of <paramref name="type"/> will 
    /// be used as the name of the logger to retrieve.</param>
    /// <returns>the logger with the name specified</returns>
    public static IEventIDLog? GetLogger(Assembly assembly, Type type)
      => WrapLogger(LoggerManager.GetLogger(assembly, type));

    #endregion

    #region Extension Handlers

    /// <summary>
    /// Lookup the wrapper object for the logger specified
    /// </summary>
    /// <param name="logger">the logger to get the wrapper for</param>
    /// <returns>the wrapper for the logger specified</returns>
    private static IEventIDLog? WrapLogger(ILogger? logger) => (IEventIDLog?)wrapperMap.GetWrapper(logger);

    /// <summary>
    /// Lookup the wrapper objects for the loggers specified
    /// </summary>
    /// <param name="loggers">the loggers to get the wrappers for</param>
    /// <returns>Lookup the wrapper objects for the loggers specified</returns>
    private static IEventIDLog[] WrapLoggers(IReadOnlyList<ILogger> loggers)
    {
      IEventIDLog[] results = new IEventIDLog[loggers.Count];
      for (int i = 0; i < loggers.Count; i++)
      {
        results[i] = WrapLogger(loggers[i]) ?? throw new ArgumentNullException(nameof(loggers));
      }
      return results;
    }

    /// <summary>
    /// Method to create the <see cref="ILoggerWrapper"/> objects used by
    /// this manager.
    /// </summary>
    /// <param name="logger">The logger to wrap</param>
    /// <returns>The wrapper for the logger specified</returns>
    private static EventIDLogImpl WrapperCreationHandler(ILogger logger) => new(logger);

    #endregion
  }
}
