using System.Numerics;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace DapperExtensions.Mapper
{
    /// <summary>
    /// 数据库配置接口
    /// </summary>
    public interface IClassMapper
    {
        /// <summary>
        /// 数据库名称
        /// </summary>
        string DbName { get; }

        /// <summary>
        /// 架构名称
        /// </summary>
        string SchemaName { get; }

        /// <summary>
        /// 表名称
        /// </summary>
        string TableName { get; }

        /// <summary>
        /// 属性字典
        /// </summary>
        IDictionary<string, IPropertyMap> Properties { get; }

        /// <summary>
        /// 实体类型
        /// </summary>
        Type EntityType { get; }
    }

    /// <summary>
    /// 泛型数据库接口配置
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IClassMapper<T> : IClassMapper where T : class
    {

    }

    /// <summary>
    /// Maps an entity to a table through a collection of property maps.
    /// </summary>
    public class ClassMapper<T> : IClassMapper<T> where T : class
    {
        private readonly Dictionary<Type, KeyType> _propertyTypeKeyTypeMapping;

        /// <summary>
        /// 数据库名称
        /// </summary>
        public string DbName { get; protected set; }

        /// <summary>
        /// 架构名称
        /// </summary>
        public string SchemaName { get; protected set; }

        /// <summary>
        /// 表名称
        /// </summary>
        public string TableName { get; protected set; }

        /// <summary>
        /// 属性字典
        /// </summary>
        public IDictionary<string, IPropertyMap> Properties { get; private set; }

        /// <summary>
        /// 实体类型
        /// </summary>
        public Type EntityType
        {
            get { return typeof(T); }
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        public ClassMapper()
        {
            _propertyTypeKeyTypeMapping = new Dictionary<Type, KeyType>
                                             {
                                                 { typeof(byte), KeyType.Identity }, { typeof(byte?), KeyType.Identity },
                                                 { typeof(sbyte), KeyType.Identity }, { typeof(sbyte?), KeyType.Identity },
                                                 { typeof(short), KeyType.Identity }, { typeof(short?), KeyType.Identity },
                                                 { typeof(ushort), KeyType.Identity }, { typeof(ushort?), KeyType.Identity },
                                                 { typeof(int), KeyType.Identity }, { typeof(int?), KeyType.Identity },
                                                 { typeof(uint), KeyType.Identity}, { typeof(uint?), KeyType.Identity },
                                                 { typeof(long), KeyType.Identity }, { typeof(long?), KeyType.Identity },
                                                 { typeof(ulong), KeyType.Identity }, { typeof(ulong?), KeyType.Identity },
                                                 { typeof(BigInteger), KeyType.Identity }, { typeof(BigInteger?), KeyType.Identity },
                                                 { typeof(Guid), KeyType.Guid }, { typeof(Guid?), KeyType.Guid },
                                             };

            Properties = new Dictionary<string, IPropertyMap>();
            Table(typeof(T).Name);
        }

        /// <summary>
        /// 设置数据库名称
        /// </summary>
        /// <param name="dbName">数据库名称</param>
        public virtual void DB(string dbName)
        {
            DbName = dbName;
        }

        /// <summary>
        /// 设置架构名称
        /// </summary>
        /// <param name="schemaName">架构名称</param>
        public virtual void Schema(string schemaName)
        {
            SchemaName = schemaName;
        }

        /// <summary>
        /// 设置表名称
        /// </summary>
        /// <param name="tableName">表名</param>
        public virtual void Table(string tableName)
        {
            TableName = tableName;
        }

        /// <summary>
        /// 自动映射
        /// </summary>
        protected virtual void AutoMap()
        {
            AutoMap(null);
        }

        /// <summary>
        /// 根据实体类型和属性自动映射
        /// </summary>
        /// <param name="canMap"></param>
        protected virtual void AutoMap(Func<Type, PropertyInfo, bool> canMap)
        {
            Type type = typeof(T);
            bool hasDefinedKey = Properties.Any(p => p.Value.KeyType != KeyType.NotAKey);
            PropertyMap keyMap = null;
            foreach (var propertyInfo in type.GetProperties())
            {
                if (Properties.Any(p => p.Value.Name.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
                {
                    continue;
                }

                if ((canMap != null && !canMap(type, propertyInfo)))
                {
                    continue;
                }

                PropertyMap map = Map(propertyInfo);
                if (!hasDefinedKey)
                {
                    if (string.Equals(map.PropertyInfo.Name, "id", StringComparison.InvariantCultureIgnoreCase))
                    {
                        keyMap = map;
                    }
                    else if (keyMap == null && map.PropertyInfo.Name.EndsWith("id", true, CultureInfo.InvariantCulture))
                    {
                        keyMap = map;
                    }
                }
            }

            if (keyMap != null)
            {
                keyMap.Key(_propertyTypeKeyTypeMapping.ContainsKey(keyMap.PropertyInfo.PropertyType)
                    ? _propertyTypeKeyTypeMapping[keyMap.PropertyInfo.PropertyType]
                    : KeyType.Assigned);
            }
        }

        /// <summary>
        /// Fluently, maps an entity property to a column
        /// </summary>
        /// <param name="expression">表达式树</param>
        /// <returns></returns>
        protected PropertyMap Map(Expression<Func<T, object>> expression)
        {
            PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
            return Map(propertyInfo);
        }

        /// <summary>
        /// Fluently, maps an entity property to a column
        /// </summary>
        /// <param name="propertyInfo">属性</param>
        /// <returns></returns>
        protected PropertyMap Map(PropertyInfo propertyInfo)
        {
            PropertyMap result = new PropertyMap(propertyInfo, TableName, DbName, SchemaName);
            Properties.Add(propertyInfo.Name, result);
            return result;
        }
    }
}