using System;
using System.Collections.Generic;
using Marr.Data.Mapping;
using System.Linq.Expressions;

namespace Marr.Data.QGen
{
    public class InsertQueryBuilder<T> : IQueryBuilder
    {
        private DataMapper _db;
        private string _tableName;
        private T _entity;
        private MappingHelper _mappingHelper;
        private ColumnMapCollection _mappings;
        private SqlModes _previousSqlMode;
        private bool _generateQuery = true;
        private bool _getIdentityValue;
        private Dialects.Dialect _dialect;
        private ColumnMapCollection _columnsToInsert;

        public InsertQueryBuilder()
        {
            // Used only for unit testing with mock frameworks
        }

        public InsertQueryBuilder(DataMapper db)
        {
            _db = db;
            _tableName = MapRepository.Instance.GetTableName(typeof(T));
            _previousSqlMode = _db.SqlMode;
            _mappingHelper = new MappingHelper(_db);
            _mappings = MapRepository.Instance.GetColumns(typeof(T));
            _dialect = QueryFactory.CreateDialect(_db);
        }

        public virtual InsertQueryBuilder<T> TableName(string tableName)
        {
            _tableName = tableName;
            return this;
        }

        public virtual InsertQueryBuilder<T> QueryText(string queryText)
        {
            _generateQuery = false;
            _db.Command.CommandText = queryText;
            return this;
        }

        public virtual InsertQueryBuilder<T> Entity(T entity)
        {
            _entity = entity;
            return this;
        }

        /// <summary>
        /// Runs an identity query to get the value of an autoincrement field.
        /// </summary>
        /// <returns></returns>
        public virtual InsertQueryBuilder<T> GetIdentity()
        {
            if (!_dialect.HasIdentityQuery)
            {
                string err = string.Format("The current dialect '{0}' does not have an identity query implemented.", _dialect.ToString());
                throw new DataMappingException(err);
            }

            _getIdentityValue = true;
            return this;
        }

        public virtual InsertQueryBuilder<T> ColumnsIncluding(params Expression<Func<T, object>>[] properties)
        {
            List<string> columnList = new List<string>();

            foreach (var column in properties)
            {
                columnList.Add(column.GetMemberName());
            }

            return ColumnsIncluding(columnList.ToArray());
        }

        public virtual InsertQueryBuilder<T> ColumnsIncluding(params string[] properties)
        {
            _columnsToInsert = new ColumnMapCollection();

            foreach (string propertyName in properties)
            {
                _columnsToInsert.Add(_mappings.GetByFieldName(propertyName));
            }

            return this;
        }

        public virtual InsertQueryBuilder<T> ColumnsExcluding(params Expression<Func<T, object>>[] properties)
        {
            List<string> columnList = new List<string>();

            foreach (var column in properties)
            {
                columnList.Add(column.GetMemberName());
            }

            return ColumnsExcluding(columnList.ToArray());
        }

        public virtual InsertQueryBuilder<T> ColumnsExcluding(params string[] properties)
        {
            _columnsToInsert = new ColumnMapCollection();

            _columnsToInsert.AddRange(_mappings);

            foreach (string propertyName in properties)
            {
                _columnsToInsert.RemoveAll(c => c.FieldName == propertyName);
            }

            return this;
        }

        public virtual object Execute()
        {
            if (_generateQuery)
            {
                BuildQuery();
            }
            else
            {
                TryAppendIdentityQuery();
                _mappingHelper.CreateParameters<T>(_entity, _mappings.NonReturnValues, _generateQuery);
            }

            object scalar = null;

            try
            {
                _db.OpenConnection();

                scalar = _db.Command.ExecuteScalar();

                if (_getIdentityValue && !_dialect.SupportsBatchQueries)
                {
                    // Run identity query as a separate query
                    _db.Command.CommandText = _dialect.IdentityQuery;
                    scalar = _db.Command.ExecuteScalar();
                }

                _mappingHelper.SetOutputValues<T>(_entity, _mappings.OutputFields);
                if (scalar != null)
                {
                    _mappingHelper.SetOutputValues<T>(_entity, _mappings.ReturnValues, scalar);
                }
            }
            finally
            {
                _db.CloseConnection();
            }

            
            if (_generateQuery)
            {
                // Return to previous sql mode
                _db.SqlMode = _previousSqlMode;
            }

            return scalar;
        }

        public virtual string BuildQuery()
        {
            if (_entity == null)
                throw new ArgumentNullException("You must specify an entity to insert.");

            // Override SqlMode since we know this will be a text query
            _db.SqlMode = SqlModes.Text;

            var columns = _columnsToInsert ?? _mappings;

            _mappingHelper.CreateParameters<T>(_entity, columns, _generateQuery);
            IQuery query = QueryFactory.CreateInsertQuery(columns, _db, _tableName);

            _db.Command.CommandText = query.Generate();

            TryAppendIdentityQuery();

            return _db.Command.CommandText;
        }

        private void TryAppendIdentityQuery()
        {
            if (_getIdentityValue && _dialect.SupportsBatchQueries)
            {
                // Append a batched identity query
                if (!_db.Command.CommandText.EndsWith(";"))
                {
                    _db.Command.CommandText += ";";
                }
                _db.Command.CommandText += _dialect.IdentityQuery;
            }
        }
    }
}