using System;
using System.Collections.Generic;
using System.Linq;

namespace InABox.Core
{
    // When creating a new field with properties, make sure to override LoadProperties() and SaveProperties()
    public abstract class DFLayoutFieldProperties : DFLayoutObject
    {
        [EditorSequence(-999)]
        [CodeEditor(Editable = Editable.Enabled, ValidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789")]
        public string Code { get; set; }

        [EditorSequence(-998)]
        [TextBoxEditor]
        public string Description { get; set; }

        [EditorSequence(-997)]
        [ComboLookupEditor(typeof(PropertyLookupGenerator))]
        public string Property { get; set; }
        
        [Caption("Read Only?")]
        [EditorSequence(-997)]
        [CheckBoxEditor]
        public bool ReadOnlyProperty { get; set; }

        [CheckBoxEditor]
        [EditorSequence(4)]
        public bool Required { get; set; }

        [CheckBoxEditor]
        [EditorSequence(5)]
        public bool Secure { get; set; }

        [CheckBoxEditor]
        [EditorSequence(6)]
        public bool Retain { get; set; }

        [CheckBoxEditor]
        [EditorSequence(7)]
        public bool Hidden { get; set; }

        [ExpressionEditor(null)]
        [EditorSequence(8)]
        public string Expression { get; set; }

        /// <summary>
        /// An expression that sets the field to have a background colour of its result.
        /// The result of the expression should be either a hex code like #rrggbb or #aarrggbb, or the string name of a <see cref="System.Drawing.KnownColor"/>,
        /// like 'Yellow'.
        /// </summary>
        [Comment("Expression for the background colour of editor.")]
        [ExpressionEditor(null, ToolTip = "Evalutes to either a hex code (#RRGGBB or #AARRGGBB) or a colour name (like 'Red' or 'Yellow'), which sets the background colour for this variable.")]
        [EditorSequence(9)]
        public string ColourExpression { get; set; }

        public abstract string FormatValue(object? value);

        public abstract void Serialize(DFSaveStorageEntry entry, object? value);
        public abstract object? Deserialize(DFLoadStorageEntry entry);

        public abstract object? GetValue(object? value);

        public virtual IEnumerable<CoreColumn> GetAdditionalColumns()
        {
            return Enumerable.Empty<CoreColumn>();
        }
        public virtual IEnumerable<KeyValuePair<string, object?>> GetAdditionalValues(object? value)
        {
            return Enumerable.Empty<KeyValuePair<string, object?>>();
        }

        public abstract IEnumerable<CoreColumn> GetDisplayColumns();
        public abstract IEnumerable<KeyValuePair<string, object?>> GetDisplayValues(object? value);

        protected override void LoadProperties()
        {
            Description = GetProperty(nameof(Description), "");
            Property = GetProperty(nameof(Property), "");
            ReadOnlyProperty = GetProperty(nameof(ReadOnlyProperty), true);
            Required = GetProperty(nameof(Required), false);
            Secure = GetProperty(nameof(Secure), false);
            Retain = GetProperty(nameof(Retain), false);
            Expression = GetProperty(nameof(Expression), "");
            ColourExpression = GetProperty(nameof(ColourExpression), "");
        }

        protected override void SaveProperties()
        {
            SetProperty(nameof(Description), Description);
            SetProperty(nameof(Property), Property);
            SetProperty(nameof(ReadOnlyProperty), ReadOnlyProperty);
            SetProperty(nameof(Required), Required);
            SetProperty(nameof(Secure), Secure);
            SetProperty(nameof(Retain), Retain);
            SetProperty(nameof(Expression), Expression);
            SetProperty(nameof(ColourExpression), ColourExpression);
        }

        public abstract object? GetDefaultValue();

        private class PropertyLookupGenerator : LookupGenerator<object>
        {
            public PropertyLookupGenerator(object[] items) : base(items)
            {
            }

            protected override void DoGenerateLookups()
            {
                var form = Items?.FirstOrDefault() as DigitalForm;
                if (form != null)
                {
                    var type = DFUtils.FormEntityType(form);
                    if (type != null)
                    {
                        foreach(var prop in DatabaseSchema.Properties(type))
                        {
                            if (prop.Editor.Editable.IsEditable())
                            {
                                AddValue(prop.Name, prop.Name);
                            }
                        }
                    }
                }
            }
        }
    }
    public abstract class DFLayoutFieldProperties<TValue, TSerialized> : DFLayoutFieldProperties
    {
        public DFLayoutFieldProperties()
        {
            Default = default;
        }

        [EditorSequence(-995)]
        public abstract TValue Default { get; set; }

        protected override void LoadProperties()
        {
            base.LoadProperties();
            Default = GetProperty("Default", default(TValue));
        }

        protected override void SaveProperties()
        {
            base.SaveProperties();
            SetProperty("Default", Default);
        }

        public sealed override void Serialize(DFSaveStorageEntry entry, object? value)
        {
            if(value is TSerialized ser)
            {
                SerializeValue(entry, ser);
            }
        }
        public sealed override object? Deserialize(DFLoadStorageEntry entry)
        {
            return DeserializeValue(entry);
        }

        public abstract void SerializeValue(DFSaveStorageEntry entry, TSerialized value);
        public abstract TSerialized DeserializeValue(DFLoadStorageEntry entry);

        public sealed override string FormatValue(object? value)
        {
            if (value is TSerialized ser)
            {
                return FormatValue(ser);
            }
            else
            {
                return "";
            }
        }

        public abstract string FormatValue(TSerialized value);

        public sealed override object? GetValue(object? value)
        {
            if (value is TSerialized ser)
            {
                return GetValue(ser);
            }
            else
            {
                return null;
            }
        }

        public abstract TValue GetValue(TSerialized value);

        public sealed override IEnumerable<KeyValuePair<string, object?>> GetAdditionalValues(object? value)
        {
            if(value is TSerialized ser)
            {
                return GetAdditionalValues(ser);
            }
            return Enumerable.Empty<KeyValuePair<string, object?>>();
        }
        public virtual IEnumerable<KeyValuePair<string, object?>> GetAdditionalValues(TSerialized value)
        {
            return Enumerable.Empty<KeyValuePair<string, object?>>();
        }

        public override IEnumerable<CoreColumn> GetDisplayColumns()
        {
            yield return new CoreColumn
            {
                ColumnName = Code,
                DataType = typeof(TValue)
            };
            foreach(var col in GetAdditionalColumns())
            {
                yield return col;
            }
        }

        public sealed override IEnumerable<KeyValuePair<string, object?>> GetDisplayValues(object? value)
        {
            if (value is TSerialized ser)
            {
                return GetDisplayValues(ser);
            }
            return Enumerable.Empty<KeyValuePair<string, object?>>();
        }
        public virtual IEnumerable<KeyValuePair<string, object?>> GetDisplayValues(TSerialized value)
        {
            yield return new KeyValuePair<string, object?>(Code, value);
            foreach (var pair in GetAdditionalValues(value))
            {
                yield return pair;
            }
        }

        public sealed override object? GetDefaultValue()
        {
            return Default;
        }
    }
}