#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
}
