// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using System.Reflection;

namespace System.Windows.Forms;

/// <summary>
///  CursorConverter is a class that can be used to convert
///  colors from one data type to another.  Access this
///  class through the TypeDescriptor.
/// </summary>
public class CursorConverter : TypeConverter
{
    private StandardValuesCollection? _values;

    /// <summary>
    ///  Determines if this converter can convert an object in the given source
    ///  type to the native type of the converter.
    /// </summary>
    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
    {
        if (sourceType == typeof(string) || sourceType == typeof(byte[]))
        {
            return true;
        }

        return base.CanConvertFrom(context, sourceType);
    }

    /// <summary>
    ///  Gets a value indicating whether this converter can
    ///  convert an object to the given destination type using the context.
    /// </summary>
    public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
    {
        if (destinationType == typeof(InstanceDescriptor) || destinationType == typeof(byte[]))
        {
            return true;
        }

        return base.CanConvertTo(context, destinationType);
    }

    /// <summary>
    ///  Converts the given object to the converter's native type.
    /// </summary>
    public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
    {
        if (value is string s)
        {
            string text = s.Trim();

            PropertyInfo[] props = GetProperties();
            foreach (var prop in props)
            {
                if (string.Equals(prop.Name, text, StringComparison.OrdinalIgnoreCase))
                {
                    return prop.GetValue(null, null);
                }
            }
        }

        if (value is byte[] bytes)
        {
            using MemoryStream ms = new(bytes);
            return new Cursor(ms);
        }

        return base.ConvertFrom(context, culture, value);
    }

    /// <summary>
    ///  Converts the given object to another type.  The most common types to convert
    ///  are to and from a string object.  The default implementation will make a call
    ///  to ToString on the object if the object is valid and if the destination
    ///  type is string.  If this cannot convert to the destination type, this will
    ///  throw a NotSupportedException.
    /// </summary>
    public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
    {
        if (value is Cursor cursor)
        {
            if (destinationType == typeof(string))
            {
                if (cursor.CursorsProperty is string propertyName)
                {
                    return propertyName;
                }
                else if (cursor.Handle == Cursors.Arrow.Handle)
                {
                    // Arrow and Default cursors share the same HCURSOR.
                    // Always return "Default" in this case.
                    return nameof(Cursors.Default);
                }

                // We have a cursor that only has handle information. This can happen when the cursor was read via PInvoke.
                // Try to find an exact instance match to a known cursor from Cursors properties using HCURSOR equality (==).
                PropertyInfo[] props = GetProperties();
                for (int i = 0; i < props.Length; i++)
                {
                    PropertyInfo prop = props[i];
                    Cursor? knownCursor = (Cursor?)prop.GetValue(obj: null, index: null);
                    if (knownCursor == cursor)
                    {
                        return prop.Name;
                    }
                }

                // We throw here because we cannot meaningfully convert a custom
                // cursor into a string. In fact, the ResXResourceWriter will use
                // this exception to indicate to itself that this object should
                // be serialized through ISerializable instead of a string.

                throw new FormatException(SR.CursorCannotCovertToString);
            }
            else if (destinationType == typeof(InstanceDescriptor))
            {
                PropertyInfo[] props = GetProperties();
                foreach (PropertyInfo prop in props)
                {
                    if (prop.GetValue(null, null) == value)
                    {
                        return new InstanceDescriptor(prop, null);
                    }
                }
            }
            else if (destinationType == typeof(byte[]))
            {
                return cursor.GetData();
            }
        }
        else if (destinationType == typeof(byte[]) && value is null)
        {
            return Array.Empty<byte>();
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

    /// <summary>
    ///  Retrieves the properties for the available cursors.
    /// </summary>
    private static PropertyInfo[] GetProperties()
    {
        return typeof(Cursors).GetProperties(BindingFlags.Static | BindingFlags.Public);
    }

    /// <summary>
    ///  Retrieves a collection containing a set of standard values
    ///  for the data type this validator is designed for.  This
    ///  will return null if the data type does not support a
    ///  standard set of values.
    /// </summary>
    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context)
    {
        if (_values is null)
        {
            List<object> list = [];
            PropertyInfo[] props = GetProperties();
            for (int i = 0; i < props.Length; i++)
            {
                PropertyInfo prop = props[i];
                object[]? tempIndex = null;
                Debug.Assert(prop.GetValue(null, tempIndex) is not null, $"Property {prop.Name} returned NULL");
                if (prop.GetValue(null, tempIndex) is object item)
                {
                    list.Add(item);
                }
            }

            _values = new StandardValuesCollection(list);
        }

        return _values;
    }

    /// <summary>
    ///  Determines if this object supports a standard set of values
    ///  that can be picked from a list.
    /// </summary>
    public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
    {
        return true;
    }
}
