using System.Text;
using Marr.Data.Mapping;
using Marr.Data.QGen.Dialects;

namespace Marr.Data.QGen
{
    /// <summary>
    /// This class is responsible for creating a select query.
    /// </summary>
    public class SelectQuery : IQuery
    {
        public Dialect Dialect { get; set; }
        public string WhereClause { get; set; }
        public ISortQueryBuilder OrderBy { get; set; }
        public TableCollection Tables { get; set; }
        public bool UseAltName;

        public SelectQuery(Dialect dialect, TableCollection tables, string whereClause, ISortQueryBuilder orderBy, bool useAltName)
        {
            Dialect = dialect;
            Tables = tables;
            WhereClause = whereClause;
            OrderBy = orderBy;
            UseAltName = useAltName;
        }

        public bool IsView
        {
            get
            {
                return Tables[0] is View;
            }
        }

        public bool IsJoin
        {
            get
            {
                return Tables.Count > 1;
            }
        }

        public virtual string Generate()
        {
            StringBuilder sql = new StringBuilder();

            BuildSelectClause(sql);
            BuildFromClause(sql);
            BuildJoinClauses(sql);
            BuildWhereClause(sql);
            BuildOrderClause(sql);

            return sql.ToString();
        }

        public void BuildSelectClause(StringBuilder sql)
        {
            sql.Append("SELECT ");

            int startIndex = sql.Length;

            // COLUMNS
            foreach (Table join in Tables)
            {
                for (int i = 0; i < join.Columns.Count; i++)
                {
                    var c = join.Columns[i];

                    if (sql.Length > startIndex)
                        sql.Append(",");

                    if (join is View)
                    {
                        string token = string.Concat(join.Alias, ".", NameOrAltName(c.ColumnInfo));
                        sql.Append(Dialect.CreateToken(token));
                    }
                    else
                    {
                        string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
                        sql.Append(Dialect.CreateToken(token));

                        if (UseAltName && c.ColumnInfo.AltName != null && c.ColumnInfo.AltName != c.ColumnInfo.Name)
                        {
                            string altName = c.ColumnInfo.AltName;
                            sql.AppendFormat(" AS {0}", altName);
                        }
                    }
                }
            }
        }

        public string NameOrAltName(IColumnInfo columnInfo)
        {
            if (UseAltName && columnInfo.AltName != null && columnInfo.AltName != columnInfo.Name)
            {
                return columnInfo.AltName;
            }
            return columnInfo.Name;
        }

        public void BuildFromClause(StringBuilder sql)
        {
            // BASE TABLE
            Table baseTable = Tables[0];
            sql.AppendFormat(" FROM {0} {1} ", Dialect.CreateToken(baseTable.Name), Dialect.CreateToken(baseTable.Alias));
        }

        public void BuildJoinClauses(StringBuilder sql)
        {
            // JOINS
            for (int i = 1; i < Tables.Count; i++)
            {
                if (Tables[i].JoinType != JoinType.None)
                {
                    sql.AppendFormat("{0} {1} {2} {3} ",
                        TranslateJoin(Tables[i].JoinType),
                        Dialect.CreateToken(Tables[i].Name),
                        Dialect.CreateToken(Tables[i].Alias),
                        Tables[i].JoinClause);
                }
            }
        }

        public void BuildWhereClause(StringBuilder sql)
        {
            sql.Append(WhereClause);
        }

        public void BuildOrderClause(StringBuilder sql)
        {
            sql.Append(OrderBy.ToString());
        }       

        private string TranslateJoin(JoinType join)
        {
            switch (join)
            {
                case JoinType.Inner:
                    return "INNER JOIN";
                case JoinType.Left:
                    return "LEFT JOIN";
                case JoinType.Right:
                    return "RIGHT JOIN";
                default:
                    return string.Empty;
            }
        }
    }
}
