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

using System.Web;
using System.Data;

using IBM.Data.DB2;
using System.Collections;

namespace B4Restore.Common.Business
{
	public enum FilterCondition
	{
		Default,
		In,
		Like,
		Equal,
		NotEqual,
		GreaterThan,
		LessThan,
		LikeIgnoreCase,
		GreaterEqualThan,
		LessEqualThan,
		OR_In,
		OR_Like,
		OR_Equal,
		OR_NotEqual,
		OR_GreaterThan,
		OR_LessThan,
		OR_LikeIgnoreCase,
		OR_GreaterEqualThan,
		OR_LessEqualThan,
		IsNULL,
		IsNonNULL,
		OR_IsNULL,
		OR_IsNonNULL,
		AND_Custom
	}

	public enum SeriesTypeConnector
	{
		NONE,
		AND,
		OR
	}

	public enum BoolCondition
	{
		None = 0,
		Sufix_AND,
		Sufix_OR,
		Prefix_AND,
		Prefix_OR
	}

	[Serializable]
	public class FilterMask
	{
		public static string Equal = " AND ({0} = {1})";
		public static string In = " AND ({0} in ({1}))";
		public static string NotEqual = " AND ({0} <> {1})";
		public static string GreaterThan = " AND ({0} > {1})";
		public static string LessThan = " AND ({0} < {1})";
		public static string GreaterEqualThan = " AND ({0} >= {1})";
		public static string LessEqualThan = " AND ({0} <= {1})";
		//
		public static string Like = @" AND ({0} LIKE '%{1}%' escape '\')";
		public static string LikeIgnoreCase = @" AND (UPPER({0}) LIKE UPPER('%{1}%') escape '\')";


		public static string OR_Equal = " OR ({0} = {1})";
		public static string OR_In = " OR ({0} in ({1}))";
		public static string OR_NotEqual = " OR ({0} <> {1})";
		public static string OR_GreaterThan = " OR ({0} > {1})";
		public static string OR_LessThan = " OR ({0} < {1})";
		public static string OR_GreaterEqualThan = " OR ({0} >= {1})";
		public static string OR_LessEqualThan = " OR ({0} <= {1})";
		//
		public static string OR_Like = @" OR ({0} LIKE '%{1}%' escape '\')";
		public static string OR_LikeIgnoreCase = @" OR (UPPER({0}) LIKE UPPER('%{1}%') escape '\')";


		public static string IsNULL = @" AND ({0} is null)";
		public static string IsNonNULL = @" AND ({0} is not null)";
		public static string OR_IsNULL = @" OR ({0} is null)";
		public static string OR_IsNonNULL = @" OR ({0} is not null)";

		public static string AND_Custom = @" AND ({0})";


		public static string GetFilterMask(FilterCondition filterCondition, string defaultMask)
		{
			switch (filterCondition)
			{
				case FilterCondition.Default:
					return defaultMask;
				case FilterCondition.Like:
					return Like;
				case FilterCondition.In:
					return In;
				case FilterCondition.Equal:
					return Equal;
				case FilterCondition.NotEqual:
					return NotEqual;
				case FilterCondition.GreaterThan:
					return GreaterThan;
				case FilterCondition.LessThan:
					return LessThan;
				case FilterCondition.GreaterEqualThan:
					return GreaterEqualThan;
				case FilterCondition.LessEqualThan:
					return LessEqualThan;
				case FilterCondition.LikeIgnoreCase:
					return LikeIgnoreCase;

				case FilterCondition.OR_Like:
					return OR_Like;
				case FilterCondition.OR_In:
					return OR_In;
				case FilterCondition.OR_Equal:
					return OR_Equal;
				case FilterCondition.OR_NotEqual:
					return OR_NotEqual;
				case FilterCondition.OR_GreaterThan:
					return OR_GreaterThan;
				case FilterCondition.OR_LessThan:
					return OR_LessThan;
				case FilterCondition.OR_GreaterEqualThan:
					return OR_GreaterEqualThan;
				case FilterCondition.OR_LessEqualThan:
					return OR_LessEqualThan;
				case FilterCondition.OR_LikeIgnoreCase:
					return OR_LikeIgnoreCase;

				case FilterCondition.IsNULL:
					return IsNULL;
				case FilterCondition.IsNonNULL:
					return IsNonNULL;
				case FilterCondition.OR_IsNULL:
					return OR_IsNULL;
				case FilterCondition.OR_IsNonNULL:
					return OR_IsNonNULL;
				case FilterCondition.AND_Custom:
					return AND_Custom;

				default:
					return " AND (1 = 1)";
			}
		}
	}

	[Serializable]
	public class QueryParam
	{
		[NonSerialized]
		private readonly DB2Parameter _db2Param;

		public FilterCondition FilterCondition { get; set; }

		public bool RemoveBeforeFill { get; set; }

		public QueryParam(string parameterName, object value)
			: this(parameterName, value, FilterCondition.Default)
		{
		}

		public QueryParam(string parameterName, object value, FilterCondition filterCondition)
		{
			_db2Param = new DB2Parameter(parameterName, value);
			FilterCondition = filterCondition;
		}

		public string ParameterName
		{
			get { return DB2Param.ParameterName; }
			set { DB2Param.ParameterName = value; }
		}

		public object Value
		{
			get { return DB2Param.Value; }
			set { DB2Param.Value = value; }
		}

		public DbType DbType
		{
			get { return DB2Param.DbType; }
			set { DB2Param.DbType = value; }
		}

		public DB2Type DB2Type
		{
			get { return DB2Param.DB2Type; }
			set { DB2Param.DB2Type = value; }
		}

		public DB2Parameter DB2Param
		{
			get { return _db2Param; }
		}
	}

	[Serializable]
	public class QueryParams
	{
		public static readonly string EmptyFilter = @"(1 = 1)";
		private static readonly Dictionary<string, QueryParams> series = new Dictionary<string, QueryParams>();

		private static readonly object syncLock = new object();

		#region Backup
		private class QueryParamsBackup
		{
			internal object Owner { get; set; }
			internal Dictionary<string, QueryParams> Series { get; set; }
			internal List<QueryParam> Current { get; set; }
		}

		private static readonly Dictionary<object, QueryParamsBackup> backup = new Dictionary<object, QueryParamsBackup>();

		public static void Backup(object owner)
		{
			Backup(owner, true);
		}

		public static void Backup(object owner, bool cleanUp)
		{
			if (owner == null) throw new ArgumentOutOfRangeException("owner", "owner must not be null");

			lock (syncLock)
			{
				if (QueryParams.backup.ContainsKey(owner))
					QueryParams.backup.Remove(owner);

				QueryParams.backup.Add(owner, new QueryParamsBackup
				{
					Owner = owner,
					Current = (QueryParams.Current == null) ? null : QueryParams.Current.ToList(),
					Series = (QueryParams.series == null) ? null : QueryParams.series.ToDictionary(k => k.Key, e => e.Value)
				});
				QueryParams.Current.Clear();
				QueryParams.series.Clear();
			}
		}

		public static void Restore(object owner)
		{
			if (owner == null) throw new ArgumentOutOfRangeException("owner", "owner must not be null");

			lock (syncLock)
			{
				if (!QueryParams.backup.ContainsKey(owner))
					throw new ApplicationException("backup for specified owner does not exisit");

				var rest = QueryParams.backup[owner];
				if (HttpContext.Current != null) HttpContext.Current.Session["CurrentQueryParams"] = rest.Current.ToList();
				QueryParams.series.Clear();
				if (rest.Series != null)
					foreach (var serie in rest.Series.Select(s => s))
						QueryParams.series.Add(serie.Key, serie.Value);

				QueryParams.backup.Remove(owner);
			}
		}
		#endregion

		private readonly SeriesTypeConnector currSeriesTypeConnector;
		private readonly List<QueryParam> queryParamsSeriesList = new List<QueryParam>();
		public List<QueryParam> QueryParamsList
		{
			get { return queryParamsSeriesList; }
		}

		public QueryParams(SeriesTypeConnector seriesTypeConnector)
		{
			currSeriesTypeConnector = seriesTypeConnector;
		}

		private static readonly List<QueryParam> _current = new List<QueryParam>();	// workaround if HttpContext.Current is null
		protected static List<QueryParam> Current
		{
			get
			{
				var result = (HttpContext.Current != null)
					? (List<QueryParam>)HttpContext.Current.Session["CurrentQueryParams"]
					: _current;

				if (HttpContext.Current != null)
				{
					if (result == null) result = new List<QueryParam>();
					HttpContext.Current.Session["CurrentQueryParams"] = result;
				}

				return result;
			}
		}

		public static void OnApplyFilter()
		{
			HttpContext.Current.Session["CurrentQueryParams_Keep"] = (int?)0;
		}

		public static void Clear()
		{
			var keepParams = (int?)HttpContext.Current.Session["CurrentQueryParams_Keep"];
			if (keepParams != null)
			{
				HttpContext.Current.Session.Remove("CurrentQueryParams_Keep");
			}
			else
			{
				Current.Clear();
				series.Clear();
			}
		}

		public static int Count
		{
			get { return Current.Count; }
		}

		public static void Remove(string paramName)
		{
			if (string.IsNullOrEmpty(paramName))
			{
				return;
			}

			lock (syncLock)
			{
				var prm = Current.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));
				//var prm = Current.FirstOrDefault(param => paramName.Equals(paramName, StringComparison.OrdinalIgnoreCase));

				if (prm != default(QueryParam))
				{
					if (Current.Contains(prm))
						Current.Remove(prm);
				}
			}
		}

		public static void SetValueWithRemovement(string paramName, object paramValue, FilterCondition filterCondition, bool valueIsNull)
		{
			SetValue(paramName, paramValue, filterCondition, valueIsNull);
			var prm = Current.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));
			if (prm != default(QueryParam))
			{
				prm.RemoveBeforeFill = true;
			}
		}

		public static void SetValue(string paramName, object paramValue, FilterCondition filterCondition, bool valueIsNull)
		{
			SetValue(paramName, paramValue, filterCondition, valueIsNull, false);
		}

		public static void SetValueWithRemovement(string paramName, object paramValue, FilterCondition filterCondition, bool valueIsNull, bool otherParamWithSameName, string seriesName, SeriesTypeConnector seriesTypeConnector)
		{
			SetValue(paramName, paramValue, filterCondition, valueIsNull, otherParamWithSameName, seriesName, seriesTypeConnector);
			var prm = Current.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));
			if (prm != default(QueryParam))
			{
				prm.RemoveBeforeFill = true;
			}
		}

		public static void SetValue(string paramName, object paramValue, FilterCondition filterCondition, bool valueIsNull, bool otherParamWithSameName, string seriesName, SeriesTypeConnector seriesTypeConnector)
		{
			lock (syncLock)
			{
				if (series.ContainsKey(seriesName))
				{
					//var newSeries = new QueryParams(seriesTypeConnector);
					var param = series[seriesName].CreateParam(paramName, paramValue, filterCondition, valueIsNull, otherParamWithSameName);
					if (param != null)
					{
						//newSeries.QueryParamsList.Add(param);
						series[seriesName].QueryParamsList.Add(param);
						//series.Add(seriesName, newSeries);
					}
					return;
				}
				else
				{
					var newSeries = new QueryParams(seriesTypeConnector);
					var param = newSeries.CreateParam(paramName, paramValue, filterCondition, valueIsNull, otherParamWithSameName);
					if (param != null)
					{
						newSeries.QueryParamsList.Add(param);
						series.Add(seriesName, newSeries);
					}
					return;
				}
			}
			// unreachable code detected
			//SetValue(paramName, paramValue, filterCondition, valueIsNull, false);
		}

		public QueryParam CreateParam(string paramName, object paramValue, FilterCondition filterCondition, bool valueIsNull, bool otherParamWithSameName)
		{
			var prm = QueryParamsList.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));

			// task #1430: "_" character is used by DB2 to represent any single character in LIKE predicate. We have to use "\_" instead in the text boxes using LIKE-filtering
			if (paramValue is string && !string.IsNullOrEmpty(paramValue.ToString()) && (filterCondition == FilterCondition.Like || filterCondition == FilterCondition.LikeIgnoreCase))
				paramValue = paramValue.ToString().Replace(@"\", @"\\").Replace("_", @"\_").Replace("&", @"\&");

			if (prm == null)
			{
				if (!valueIsNull)
				{
					return new QueryParam(paramName, paramValue, filterCondition);
				}
			}
			else
			{
				if (otherParamWithSameName && !valueIsNull)
				{
					return new QueryParam(paramName, paramValue, filterCondition);
				}

				if (!valueIsNull)
				{
					prm.Value = paramValue;
					prm.FilterCondition = filterCondition;
				}
				else
				{
					QueryParamsList.Remove(prm);
				}
			}
			return null;
		}

		public static void SetValueWithRemovement(string paramName, object paramValue, FilterCondition filterCondition, bool valueIsNull, bool otherParamWithSameName)
		{
			SetValue(paramName, paramValue, filterCondition, valueIsNull, otherParamWithSameName);
			var prm = Current.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));
			if (prm != default(QueryParam))
			{
				prm.RemoveBeforeFill = true;
			}
		}

		public static void SetValue(string paramName, object paramValue, FilterCondition filterCondition, bool valueIsNull, bool otherParamWithSameName)
		{
			lock (syncLock)
			{
				var prm = Current.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));
				//var prm = Current.FirstOrDefault(param => param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));

				// task #1430: "_" character is used by DB2 to represent any single character in LIKE predicate. We have to use "\_" instead in the text boxes using LIKE-filtering
				if (paramValue is string && !string.IsNullOrEmpty(paramValue.ToString()) && (filterCondition == FilterCondition.Like || filterCondition == FilterCondition.LikeIgnoreCase))
					paramValue = paramValue.ToString().Replace(@"\", @"\\").Replace("_", @"\_").Replace("&", @"\&");

				if (prm == null)
				{
					if (!valueIsNull)
					{
						Current.Add(new QueryParam(paramName, paramValue, filterCondition));
					}
				}
				else
				{
					if (otherParamWithSameName && !valueIsNull)
					{
						Current.Add(new QueryParam(paramName, paramValue, filterCondition));
						return;
					}

					if (!valueIsNull)
					{
						prm.Value = paramValue;
						prm.FilterCondition = filterCondition;
					}
					else
					{
						Current.Remove(prm);
					}
				}
			}
		}

		public static void SetValueWithRemovement(string paramName, object paramValue, bool valueIsNull)
		{
			SetValue(paramName, paramValue, FilterCondition.Default, valueIsNull);
			var prm = Current.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));
			if (prm != default(QueryParam))
			{
				prm.RemoveBeforeFill = true;
			}
		}

		public static void SetValue(string paramName, object paramValue, bool valueIsNull)
		{
			SetValue(paramName, paramValue, FilterCondition.Default, valueIsNull);
		}

		public static T GetValue<T>(string paramName, T defaultValue)
		{
			var prm = Current.FirstOrDefault(param => param.DB2Param != null && string.Equals(param.ParameterName, paramName, StringComparison.OrdinalIgnoreCase));

			if (prm == default(QueryParam))
			{
				return defaultValue;
			}

			return (T)prm.Value;
		}

		public static string CurrentFilterString
		{
			get
			{
				var result = @"(1 = 1)";

				lock (syncLock)
				{
					result = GetResultFromSeries(result, Current, false);

					if (series.Count > 0)
					{
						result = "(" + result + ")";
						foreach (var pair in series)
						{
							result = GetResultOfSeriesOperatorConnector(result, pair.Value);
							result = result + " (";
							result = GetResultFromSeries(result, pair.Value.QueryParamsList, true);
							result = result + ")";
						}
					}
				}
				return result;
			}
		}

		private static string GetResultOfSeriesOperatorConnector(string result, QueryParams p)
		{
			switch (p.currSeriesTypeConnector)
			{
				case SeriesTypeConnector.AND:
					result += " AND";
					break;
				case SeriesTypeConnector.OR:
					result += " OR";
					break;
			}

			return result;
		}

		private static string GetResultFromSeries(string result, List<QueryParam> listOfQueryParams, bool trimFirstOperatorConnector)
		{
			var i = 0;
			//!!!!!!!!!!!!!!!!!!!!!!!!!!!! trimFirstOperatorConnector

			var res = string.Empty;

			foreach (var prm in listOfQueryParams)
			{
				if (prm.DB2Param != null)
				{

					switch (prm.DbType)
					{
						case DbType.DateTime:
							{
								res = GetResultForDBDateTimeType(prm, res);
								break;
							}
						case DbType.String:
							{
								res = GetResultForDBStringType(prm, res);
								break;
							}
						case DbType.Int32:
							{
								res = GetResultForDBIntType(prm, res);
								break;
							}
						case DbType.Decimal:
							{
								res = GetResultForDBDecimal(prm, res);
								break;
							}
					}
				}
				if (trimFirstOperatorConnector && i == 0)
				{
					res = " " + res.Trim('A', 'N', 'D', 'O', 'R', ' ');
				}
				i++;
			}
			return result + res;
		}

		private static string GetResultForDBDateTimeType(QueryParam prm, string result)
		{
			var d = (DateTime)prm.Value;
			result = result + String.Format(FilterMask.GetFilterMask(prm.FilterCondition, FilterMask.Equal),
											prm.ParameterName,
											string.Format("cast( '{0}' as timestamp )", d.ToString("u").Trim('Z')));
			return result;
		}

		private static string GetResultForDBDecimal(QueryParam prm, string result)
		{
			result = result + String.Format(
								  FilterMask.GetFilterMask(prm.FilterCondition, FilterMask.Equal),
								  prm.ParameterName, ((Decimal)prm.Value).ToString(System.Globalization.CultureInfo.InvariantCulture));
			return result;
		}

		private static string GetResultForDBIntType(QueryParam prm, string result)
		{
			result = result + String.Format(
								  FilterMask.GetFilterMask(prm.FilterCondition, FilterMask.Equal),
								  prm.ParameterName, prm.Value);
			return result;
		}

		private static string GetResultForDBStringType(QueryParam prm, string result)
		{
			if (prm.FilterCondition == FilterCondition.AND_Custom)
			{
				result = result + String.Format(
										 FilterMask.GetFilterMask(prm.FilterCondition, FilterMask.AND_Custom),
										 prm.ParameterName);
			}
			else
			{
				if (prm.FilterCondition == FilterCondition.Default || prm.FilterCondition == FilterCondition.LikeIgnoreCase
					|| prm.FilterCondition == FilterCondition.In)
				{
					result = result + String.Format(
										  FilterMask.GetFilterMask(prm.FilterCondition, FilterMask.Like),
										  prm.ParameterName, prm.Value);
				}
				else
				{
					result = result + String.Format(
										  FilterMask.GetFilterMask(prm.FilterCondition, FilterMask.Like),
										  prm.ParameterName, "'" + prm.Value + "'");
				}
			}
			return result;
		}

		/// <summary>
		/// add to filter "AND (WIZARD.CLIENT.PARTNER_ID=partnerId)"
		/// </summary>
		/// <param name="partnerId"></param>
		public static void AddPartnerIDFilter(int? partnerId)
		{
			SetValue("WIZARD.CLIENT.PARTNER_ID", partnerId, FilterCondition.Equal, false);
		}
		public static void RemovePartnerIDFilter()
		{
			Remove("WIZARD.CLIENT.PARTNER_ID");
		}

		/*
	 public static void ReloadFromQueryString()
	 {
		 foreach (string key in HttpContext.Current.Request.QueryString.Keys)
		 {
			 SetValue(key, HttpContext.Current.Request.QueryString[key], false);
		 }
	 }*/

		public static void RemoveMarkedForRemovement()
		{
			lock (syncLock)
			{
				var listOfQueryParams = new List<QueryParam>();
				listOfQueryParams.AddRange(Current);

				foreach (var prm in listOfQueryParams)
				{
					if (prm.RemoveBeforeFill)
					{
						Current.Remove(prm);
					}
				}
			}
		}
	}
}
