﻿using System;
using System.Linq;
using System.Collections.Generic;
using OKHOSTING.ORM.Operations;

namespace OKHOSTING.ORM.Model
{
	/// <summary>
	/// Allows to access multiple-primary-key ORM objects in a Dictionary way
	/// <para xml:lang="es">
	/// Permite acceder a los objetos de clave primaria de varios ORM de una manera diccionario
	/// </para>
	/// </summary>
	/// <typeparam name="TType">
	/// Type of object that will be stored.
	/// <para xml:lang="es">Tipo de objeto que se va a almacenar</para>
	/// </typeparam>
	public class MultipleKeyDictionary<TType>: Dictionary<object[], TType>
	{
		/// <Docs>
		/// The item to add to the current collection.
		/// <para xml:lang="es">El elemento a agregar a la coleccion actual.</para>
		/// </Docs>
		/// <para>Adds an item to the current collection.</para>
		/// <para xml:lang="es">Agrega un elemento a la coleccion actual.</para>
		/// <remarks>To be added.
		/// <para xml:lang="es">Para ser agregado.</para>
		/// </remarks>
		/// <exception cref="System.NotSupportedException">
		/// The current collection is read-only.
		/// <para xml:lang="es">La coleccion actual es de solo lectura.</para>
		/// </exception>
		/// <summary>
		/// Add the specified item.
		/// <para xml:lang="es">
		/// Agrega el elemento especificado.
		/// </para>
		/// </summary>
		/// <param name="item">Item.
		/// <para xml:lang="es">El elemento a agregar.</para>
		/// </param>
		public override void Add(TType item)
		{
			Add(DataType.PrimaryKey.Select(dm => dm.Expression.GetValue(item)).ToArray(), item);
		}

		/// <Docs>
		/// The object to locate in the current collection.
		/// <para xml:lang="es">El objeto a localizar en la coleccion actual.</para>
		/// </Docs>
		/// <para>Determines whether the current collection contains a specific value.</para>
		/// <para xml:lang="es">Determina si la coleccion actual contiene un valor especifico.</para>
		/// <summary>
		/// Contains the specified item.
		/// <para xml:lang="es">
		/// Contiene el elemento especificado.
		/// </para>
		/// </summary>
		public override bool Contains(TType item)
		{
			return ContainsKey(DataType.PrimaryKey.Select(dm => dm.Expression.GetValue(item)).ToArray());
		}

		/// <Docs>The item to remove from the current collection.
		/// <para xml:lang="es">El elemento para eliminar de la coleccion actual.</para>
		/// </Docs>
		/// <para>Removes the first occurrence of an item from the current collection.</para>
		/// <para xml:lang="es">Quita la primer aparicion de un elemento de la coleccion actual.</para>
		/// <summary>
		/// Remove the specified item.
		/// <para xml:lang="es">
		/// Remueve el elemento especificado.
		/// </para>
		/// </summary>
		/// <param name="item">Item.
		/// <para xml:lang="es">El elemento a remover.</para>
		/// </param>
		public override bool Remove(TType item)
		{
			return Remove(DataType.PrimaryKey.Select(dm => dm.Expression.GetValue(item)).ToArray());
		}

		//Other overrides
		/// <summary>
		/// Gets a collection of keys object.
		/// <para xml:lang="es">
		/// Obtiene una coleccion de las llaves del objero.
		/// </para>
		/// </summary>
		/// <value>The keys.
		/// <para xml:lang="es">las llaves.</para>
		/// </value>
		public override ICollection<object[]> Keys
		{
			get 
			{
				Select<TType> select = new Select<TType>();
				select.DataType = DataType;
				
				foreach (DataMember pk in DataType.PrimaryKey)
				{
					select.Members.Add(pk);
				}

				var primaryKeys = DataType.PrimaryKey.ToList();
				var keys = new List<object[]>();

				foreach (TType instance in DataBase.Select(select))
				{
					object[] k = new object[primaryKeys.Count];
					
					for (int i = 0; i < primaryKeys.Count; i++)
					{
						k[i] = Convert.ChangeType(primaryKeys[i].Expression.GetValue(instance), primaryKeys[i].Expression.ReturnType);
					}
					
					keys.Add(k);
				}

				return keys;
			}
		}

		/// <summary>
		/// Gets an enumeration of the primary key and the object itself, of each item.
		/// <para xml:lang="es">
		/// Obtiene una enumeracion de la llave primaria y el objeto en si, de cada elemento. 
		/// </para>
		/// </summary>
		/// <returns>The enumerator.
		/// <para xml:lang="es">La enumeracion.</para>
		/// </returns>
		public override IEnumerator<KeyValuePair<object[], TType>> GetEnumerator()
		{
			List<DataMember> primaryKeys = DataType.PrimaryKey.ToList();

			foreach (TType instance in DataBase.Select(Select))
			{
				object[] key = ORM.DataType.GetPrimaryKeyValues(instance).ToArray();
				yield return new KeyValuePair<object[], TType>(key, instance);
			}
		}

		/// <summary>
		/// Gets the primary key filter.
		/// <para xml:lang="es">
		/// Obtiene el filtro de la llave primaria.
		/// </para>
		/// </summary>
		/// <returns>
		/// The primary key filter.
		/// <para xml:lang="es">El filtro de llave primaria</para>
		/// </returns>
		protected override Filters.Filter GetPrimaryKeyFilter(DataType dtype, object[] key)
		{
			Filters.AndFilter filter = new Filters.AndFilter();
			var primaryKeys = dtype.PrimaryKey.ToList();

			for (int i = 0; i < primaryKeys.Count; i++)
			{
				filter.InnerFilters.Add(new Filters.ValueCompareFilter()
				{
					Member = primaryKeys[i],
					ValueToCompare = (IComparable) key[i],
					Operator = Data.CompareOperator.Equal,
				});
			}

			return filter;
		}
	}
}