using System;
using System.Data;
using System.Reflection;
using ZeroLevel.Services.ObjectMapping;
using ZeroLevel.Services.Reflection;

namespace ZeroLevel.MsSql
{
    public class DbField : MapMemberInfo, IDbField
    {
        public bool AutoIncrement { get; internal set; }
        public bool IsIdentity { get; internal set; }
        public bool IsIndexed { get; internal set; }
        public bool AllowNull { get; internal set; }
        public long Size { get; internal set; }
        public DbType DbType { get; internal set; }

        private DbField(Action<object, object> setter, Func<object, object> getter)
            :base(setter, getter)
        {
        }

        private static bool IsNullable(Type type)
        {
            if (!type.IsValueType) return true; // ref-type
            if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
            return false; // value-type
        }

        public static DbField FromField(FieldInfo fieldInfo)
        {
            var meta = ((DbMemberAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(DbMemberAttribute)));
            var index = ((DbIndexAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(DbIndexAttribute)));
            var field = new DbField(TypeGetterSetterBuilder.BuildSetter(fieldInfo), TypeGetterSetterBuilder.BuildGetter(fieldInfo))
            {
                Name = fieldInfo.Name,
                IsIdentity = meta?.IsIdentity ?? false,
                AllowNull = meta?.AllowNull ?? true,
                AutoIncrement = meta?.AutoIncrement ?? false,
                Size = meta?.Size ?? -1,
                IsIndexed = index != null
            };
            field.IsField = true;
            var type = fieldInfo.FieldType;
            field.ClrType = type;
            field.DbType = type.ToDbType();
            return field;
        }

        public static DbField FromProperty(PropertyInfo propertyInfo)
        {
            var meta = ((DbMemberAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(DbMemberAttribute)));
            var index = ((DbIndexAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(DbIndexAttribute)));
            var field = new DbField(TypeGetterSetterBuilder.BuildSetter(propertyInfo), TypeGetterSetterBuilder.BuildGetter(propertyInfo))
            {
                Name = propertyInfo.Name,
                IsIdentity = meta?.IsIdentity ?? false,
                AllowNull = meta?.AllowNull ?? true,
                AutoIncrement = meta?.AutoIncrement ?? false,
                Size = meta?.Size ?? -1,
                IsIndexed = index != null
            };
            field.IsField = false;
            var type = propertyInfo.PropertyType;
            field.ClrType = type;
            field.DbType = type.ToDbType();
            return field;
        }

        public new static DbField FromMember(MemberInfo memberInfo)
        {
            switch (memberInfo.MemberType)
            {
                case MemberTypes.Field:
                    return FromField(memberInfo as FieldInfo);
                case MemberTypes.Property:
                    return FromProperty(memberInfo as PropertyInfo);
            }
            return null;
        }

        public void SetValue(object instance, object dbvalue, Func<DbField, object, object> converter = null)
        {
            var value = (null == dbvalue || DBNull.Value == dbvalue) ? null : dbvalue;
            if (null != converter)
            {
                value = converter(this, value);
            }
            if (null == value && false == IsNullable(ClrType))
                Setter(instance, TypeExtensions.GetDefault(ClrType));
            else
                Setter(instance, value);
        }

    }
}
