using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace Orciid.Core
{
	/// <summary>
	/// SQL query class
	/// </summary>
	/// <remarks>
	/// This class holds information about SQL queries that are executed by Orciid.Core classes.
	/// It replaces variables with their values and provides the resulting SQL query, adjusted
	/// for the database it is to be run against, in the <see cref="SQL"/> property.
	/// Variables are in the form <c>{name[:option]}</c>, where name can consist of any alphanumeric
	/// character (must start with a letter), and option is one or more of the following letters:
	/// <c>u</c> (value is a Unicode string), <c>a</c> (value is an ASCII string),
	/// <c>l</c> (value is a LIKE comparison pattern string), <c>n</c> (if value is the minimum value
	/// for its type, insert NULL instead), <c>q</c> (don't quote string value).
	/// </remarks>
	public class Query
	{
		private string sql;
		private Hashtable parameters = new Hashtable();
		private DBConnection conn;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates a new query object
		/// </remarks>
		/// <param name="conn">The database connection this query will be run against</param>
		/// <param name="sql">The SQL statement of the query</param>
		public Query(DBConnection conn, string sql)
		{
			this.sql = sql;
			this.conn = conn;
		}

		/// <summary>
		/// Add query parameter
		/// </summary>
		/// <remarks>
		/// Before the query can be built, all required parameters must be added.
		/// </remarks>
		/// <param name="name">The name of the parameter</param>
		/// <param name="val">The value of the parameter</param>
		public void AddParam(string name, object val)
		{
			parameters.Add(name, val);
		}

		/// <summary>
		/// The resulting SQL query string
		/// </summary>
		/// <remarks>
		/// Returns the SQL query string with variables replaced with their values and 
		/// adjusted for the database it is to be run against.
		/// </remarks>
		public string SQL
		{
			get
			{
				return new Regex(
					@"(?<!\{)\{(?'name'@?[a-zA-Z]\w*)(?::(?'options'[ualnq]+))?\}(?!\})")
					.Replace(conn.AdjustSqlSyntax(sql), new MatchEvaluator(EvaluateParameter))
					.Replace("{{", "{")
					.Replace("}}", "}");
			}
		}

		private string EvaluateSingleParameter(DBConnection conn,
			object val, bool unicode, bool ascii, bool likecomparison, bool nullforminvalue,
			bool noquotes)
		{
			if (val == null)
				return "NULL";
			if (val is string)
			{
				string s = (likecomparison ? conn.LikeEncode((string)val) : conn.Encode((string)val));
				if (unicode == ascii) // autodetect string type
				{
					unicode = false;
					foreach (char c in s)
						if (c > 127)
						{
							unicode = true;
							break;
						}
				}
				if (noquotes)
					return s;
				else if (unicode)
                    return conn.UnicodeStringPrefix() + "'" + s + "'";
				else
					return "'" + s + "'";
			}
			if (val is char)
			{
				if (noquotes)
					return ((char)val).ToString();
				else
					return "'" + (char)val + "'";
			}
			if (val is DateTime)
			{
				if (nullforminvalue && (DateTime)val == DateTime.MinValue)
					return "NULL";
				else
				{
					if (noquotes)
						return conn.DateToDBFormatString((DateTime)val);
					else
						return "'" + conn.DateToDBFormatString((DateTime)val) + "'";
				}
			}			
			if (val is int)
			{
				if (nullforminvalue && (int)val == int.MinValue)
					return "NULL";
				else
					return val.ToString();
			}
			if (val is bool)
				return conn.BoolToData((bool)val);
			if (val is Enum)
				return ((int)val).ToString();
			return val.ToString();
		}

		private string EvaluateParameter(Match match)
		{
			string name = match.Groups["name"].Value;
			string options = match.Groups["options"].Value;
			if (!parameters.ContainsKey(name))
				throw new CoreException(String.Format("No value for parameter '{0}' provided.", name));
			object val = parameters[name];
			if (name.StartsWith("@"))
				return val.ToString();
			bool unicode = (options.IndexOf("u") >= 0);
			bool ascii = (options.IndexOf("a") >= 0);
			bool likecomparison = (options.IndexOf("l") >= 0);
			bool nullforminvalue = (options.IndexOf("n") >= 0);
			bool noquotes = (options.IndexOf("q") >= 0);
			if (val == null)
				return "NULL";
			if (val is ICollection)
			{
				StringBuilder s = new StringBuilder("(");
				bool needcomma = false;
				foreach (object v in (ICollection)val)
				{
					if (needcomma)
						s.Append(',');
					else
						needcomma = true;
					s.Append(EvaluateSingleParameter(conn, v, unicode, ascii, likecomparison, nullforminvalue, noquotes));
				}
				s.Append(')');
				return s.ToString();
			}
			else
				return EvaluateSingleParameter(conn, val, unicode, ascii, likecomparison, nullforminvalue, noquotes);
		}
	}
}
