using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using TextFieldParserStandard;

namespace InABox.Core
{
    public class VariableTypeLookupGenerator : LookupGenerator<DigitalFormVariable>
    {
        public VariableTypeLookupGenerator(DigitalFormVariable[] items) : base(items)
        {
            foreach (var fieldType in DFUtils.GetFieldTypes(true))
            {
                var caption = fieldType.GetCaption();
                if (string.IsNullOrWhiteSpace(caption))
                {
                    caption = CoreUtils.Neatify(fieldType.Name);
                }
                AddValue(fieldType.FullName, caption);
            }
        }
    }

    [Caption("Variables")]
    public class DigitalFormVariable : Entity, IRemotable, IPersistent, IStringAutoIncrement<DigitalFormVariable>, IOneToMany<DigitalForm>,
        ISequenceable, ILicense<DigitalFormsLicense>
    {
        private string _parameters = "";
        private DFLayoutFieldProperties _properties = new DFLayoutStringFieldProperties();

        private Type _variableType = typeof(DFLayoutStringField);

        [NullEditor]
        [Obsolete("Replaced with Form")]
        public QAFormLink QAForm => InitializeField(ref _qAForm, nameof(QAForm));
        private QAFormLink _qAForm;

        [NullEditor]
        [EntityRelationship(DeleteAction.Cascade)]
        public DigitalFormLink Form => InitializeField(ref _form, nameof(Form));
        private DigitalFormLink _form;

        [EditorSequence(1)]
        [CodeEditor(Visible = Visible.Default, Editable = Editable.Disabled)]
        public string Code { get; set; }

        [EditorSequence(2)]
        [TextBoxEditor(Visible = Visible.Default, Editable = Editable.Disabled)]
        public string Description { get; set; }

        [NullEditor]
        [Obsolete("Use VariableType Instead!")]
        public string Type { get; set; }

        [EditorSequence(3)]
        [ComboLookupEditor(typeof(VariableTypeLookupGenerator), Visible = Visible.Default, Editable = Editable.Disabled)]
        public string VariableType
        {
            get => _variableType.FullName;
            set
            {
                _variableType = DFUtils.GetFieldType(value) ?? typeof(DFLayoutStringField);
                _properties = CreateProperties();
            }
        }
        
        [EditorSequence(4)]
        [TextBoxEditor]
        public String Group { get; set; }
        
        [EditorSequence(5)]
        [MemoEditor(Visible = Visible.Hidden, Editable = Editable.Disabled)]
        public string Parameters
        {
            get => _parameters;
            set
            {
                _parameters = value;
                _properties = CreateProperties();
            }
        }

        [CheckBoxEditor(Visible = Visible.Default, Editable = Editable.Disabled)]
        [EditorSequence(6)]
        public bool Required { get; set; }

        [CheckBoxEditor(Visible = Visible.Default, Editable = Editable.Disabled)]
        [EditorSequence(7)]
        public bool Secure { get; set; }

        [CheckBoxEditor(Visible = Visible.Default, Editable = Editable.Disabled)]
        [EditorSequence(8)]
        public bool Retain { get; set; }

        [CheckBoxEditor(Visible = Visible.Optional)]
        [EditorSequence(9)]
        public bool Hidden { get; set; } = false;

        [NullEditor]
        public long Sequence { get; set; }

        public Expression<Func<DigitalFormVariable, string>> AutoIncrementField()
        {
            return x => x.Code;
        }

        public Filter<DigitalFormVariable> AutoIncrementFilter()
        {
            return new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(Form.ID);
        }

        public string AutoIncrementPrefix() => "V";

        public string AutoIncrementFormat()
        {
            return "{0:D3}";
        }

        public DFLayoutFieldProperties GetProperties() => _properties;

        public string FormatValue(object? value)
        {
            return _properties.FormatValue(value);
        }

        public void Serialize(DFSaveStorageEntry storage, object? value)
        {
            _properties.Serialize(storage, value);
        }

        public object? Deserialize(DFLoadStorageEntry storage)
        {
            return _properties.Deserialize(storage);
        }

        public IEnumerable<CoreColumn> GetDisplayColumns()
        {
            return _properties.GetDisplayColumns().ToArray();
        }
        public IEnumerable<KeyValuePair<string, object?>> GetDisplayValues(object? value)
        {
            return _properties.GetDisplayValues(value);
        }

        public IEnumerable<CoreColumn> GetVariableColumns()
        {
            yield return new CoreColumn
            {
                ColumnName = Code,
                DataType = DataType(FieldType())
            };

            foreach(var column in _properties.GetAdditionalColumns())
            {
                yield return column;
            }
        }

        public IEnumerable<KeyValuePair<string, object?>> GetVariableColumnValues(object? value)
        {
            yield return new KeyValuePair<string, object?>(Code, _properties.GetValue(value));

            foreach (var pair in _properties.GetAdditionalValues(value))
            {
                yield return pair;
            }
        }

        public static Type DataType(Type variableType)
        {
            var propertiesType = GetPropertiesType(variableType);
            var baseType = propertiesType?.GetSuperclassDefinition(typeof(DFLayoutFieldProperties<,>));
            var dataType = baseType?.GenericTypeArguments[0] ?? typeof(string);
            return dataType;
        }

        public Type FieldType()
        {
            return _variableType;
        }
        public void SetFieldType(Type type)
        {
            _variableType = type.HasInterface<IDFLayoutFormField>() ? type : typeof(DFLayoutStringField);
        }

        public static Type? GetPropertiesType(Type fieldType)
        {
            var fieldSuperType = fieldType.GetSuperclassDefinition(typeof(DFLayoutField<>));
            if (fieldSuperType != null)
            {
                return fieldSuperType.GenericTypeArguments[0];
            }
            return null;
        }

        public DFLayoutFieldProperties CreateProperties()
        {
            var propertiesType = GetPropertiesType(_variableType) ?? typeof(DFLayoutStringFieldProperties);

            var properties = (Activator.CreateInstance(propertiesType) as DFLayoutFieldProperties)!;
            properties.LoadFromString(Parameters);
            properties.Code = Code;
            properties.Hidden = Hidden;
            return properties;
        }

        public DFLayoutFieldProperties? LoadProperties(DFLayoutField field)
        {
            DFLayoutFieldProperties? properties = null;
            if (field.GetType().IsSubclassOfRawGeneric(typeof(DFLayoutField<>)))
            {
                properties = field.GetType().GetProperty("Properties").GetValue(field) as DFLayoutFieldProperties;
            }
            if(properties != null)
            {
                properties.LoadFromString(Parameters);
                properties.Hidden = Hidden;
            }
            return properties;
        }

        public void SaveProperties(Type variableType, DFLayoutFieldProperties properties)
        {
            _variableType = variableType;
            SaveProperties(properties);
        }

        public void SaveProperties(DFLayoutFieldProperties properties)
        {
            Code = properties.Code;
            Description = properties.Description;
            Required = properties.Required;
            Secure = properties.Secure;
            Retain = properties.Retain;
            Hidden = properties.Hidden;
            Parameters = properties.SaveToString();
        }
    }
}