using System;
using System.Data;
using System.Collections.Generic;
using Migrator.Framework;

namespace Migrator.Providers
{
    /// <summary>
    /// Defines the implementations specific details for a particular database.
    /// </summary>
    public abstract class Dialect
    {
        private readonly Dictionary<ColumnProperty, string> propertyMap = new Dictionary<ColumnProperty, string>();
        private readonly TypeNames typeNames = new TypeNames();

        protected Dialect()
        {
            RegisterProperty(ColumnProperty.Null, "NULL");
            RegisterProperty(ColumnProperty.NotNull, "NOT NULL");
            RegisterProperty(ColumnProperty.Unique, "UNIQUE");
            RegisterProperty(ColumnProperty.PrimaryKey, "PRIMARY KEY");
        }

        public abstract Type TransformationProvider { get; }

        public ITransformationProvider NewProviderForDialect(string connectionString)
        {
            return (ITransformationProvider)Activator.CreateInstance(TransformationProvider, this, connectionString);
        }

        /// <summary>
        /// Subclasses register a typename for the given type code and maximum
        /// column length. <c>$l</c> in the type name will be replaced by the column
        /// length (if appropriate)
        /// </summary>
        /// <param name="code">The typecode</param>
        /// <param name="capacity">Maximum length of database type</param>
        /// <param name="name">The database type name</param>
        protected void RegisterColumnType(DbType code, int capacity, string name)
        {
            typeNames.Put(code, capacity, name);
        }

        /// <summary>
        /// Suclasses register a typename for the given type code. <c>$l</c> in the 
        /// typename will be replaced by the column length (if appropriate).
        /// </summary>
        /// <param name="code">The typecode</param>
        /// <param name="name">The database type name</param>
        protected void RegisterColumnType(DbType code, string name)
        {
            typeNames.Put(code, name);
        }

        public ColumnPropertiesMapper GetColumnMapper(Column column)
        {
            string type = column.Size > 0 ? GetTypeName(column.Type, column.Size) : GetTypeName(column.Type);
            if (!IdentityNeedsType && column.IsIdentity)
                type = String.Empty;

            return new ColumnPropertiesMapper(this, type);
        }

        /// <summary>
        /// Get the name of the database type associated with the given 
        /// </summary>
        /// <param name="type">The DbType</param>
        /// <returns>The database type name used by ddl.</returns>
        public virtual string GetTypeName(DbType type)
        {
            string result = typeNames.Get(type);
            if (result == null)
            {
                throw new Exception(string.Format("No default type mapping for DbType {0}", type));
            }

            return result;
        }

        /// <summary>
        /// Get the name of the database type associated with the given 
        /// </summary>
        /// <param name="type">The DbType</param>
        /// <returns>The database type name used by ddl.</returns>
        /// <param name="length"></param>
        public virtual string GetTypeName(DbType type, int length)
        {
            return GetTypeName(type, length, 0, 0);
        }

        /// <summary>
        /// Get the name of the database type associated with the given 
        /// </summary>
        /// <param name="type">The DbType</param>
        /// <returns>The database type name used by ddl.</returns>
        /// <param name="length"></param>
        /// <param name="precision"></param>
        /// <param name="scale"></param>
        public virtual string GetTypeName(DbType type, int length, int precision, int scale)
        {
            string resultWithLength = typeNames.Get(type, length, precision, scale);
            if (resultWithLength != null)
                return resultWithLength;

            return GetTypeName(type);
        }

        public void RegisterProperty(ColumnProperty property, string sql)
        {
            if (!propertyMap.ContainsKey(property))
            {
                propertyMap.Add(property, sql);
            }
            propertyMap[property] = sql;
        }

        public string SqlForProperty(ColumnProperty property)
        {
            if (propertyMap.ContainsKey(property))
            {
                return propertyMap[property];
            }
            return String.Empty;
        }

        public virtual bool ColumnNameNeedsQuote
        {
            get { return false; }
        }

        public virtual bool TableNameNeedsQuote
        {
            get { return false; }
        }

        public virtual bool ConstraintNameNeedsQuote
        {
            get { return false; }
        }

        public virtual bool IdentityNeedsType
        {
            get { return true; }
        }

        public virtual bool NeedsNotNullForIdentity
        {
            get { return true; }
        }

        public virtual bool SupportsIndex
        {
            get { return true; }
        }

        public virtual string Quote(string value)
        {
            return String.Format(QuoteTemplate, value);
        }

        public virtual string QuoteTemplate
        {
            get { return "\"{0}\""; }
        }

        public virtual string Default(object defaultValue)
        {
            if (defaultValue is String && defaultValue.ToString() == String.Empty)
            {
                defaultValue = "''";
            }

            return String.Format("DEFAULT {0}", defaultValue);
        }

        public ColumnPropertiesMapper GetAndMapColumnProperties(Column column)
        {
            ColumnPropertiesMapper mapper = GetColumnMapper(column);
            mapper.MapColumnProperties(column);
            if (column.DefaultValue != null)
            {
                if (column.DefaultValue is String && column.DefaultValue.ToString() == string.Empty)
                {
                    column.DefaultValue = @"''";
                }

                mapper.Default = column.DefaultValue;
            }
            return mapper;
        }
    }
}