using System.ComponentModel;

namespace UnshackleGUI
{
    /// <summary>
    /// This is the object assigned to PropertyGrid.SelectedObject
    /// It correctly implements ICustomTypeDescriptor.
    /// </summary>
    public class DynamicObject : ICustomTypeDescriptor
    {
        private readonly Dictionary<string, object> _values;
        private readonly List<UnshackleParameter> _defs;

        public DynamicObject(Dictionary<string, object> values, List<UnshackleParameter> defs)
        {
            _values = values;
            _defs = defs;
        }

        public PropertyDescriptorCollection GetProperties()
        {
            return new DynamicCustomTypeDescriptor(_values, _defs).GetProperties();
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            return GetProperties();
        }

        public object GetPropertyOwner(PropertyDescriptor pd) => this;

        public AttributeCollection GetAttributes() => AttributeCollection.Empty;
        public string GetClassName() => nameof(DynamicObject);
        public string GetComponentName() => null;
        public TypeConverter GetConverter() => null;
        public EventDescriptor GetDefaultEvent() => null;
        public PropertyDescriptor GetDefaultProperty() => null;
        public object GetEditor(Type editorBaseType) => null;
        public EventDescriptorCollection GetEvents() => EventDescriptorCollection.Empty;
        public EventDescriptorCollection GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty;
    }

    internal class DynamicCustomTypeDescriptor : CustomTypeDescriptor
    {
        private readonly Dictionary<string, object> _values;
        private readonly List<UnshackleParameter> _defs;
        private PropertyDescriptorCollection _cache;

        public DynamicCustomTypeDescriptor(Dictionary<string, object> values, List<UnshackleParameter> defs)
        {
            _values = values;
            _defs = defs;
        }

        public override PropertyDescriptorCollection GetProperties()
        {
            return GetProperties(null);
        }

        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            if (_cache == null)
            {
                var props = new List<PropertyDescriptor>();

                foreach (var def in _defs)
                {
                    props.Add(new DynamicPropertyDescriptor(def, _values));
                }

                _cache = new PropertyDescriptorCollection(props.ToArray());
            }

            return _cache;
        }
    }

    internal class DynamicPropertyDescriptor : PropertyDescriptor
    {
        private readonly UnshackleParameter _parameter;
        private readonly Dictionary<string, object> _values;

        public DynamicPropertyDescriptor(UnshackleParameter parameter, Dictionary<string, object> values)
            : base(parameter.Name, null)
        {
            _parameter = parameter;
            _values = values;
        }

        public override string Category => _parameter.Category;

        // IMPORTANT FIX
        public override Type ComponentType => typeof(object);

        public override bool IsReadOnly => false;

        public override Type PropertyType =>
            _parameter.Type switch
            {
                "Bool" => typeof(bool),
                "Number" => typeof(int),
                _ => typeof(string)
            };

        public override TypeConverter Converter =>
            (_parameter.Type == "Selection" && _parameter.Options != null)
                ? new StandardValuesConverter(_parameter.Options)
                : base.Converter;

        public override object GetValue(object component)
        {
            return _values.ContainsKey(_parameter.Name)
                ? _values[_parameter.Name]
                : _parameter.Default;
        }

        public override void SetValue(object component, object value)
        {
            _values[_parameter.Name] = value;

            OnValueChanged(component, EventArgs.Empty);

            // Force grid refresh
            TypeDescriptor.Refresh(component);
        }

        public override bool CanResetValue(object component) => true;

        public override void ResetValue(object component)
        {
            _values[_parameter.Name] = _parameter.Default;
        }

        public override bool ShouldSerializeValue(object component) => true;
    }

    internal class StandardValuesConverter : TypeConverter
    {
        private readonly List<string> _values;

        public StandardValuesConverter(List<string> values)
        {
            _values = values;
        }

        public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;

        public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => true;

        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            return new StandardValuesCollection(_values);
        }
    }
}
