#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.Concurrent;

namespace log4net.Util.TypeConverters
{
  /// <summary>
  /// Register of type converters for specific types.
  /// </summary>
  /// <remarks>
  /// <para>
  /// Maintains a registry of type converters used to convert between types.
  /// </para>
  /// <para>
  /// Use the <see cref="M:AddConverter(Type, object)"/> and 
  /// <see cref="M:AddConverter(Type, Type)"/> methods to register new converters.
  /// The <see cref="GetConvertTo"/> and <see cref="GetConvertFrom"/> methods
  /// lookup appropriate converters to use.
  /// </para>
  /// </remarks>
  /// <seealso cref="IConvertFrom"/>
  /// <seealso cref="IConvertTo"/>
  /// <author>Nicko Cadell</author>
  /// <author>Gert Driesen</author>
  public static class ConverterRegistry
  {
    /// <summary>
    /// This class constructor adds the intrinsic type converters
    /// </summary>
    static ConverterRegistry()
    {
      // Add predefined converters here
      AddConverter(typeof(bool), typeof(BooleanConverter));
      AddConverter(typeof(System.Text.Encoding), typeof(EncodingConverter));
      AddConverter(typeof(Type), typeof(TypeConverter));
      AddConverter(typeof(Layout.PatternLayout), typeof(PatternLayoutConverter));
      AddConverter(typeof(PatternString), typeof(PatternStringConverter));
      AddConverter(typeof(System.Net.IPAddress), typeof(IPAddressConverter));
    }

    /// <summary>
    /// Adds a converter for a specific type.
    /// </summary>
    /// <param name="destinationType">The type being converted to.</param>
    /// <param name="converter">The type converter to use to convert to the destination type.</param>
    public static void AddConverter(Type? destinationType, object? converter)
    {
      if (destinationType is null || converter is null)
      {
        return;
      }
      if (converter is IConvertTo convertTo)
      {
        s_type2ConvertTo[destinationType] = convertTo;
      }
      if (converter is IConvertFrom convertFrom)
      {
        s_type2ConvertFrom[destinationType] = convertFrom;
      }
    }

    /// <summary>
    /// Adds a converter for a specific type.
    /// </summary>
    /// <param name="destinationType">The type being converted to.</param>
    /// <param name="converterType">The type of the type converter to use to convert to the destination type.</param>
    public static void AddConverter(Type destinationType, Type converterType)
      => AddConverter(destinationType, CreateConverterInstance(converterType));

    /// <summary>
    /// Gets the type converter to use to convert values to the destination type.
    /// </summary>
    /// <param name="sourceType">The type being converted from.</param>
    /// <param name="destinationType">The type being converted to.</param>
    /// <returns>
    /// The type converter instance to use for type conversions or <c>null</c> 
    /// if no type converter is found.
    /// </returns>
    public static IConvertTo? GetConvertTo(Type sourceType, Type destinationType)
    {
      // TODO: Support inheriting type converters.
      // i.e. getting a type converter for a base of sourceType

      // TODO: Is destinationType required? We don't use it for anything.

      // Look up in the static registry
      if (!s_type2ConvertTo.TryGetValue(sourceType, out IConvertTo? converter))
      {
        // Look up using attributes
        converter = GetConverterFromAttribute(sourceType) as IConvertTo;
        if (converter is not null)
        {
          // Store in registry
          s_type2ConvertTo[sourceType] = converter;
        }
      }

      return converter;
    }

    /// <summary>
    /// Gets the type converter to use to convert values to the destination type.
    /// </summary>
    /// <param name="destinationType">The type being converted to.</param>
    /// <returns>
    /// The type converter instance to use for type conversions or <c>null</c> 
    /// if no type converter is found.
    /// </returns>
    public static IConvertFrom? GetConvertFrom(Type destinationType)
    {
      // TODO: Support inheriting type converters.
      // i.e. getting a type converter for a base of destinationType

      // Lookup in the static registry
      if (!s_type2ConvertFrom.TryGetValue(destinationType, out IConvertFrom? converter))
      {
        // Look up using attributes
        converter = GetConverterFromAttribute(destinationType) as IConvertFrom;
        if (converter is not null)
        {
          // Store in registry
          s_type2ConvertFrom[destinationType] = converter;
        }
      }

      return converter;
    }

    /// <summary>
    /// Lookups the type converter to use as specified by the attributes on the 
    /// destination type.
    /// </summary>
    /// <param name="destinationType">The type being converted to.</param>
    /// <returns>
    /// The type converter instance to use for type conversions or <c>null</c> 
    /// if no type converter is found.
    /// </returns>
    private static object? GetConverterFromAttribute(Type destinationType)
    {
      // Look for an attribute on the destination type
      object[] attributes = destinationType.GetCustomAttributes(typeof(TypeConverterAttribute), true);
      foreach (var attribute in attributes)
      {
        if (attribute is TypeConverterAttribute tcAttr)
        {
          if (SystemInfo.GetTypeFromString(destinationType, tcAttr.ConverterTypeName, false, true) is Type converterType)
          {
            return CreateConverterInstance(converterType);
          }
        }
      }

      // Not found converter using attributes
      return null;
    }

    /// <summary>
    /// Creates the instance of the type converter.
    /// </summary>
    /// <param name="converterType">The type of the type converter.</param>
    /// <returns>
    /// The type converter instance to use for type conversions or <c>null</c> 
    /// if no type converter is found.
    /// </returns>
    /// <remarks>
    /// <para>
    /// The type specified for the type converter must implement 
    /// the <see cref="IConvertFrom"/> or <see cref="IConvertTo"/> interfaces 
    /// and must have a public default (no argument) constructor.
    /// </para>
    /// </remarks>
    private static object? CreateConverterInstance(Type converterType)
    {
      // Check type is a converter
      if (typeof(IConvertFrom).IsAssignableFrom(converterType) || typeof(IConvertTo).IsAssignableFrom(converterType))
      {
        try
        {
          // Create the type converter
          return Activator.CreateInstance(converterType);
        }
        catch (Exception ex)
        {
          LogLog.Error(declaringType, $"Cannot CreateConverterInstance of type [{converterType.FullName}], exception in call to Activator.CreateInstance", ex);
        }
      }
      else
      {
        LogLog.Error(declaringType, $"Cannot CreateConverterInstance of type [{converterType.FullName}], type does not implement IConvertFrom or IConvertTo");
      }
      return null;
    }

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

    private static readonly ConcurrentDictionary<Type, IConvertTo> s_type2ConvertTo = new();
    private static readonly ConcurrentDictionary<Type, IConvertFrom> s_type2ConvertFrom = new();
  }
}