using System;
using System.Linq;
using System.Linq.Expressions;
using System.Data;
using Marr.Data.Mapping.Strategies;

namespace Marr.Data.Mapping
{
    /// <summary>
    /// This class has fluent methods that are used to easily configure column mappings.
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public class ColumnMapBuilder<TEntity>
    {
        private FluentMappings.MappingsFluentEntity<TEntity> _fluentEntity;
        private string _currentPropertyName;

        public ColumnMapBuilder(FluentMappings.MappingsFluentEntity<TEntity> fluentEntity, ColumnMapCollection mappedColumns)
        {
            _fluentEntity = fluentEntity;
            MappedColumns = mappedColumns;
        }

        /// <summary>
        /// Gets the list of column mappings that are being configured.
        /// </summary>
        public ColumnMapCollection MappedColumns { get; private set; }

        #region - Fluent Methods -

        /// <summary>
        /// Initializes the configurator to configure the given property.
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        public ColumnMapBuilder<TEntity> For(Expression<Func<TEntity, object>> property)
        {
            For(property.GetMemberName());
            return this;
        }

        /// <summary>
        /// Initializes the configurator to configure the given property or field.
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        public ColumnMapBuilder<TEntity> For(string propertyName)
        {
            _currentPropertyName = propertyName;

            // Try to add the column map if it doesn't exist
            if (MappedColumns.GetByFieldName(_currentPropertyName) == null)
            {
                TryAddColumnMapForField(_currentPropertyName);
            }

            return this;
        }

        public ColumnMapBuilder<TEntity> SetPrimaryKey()
        {
            AssertCurrentPropertyIsSet();
            return SetPrimaryKey(_currentPropertyName);
        }

        public ColumnMapBuilder<TEntity> SetPrimaryKey(string propertyName)
        {
            MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsPrimaryKey = true;
            return this;
        }

        public ColumnMapBuilder<TEntity> SetAutoIncrement()
        {
            AssertCurrentPropertyIsSet();
            return SetAutoIncrement(_currentPropertyName);
        }

        public ColumnMapBuilder<TEntity> SetAutoIncrement(string propertyName)
        {
            MappedColumns.GetByFieldName(propertyName).ColumnInfo.IsAutoIncrement = true;
            return this;
        }

        public ColumnMapBuilder<TEntity> SetColumnName(string columnName)
        {
            AssertCurrentPropertyIsSet();
            return SetColumnName(_currentPropertyName, columnName);
        }

        public ColumnMapBuilder<TEntity> SetColumnName(string propertyName, string columnName)
        {
            MappedColumns.GetByFieldName(propertyName).ColumnInfo.Name = columnName;
            return this;
        }

        public ColumnMapBuilder<TEntity> SetReturnValue()
        {
            AssertCurrentPropertyIsSet();
            return SetReturnValue(_currentPropertyName);
        }

        public ColumnMapBuilder<TEntity> SetReturnValue(string propertyName)
        {
            MappedColumns.GetByFieldName(propertyName).ColumnInfo.ReturnValue = true;
            return this;
        }

        public ColumnMapBuilder<TEntity> SetSize(int size)
        {
            AssertCurrentPropertyIsSet();
            return SetSize(_currentPropertyName, size);
        }

        public ColumnMapBuilder<TEntity> SetSize(string propertyName, int size)
        {
            MappedColumns.GetByFieldName(propertyName).ColumnInfo.Size = size;
            return this;
        }

        public ColumnMapBuilder<TEntity> SetAltName(string altName)
        {
            AssertCurrentPropertyIsSet();
            return SetAltName(_currentPropertyName, altName);
        }

        public ColumnMapBuilder<TEntity> SetAltName(string propertyName, string altName)
        {
            MappedColumns.GetByFieldName(propertyName).ColumnInfo.AltName = altName;
            return this;
        }

        public ColumnMapBuilder<TEntity> SetParamDirection(ParameterDirection direction)
        {
            AssertCurrentPropertyIsSet();
            return SetParamDirection(_currentPropertyName, direction);
        }

        public ColumnMapBuilder<TEntity> SetParamDirection(string propertyName, ParameterDirection direction)
        {
            MappedColumns.GetByFieldName(propertyName).ColumnInfo.ParamDirection = direction;
            return this;
        }

        public ColumnMapBuilder<TEntity> Ignore(Expression<Func<TEntity, object>> property)
        {
            string propertyName = property.GetMemberName();
            return Ignore(propertyName);
        }

        public ColumnMapBuilder<TEntity> Ignore(string propertyName)
        {
            var columnMap = MappedColumns.GetByFieldName(propertyName);
            MappedColumns.Remove(columnMap);
            return this;
        }

        public ColumnMapBuilder<TEntity> PrefixAltNames(string prefix)
        {
            MappedColumns.PrefixAltNames(prefix);
            return this;
        }

        public ColumnMapBuilder<TEntity> SuffixAltNames(string suffix)
        {
            MappedColumns.SuffixAltNames(suffix);
            return this;
        }

        public FluentMappings.MappingsFluentTables<TEntity> Tables
        {
            get
            {
                if (_fluentEntity == null)
                {
                    throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
                }

                return _fluentEntity.Table;
            }
        }

        public FluentMappings.MappingsFluentRelationships<TEntity> Relationships
        {
            get
            {
                if (_fluentEntity == null)
                {
                    throw new Exception("This property is not compatible with the obsolete 'MapBuilder' class.");
                }

                return _fluentEntity.Relationships;
            }
        }

        public FluentMappings.MappingsFluentEntity<TNewEntity> Entity<TNewEntity>()
        {
            return new FluentMappings.MappingsFluentEntity<TNewEntity>(true);
        }

        /// <summary>
        /// Tries to add a ColumnMap for the given field name.  
        /// Throws and exception if field cannot be found.
        /// </summary>
        private void TryAddColumnMapForField(string fieldName)
        {
            // Set strategy to filter for public or private fields
            ConventionMapStrategy strategy = new ConventionMapStrategy(false);

            // Find the field that matches the given field name
            strategy.ColumnPredicate = mi => mi.Name == fieldName;
            ColumnMap columnMap = strategy.MapColumns(typeof(TEntity)).FirstOrDefault();

            if (columnMap == null)
            {
                throw new DataMappingException(string.Format("Could not find the field '{0}' in '{1}'.",
                    fieldName,
                    typeof(TEntity).Name));
            }
            MappedColumns.Add(columnMap);
        }

        /// <summary>
        /// Throws an exception if the "current" property has not been set.
        /// </summary>
        private void AssertCurrentPropertyIsSet()
        {
            if (string.IsNullOrEmpty(_currentPropertyName))
            {
                throw new DataMappingException("A property must first be specified using the 'For' method.");
            }
        }

        #endregion
    }
}
