using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Marr.Data.QGen
{
    /// <summary>
    /// Decorates the SelectQuery by wrapping it in a paging query.
    /// </summary>
    public class SqlitePagingQueryDecorator : IQuery
    {
        private SelectQuery _innerQuery;
        private int _skip;
        private int _take;

        public SqlitePagingQueryDecorator(SelectQuery innerQuery, int skip, int take)
        {
            if (string.IsNullOrEmpty(innerQuery.OrderBy.ToString()))
            {
                throw new DataMappingException("A paged query must specify an order by clause.");
            }

            _innerQuery = innerQuery;
            _skip = skip;
            _take = take;
        }

        public string Generate()
        {
            if (_innerQuery.IsView || _innerQuery.IsJoin)
            {
                return ComplexPaging();
            }
            return SimplePaging();
        }

        private string SimplePaging()
        {
            // Create paged query
            StringBuilder sql = new StringBuilder();

            _innerQuery.BuildSelectClause(sql);
            _innerQuery.BuildFromClause(sql);
            _innerQuery.BuildJoinClauses(sql);
            _innerQuery.BuildWhereClause(sql);
            _innerQuery.BuildOrderClause(sql);
            sql.AppendLine(String.Format(" LIMIT {0},{1}", _skip, _take));

            return sql.ToString();
        }

        private string ComplexPaging()
        {
            var baseTable = _innerQuery.Tables.First();


            StringBuilder sql = new StringBuilder();

            _innerQuery.BuildSelectClause(sql);
            sql.Append(" FROM (");
            BuildSimpleInnerSelect(sql);
            _innerQuery.BuildFromClause(sql);
            _innerQuery.BuildJoinClauses(sql);
            _innerQuery.BuildWhereClause(sql);
            BuildGroupBy(sql);
            BuildOrderBy(sql);
            sql.AppendFormat(" LIMIT {0},{1}", _skip, _take);
            sql.AppendFormat(") AS {0} ", _innerQuery.Dialect.CreateToken(baseTable.Alias));

            _innerQuery.BuildJoinClauses(sql);

            return sql.ToString();
        }

        public void BuildSelectClause(StringBuilder sql)
        {
            List<string> appended = new List<string>();

            sql.Append("SELECT ");

            int startIndex = sql.Length;

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

                    if (sql.Length > startIndex && sql[sql.Length - 1] != ',')
                        sql.Append(",");

                    if (join is View)
                    {
                        string token = _innerQuery.Dialect.CreateToken(string.Concat(join.Alias, ".", _innerQuery.NameOrAltName(c.ColumnInfo)));
                        if (appended.Contains(token))
                            continue;

                        sql.Append(token);
                        appended.Add(token);
                    }
                    else
                    {
                        string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
                        if (appended.Contains(token))
                            continue;

                        sql.Append(_innerQuery.Dialect.CreateToken(token));

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

        private void BuildSimpleInnerSelect(StringBuilder sql)
        {
            sql.Append("SELECT ");
            int startIndex = sql.Length;

            // COLUMNS
            var join = _innerQuery.Tables.First();

            for (int i = 0; i < join.Columns.Count; i++)
            {
                var c = join.Columns[i];

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

                string token = string.Concat(join.Alias, ".", c.ColumnInfo.Name);
                sql.Append(_innerQuery.Dialect.CreateToken(token));
            }

        }

        private void BuildOrderBy(StringBuilder sql)
        {
            sql.Append(_innerQuery.OrderBy.BuildQuery(false));
        }

        private void BuildGroupBy(StringBuilder sql)
        {
            var baseTable = _innerQuery.Tables.First();
            var primaryKeyColumn = baseTable.Columns.Single(c => c.ColumnInfo.IsPrimaryKey);

            string token = _innerQuery.Dialect.CreateToken(string.Concat(baseTable.Alias, ".", primaryKeyColumn.ColumnInfo.Name));
            sql.AppendFormat(" GROUP BY {0}", token);
        }
    }
}
