//------------------------------------------------------------------------------
// <copyright file=”*.cs” company=”EhLib Team”>
//     Copyright (c) 2017 Dmitry V. Bolshakov   
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Windows.Forms;

namespace EhLib
{
  public class DisplayInternalValueConverter
  {
    #region static protected
    private static readonly Type StringType = typeof(string);
    private static readonly Type BooleanType = typeof(bool);
    private static readonly Type CheckStateType = typeof(CheckState);
    private static readonly object ParseMethodNotFound = new object();
    private static readonly object DefaultDataSourceNullValue = System.DBNull.Value;
    #endregion static protected

    #region public methods
    public virtual object FormatValue(object value,
                                      Type targetType,
                                      TypeConverter sourceConverter,
                                      TypeConverter targetConverter,
                                      string formatString,
                                      IFormatProvider formatInfo,
                                      object formattedNullValue,
                                      object dataSourceNullValue)
    {
      if (IsNullData(value, dataSourceNullValue))
      {
        value = System.DBNull.Value;
      }

      if (value is decimal)
        value = (decimal)value / 1.000000000000000000000000000000000m;

      Type oldTargetType = targetType;

      targetType = NullableUnwrap(targetType);
      sourceConverter = NullableUnwrap(sourceConverter);
      targetConverter = NullableUnwrap(targetConverter);

      bool isNullableTargetType = (targetType != oldTargetType);

      object result = FormatValueInternal(value, targetType, sourceConverter, targetConverter, formatString, formatInfo, formattedNullValue);

      if (oldTargetType.IsValueType && result == null && !isNullableTargetType)
      {
        throw new FormatException(GetCannotConvertMessage(value, targetType));
      }
      return result;
    }

    public object FormatValue(object value, Type targetType)
    {
      return FormatValue(value, targetType, null, null, "", null, "", null);
    }

    public virtual object ParseValue(object value,
                                     Type targetType,
                                     Type sourceType,
                                     TypeConverter targetConverter,
                                     TypeConverter sourceConverter,
                                     IFormatProvider formatInfo,
                                     object formattedNullValue,
                                     object dataSourceNullValue)
    {
      Type oldTargetType = targetType;

      sourceType = NullableUnwrap(sourceType);
      targetType = NullableUnwrap(targetType);
      sourceConverter = NullableUnwrap(sourceConverter);
      targetConverter = NullableUnwrap(targetConverter);

      //bool isNullableTargetType = (targetType != oldTargetType);

      object result = ParseValueInternal(value, targetType, sourceType, targetConverter, sourceConverter, formatInfo, formattedNullValue);

      if (result == System.DBNull.Value)
      {
        return NullData(oldTargetType, dataSourceNullValue);
      }

      return result;
    }


    public virtual object InvokeStringParseMethod(object value, Type targetType, IFormatProvider formatInfo)
    {
      try
      {
        MethodInfo mi;

        mi = targetType.GetMethod("Parse",
                                BindingFlags.Public | BindingFlags.Static,
                                null,
                                new Type[] { StringType, typeof(System.Globalization.NumberStyles), typeof(System.IFormatProvider) },
                                null);
        if (mi != null)
        {
          return mi.Invoke(null, new object[] { (string)value, NumberStyles.Any, formatInfo });
        }

        mi = targetType.GetMethod("Parse",
                                BindingFlags.Public | BindingFlags.Static,
                                null,
                                new Type[] { StringType, typeof(System.IFormatProvider) },
                                null);
        if (mi != null)
        {
          return mi.Invoke(null, new object[] { (string)value, formatInfo });
        }

        mi = targetType.GetMethod("Parse",
                                BindingFlags.Public | BindingFlags.Static,
                                null,
                                new Type[] { StringType },
                                null);
        if (mi != null)
        {
          return mi.Invoke(null, new object[] { (string)value });
        }

        return ParseMethodNotFound;
      }
      catch (TargetInvocationException ex)
      {
        Debug.Assert(ex.InnerException != null, "ex.InnerException != null");
        throw new FormatException(ex.InnerException.Message, ex.InnerException);
      }
    }

    public virtual bool IsNullData(object value, object dataSourceNullValue)
    {
      return value == null ||
             value == System.DBNull.Value ||
             object.Equals(value, NullData(value.GetType(), dataSourceNullValue));
    }

    public virtual object NullData(Type type, object dataSourceNullValue)
    {
      if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
      {
        if (dataSourceNullValue == null || dataSourceNullValue == DBNull.Value)
          return null;
        else
          return dataSourceNullValue;
      }
      else
      {
        return dataSourceNullValue;
      }
    }

    public virtual object GetDefaultDataSourceNullValue(Type type)
    {
      return (type != null && !type.IsValueType) ? null : DefaultDataSourceNullValue;
    }
    #endregion public methods

    #region internal methods
    protected virtual object FormatValueInternal(object value,
                                               Type targetType,
                                               TypeConverter sourceConverter,
                                               TypeConverter targetConverter,
                                               string formatString,
                                               IFormatProvider formatInfo,
                                               object formattedNullValue)
    {
      if (value == System.DBNull.Value || value == null)
      {

        if (formattedNullValue != null)
        {
          return formattedNullValue;
        }

        if (targetType == StringType)
        {
          return string.Empty;
        }

        if (targetType == CheckStateType)
        {
          return CheckState.Indeterminate;
        }

        return null;
      }

      if (targetType == StringType)
      {
        var formattable = value as IFormattable;
        if (formattable != null && !string.IsNullOrEmpty(formatString))
        {
          return formattable.ToString(formatString, formatInfo);
        }
      }

      Type sourceType = value.GetType();
      TypeConverter sourceTypeTypeConverter = TypeDescriptor.GetConverter(sourceType);
      if (sourceConverter != null && sourceConverter != sourceTypeTypeConverter && sourceConverter.CanConvertTo(targetType))
      {
        return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
      }

      TypeConverter targetTypeTypeConverter = TypeDescriptor.GetConverter(targetType);
      if (targetConverter != null && targetConverter != targetTypeTypeConverter && targetConverter.CanConvertFrom(sourceType))
      {
        return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
      }

      if (targetType == CheckStateType)
      {
        if (sourceType == BooleanType)
        {
          return ((bool)value) ? CheckState.Checked : CheckState.Unchecked;
        }
        else {
          if (sourceConverter == null)
          {
            sourceConverter = sourceTypeTypeConverter;
          }
          if (/*sourceConverter != null && */sourceConverter.CanConvertTo(BooleanType))
          {
            object convertTo = sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, BooleanType);
            if (convertTo != null && (bool) convertTo)
              return CheckState.Checked;
            else
              return CheckState.Unchecked;
          }
        }
      }

      if (targetType.IsAssignableFrom(sourceType))
      {
        return value;
      }

      if (sourceConverter == null)
      {
        sourceConverter = sourceTypeTypeConverter;
      }

      if (targetConverter == null)
      {
        targetConverter = targetTypeTypeConverter;
      }


      if (/*sourceConverter != null && */sourceConverter.CanConvertTo(targetType))
      {
        return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
      }
      else if (/*targetConverter != null && */targetConverter.CanConvertFrom(sourceType))
      {
        return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
      }
      else if (value is IConvertible)
      {
        return ChangeType(value, targetType, formatInfo);
      }

      throw new FormatException(GetCannotConvertMessage(value, targetType));
    }

    protected virtual object ParseValueInternal(object value,
                                              Type targetType,
                                              Type sourceType,
                                              TypeConverter targetConverter,
                                              TypeConverter sourceConverter,
                                              IFormatProvider formatInfo,
                                              object formattedNullValue)
    {
      if (EqualsFormattedNullValue(value, formattedNullValue, formatInfo) || value == System.DBNull.Value)
      {
        return System.DBNull.Value;
      }

      TypeConverter targetTypeTypeConverter = TypeDescriptor.GetConverter(targetType);
      if (targetConverter != null && targetTypeTypeConverter != targetConverter && targetConverter.CanConvertFrom(sourceType))
      {
        return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
      }

      TypeConverter sourceTypeTypeConverter = null;
      if (sourceType != null)
        sourceTypeTypeConverter = TypeDescriptor.GetConverter(sourceType);
      if (sourceConverter != null && sourceTypeTypeConverter != sourceConverter && sourceConverter.CanConvertTo(targetType))
      {
        return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
      }

      if (value is string)
      {
        object parseResult = InvokeStringParseMethod(value, targetType, formatInfo);
        if (parseResult != ParseMethodNotFound)
        {
          return parseResult;
        }
      }
      else if (value is CheckState)
      {
//#pragma warning disable IDE0020 // Use pattern matching
        CheckState state = (CheckState)value;
//#pragma warning restore IDE0020 // Use pattern matching
        if (state == CheckState.Indeterminate)
        {
          return DBNull.Value;
        }
        if (targetType == BooleanType)
        {
          return (state == CheckState.Checked);
        }
        if (targetConverter == null)
        {
          targetConverter = targetTypeTypeConverter;
        }
        if (/*targetConverter != null && */targetConverter.CanConvertFrom(BooleanType))
        {
          return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), state == CheckState.Checked);
        }
      }
      else if (value != null && targetType.IsInstanceOfType(value))
      {
        return value;
      }

      if (targetConverter == null)
      {
        targetConverter = targetTypeTypeConverter;
      }

      if (sourceConverter == null)
      {
        sourceConverter = sourceTypeTypeConverter;
      }

      if (/*targetConverter != null && */targetConverter.CanConvertFrom(sourceType))
      {
        return targetConverter.ConvertFrom(null, GetFormatterCulture(formatInfo), value);
      }
      else if (/*sourceConverter != null && */sourceConverter.CanConvertTo(targetType))
      {
        return sourceConverter.ConvertTo(null, GetFormatterCulture(formatInfo), value, targetType);
      }
      else if (value is IConvertible)
      {
        return ChangeType(value, targetType, formatInfo);
      }

      throw new FormatException(GetCannotConvertMessage(value, targetType));
    }

    private object ChangeType(object value, Type type, IFormatProvider formatInfo)
    {
      try
      {
        if (formatInfo == null)
        {
          formatInfo = CultureInfo.CurrentCulture;
        }

        return Convert.ChangeType(value, type, formatInfo);
      }
      catch (InvalidCastException ex)
      {
        throw new FormatException(ex.Message, ex);
      }
    }

    protected virtual bool EqualsFormattedNullValue(object value, object formattedNullValue, IFormatProvider formatInfo)
    {
      string formattedNullValueStr = formattedNullValue as string;
      string valueStr = value as string;
      if (formattedNullValueStr != null && valueStr != null)
      {
        if (formattedNullValueStr.Length != valueStr.Length)
        {
          return false;
        }
        return string.Compare(valueStr, formattedNullValueStr, true, GetFormatterCulture(formatInfo)) == 0;
      }
      else {
        return object.Equals(value, formattedNullValue);
      }
    }

    protected virtual string GetCannotConvertMessage(object value, Type targetType)
    {
      string stringResId = (value == null) ? "SR.Formatter_CantConvertNull" : "SR.Formatter_CantConvert";
      //return String.Format(CultureInfo.CurrentCulture, SR.GetString(stringResId), value, targetType.Name);
      return string.Format(CultureInfo.CurrentCulture, stringResId, value, targetType.Name);
    }

    protected virtual CultureInfo GetFormatterCulture(IFormatProvider formatInfo)
    {
      var info = formatInfo as CultureInfo;
      if (info != null)
      {
        return info;
      }
      else
      {
        return CultureInfo.CurrentCulture;
      }
    }

    private Type NullableUnwrap(Type type)
    {
      if (type == null) // ...performance optimization for the most common case
        return null;
      if (type == StringType) // ...performance optimization for the most common case
        return StringType;

      Type underlyingType = Nullable.GetUnderlyingType(type);
      return underlyingType ?? type;
    }

    private TypeConverter NullableUnwrap(TypeConverter typeConverter)
    {
      NullableConverter nullableConverter = typeConverter as NullableConverter;
      return (nullableConverter != null) ? nullableConverter.UnderlyingTypeConverter : typeConverter;
    }
    #endregion internal methods

  }
}
