using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Engine;
using NHibernate.Type;

namespace NHibernate.Cache
{
	/// <summary>
	/// Defines the contract for caches capable of storing query results.  These
	/// caches should only concern themselves with storing the matching result ids
	/// of entities.
	/// The transactional semantics are necessarily less strict than the semantics
	/// of an item cache.
	/// <see cref="IBatchableQueryCache" /> should also be implemented for
	/// compatibility with future versions.
	/// </summary>
	public partial interface IQueryCache
	{
		/// <summary>
		/// The underlying <see cref="CacheBase"/>.
		/// </summary>
		// 6.0 TODO: type as CacheBase instead
#pragma warning disable 618
		ICache Cache { get; }
#pragma warning restore 618

		/// <summary>
		/// The cache region.
		/// </summary>
		string RegionName { get; }

		/// <summary>
		/// Clear the cache.
		/// </summary>
		void Clear();

		// Since 5.2
		[Obsolete("Have the query cache implement IBatchableQueryCache, and use IBatchableQueryCache.Put")]
		bool Put(QueryKey key, ICacheAssembler[] returnTypes, IList result, bool isNaturalKeyLookup, ISessionImplementor session);

		// Since 5.2
		[Obsolete("Have the query cache implement IBatchableQueryCache, and use IBatchableQueryCache.Get")]
		IList Get(QueryKey key, ICacheAssembler[] returnTypes, bool isNaturalKeyLookup, ISet<string> spaces, ISessionImplementor session);

		/// <summary>
		/// Clean up resources.
		/// </summary>
		/// <remarks>
		/// This method should not destroy <see cref="Cache" />. The session factory is responsible for it.
		/// </remarks>
		void Destroy();
	}

	// 6.0 TODO: merge into IQueryCache
	/// <summary>
	/// Transitional interface for <see cref="IQueryCache" />.
	/// </summary>
	public partial interface IBatchableQueryCache : IQueryCache
	{
		/// <summary>
		/// Get query results from the cache.
		/// </summary>
		/// <param name="key">The query key.</param>
		/// <param name="queryParameters">The query parameters.</param>
		/// <param name="returnTypes">The query result row types.</param>
		/// <param name="spaces">The query spaces.</param>
		/// <param name="session">The session for which the query is executed.</param>
		/// <returns>The query results, if cached.</returns>
		IList Get(
			QueryKey key, QueryParameters queryParameters, ICacheAssembler[] returnTypes, ISet<string> spaces,
			ISessionImplementor session);

		/// <summary>
		/// Put query results in the cache.
		/// </summary>
		/// <param name="key">The query key.</param>
		/// <param name="queryParameters">The query parameters.</param>
		/// <param name="returnTypes">The query result row types.</param>
		/// <param name="result">The query result.</param>
		/// <param name="session">The session for which the query was executed.</param>
		/// <returns><see langword="true" /> if the result has been cached, <see langword="false" />
		/// otherwise.</returns>
		bool Put(
			QueryKey key, QueryParameters queryParameters, ICacheAssembler[] returnTypes, IList result,
			ISessionImplementor session);

		/// <summary>
		/// Retrieve multiple query results from the cache.
		/// </summary>
		/// <param name="keys">The query keys.</param>
		/// <param name="queryParameters">The array of query parameters matching <paramref name="keys"/>.</param>
		/// <param name="returnTypes">The array of query result row types matching <paramref name="keys"/>.</param>
		/// <param name="spaces">The array of query spaces matching <paramref name="keys"/>.</param>
		/// <param name="session">The session for which the queries are executed.</param>
		/// <returns>The cached query results, matching each key of <paramref name="keys"/> respectively. For each
		/// missed key, it will contain a <see langword="null" />.</returns>
		IList[] GetMany(
			QueryKey[] keys, QueryParameters[] queryParameters, ICacheAssembler[][] returnTypes,
			ISet<string>[] spaces, ISessionImplementor session);

		/// <summary>
		/// Attempt to cache objects, after loading them from the database.
		/// </summary>
		/// <param name="keys">The query keys.</param>
		/// <param name="queryParameters">The array of query parameters matching <paramref name="keys"/>.</param>
		/// <param name="returnTypes">The array of query result row types matching <paramref name="keys"/>.</param>
		/// <param name="results">The array of query results matching <paramref name="keys"/>.</param>
		/// <param name="session">The session for which the queries were executed.</param>
		/// <returns>An array of boolean indicating if each query was successfully cached.</returns>
		/// <exception cref="CacheException"></exception>
		bool[] PutMany(
			QueryKey[] keys, QueryParameters[] queryParameters, ICacheAssembler[][] returnTypes, IList[] results,
			ISessionImplementor session);
	}

	// 6.0 TODO: drop
	internal static partial class QueryCacheExtensions
	{
		private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(QueryCacheExtensions));

		// Non thread safe: not an issue, at worst it will cause a few more logs than one.
		// Does not handle the possibility of using multiple diffreent obsoleted query cache implementation:
		// only the first encountered will be logged.
		private static bool _hasWarnForObsoleteQueryCache;

		/// <summary>
		/// Get query results from the cache.
		/// </summary>
		/// <param name="queryCache">The cache.</param>
		/// <param name="key">The query key.</param>
		/// <param name="queryParameters">The query parameters.</param>
		/// <param name="returnTypes">The query result row types.</param>
		/// <param name="spaces">The query spaces.</param>
		/// <param name="session">The session for which the query is executed.</param>
		/// <returns>The query results, if cached.</returns>
		public static IList Get(
			this IQueryCache queryCache,
			QueryKey key,
			QueryParameters queryParameters,
			ICacheAssembler[] returnTypes,
			ISet<string> spaces,
			ISessionImplementor session)
		{
			if (queryCache is IBatchableQueryCache batchableQueryCache)
			{
				return batchableQueryCache.Get(
					key,
					queryParameters,
					returnTypes,
					spaces,
					session);
			}

			if (!_hasWarnForObsoleteQueryCache)
			{
				_hasWarnForObsoleteQueryCache = true;
				Log.Warn("{0} is obsolete, it should implement {1}", queryCache, nameof(IBatchableQueryCache));
			}

			var persistenceContext = session.PersistenceContext;

			var defaultReadOnlyOrig = persistenceContext.DefaultReadOnly;

			if (queryParameters.IsReadOnlyInitialized)
				persistenceContext.DefaultReadOnly = queryParameters.ReadOnly;
			else
				queryParameters.ReadOnly = persistenceContext.DefaultReadOnly;

			try
			{
#pragma warning disable 618
				return queryCache.Get(
#pragma warning restore 618
					key,
					returnTypes,
					queryParameters.NaturalKeyLookup,
					spaces,
					session);
			}
			finally
			{
				persistenceContext.DefaultReadOnly = defaultReadOnlyOrig;
			}
		}

		/// <summary>
		/// Put query results in the cache.
		/// </summary>
		/// <param name="queryCache">The cache.</param>
		/// <param name="key">The query key.</param>
		/// <param name="queryParameters">The query parameters.</param>
		/// <param name="returnTypes">The query result row types.</param>
		/// <param name="result">The query result.</param>
		/// <param name="session">The session for which the query was executed.</param>
		/// <returns><see langword="true" /> if the result has been cached, <see langword="false" />
		/// otherwise.</returns>
		public static bool Put(
			this IQueryCache queryCache,
			QueryKey key,
			QueryParameters queryParameters,
			ICacheAssembler[] returnTypes,
			IList result,
			ISessionImplementor session)
		{
			if (queryCache is IBatchableQueryCache batchableQueryCache)
			{
				return batchableQueryCache.Put(
					key, queryParameters,
					returnTypes,
					result, session);
			}

#pragma warning disable 618
			return queryCache.Put(
#pragma warning restore 618
				key,
				returnTypes,
				result,
				queryParameters.NaturalKeyLookup,
				session);
		}

		/// <summary>
		/// Retrieve multiple query results from the cache.
		/// </summary>
		/// <param name="queryCache">The cache.</param>
		/// <param name="keys">The query keys.</param>
		/// <param name="queryParameters">The array of query parameters matching <paramref name="keys"/>.</param>
		/// <param name="returnTypes">The array of query result row types matching <paramref name="keys"/>.</param>
		/// <param name="spaces">The array of query spaces matching <paramref name="keys"/>.</param>
		/// <param name="session">The session for which the queries are executed.</param>
		/// <returns>The cached query results, matching each key of <paramref name="keys"/> respectively. For each
		/// missed key, it will contain a <see langword="null" />.</returns>
		public static IList[] GetMany(
			this IQueryCache queryCache,
			QueryKey[] keys,
			QueryParameters[] queryParameters,
			ICacheAssembler[][] returnTypes,
			ISet<string>[] spaces,
			ISessionImplementor session)
		{
			if (queryCache is IBatchableQueryCache batchableQueryCache)
			{
				return batchableQueryCache.GetMany(
					keys,
					queryParameters,
					returnTypes,
					spaces,
					session);
			}

			var results = new IList[keys.Length];
			for (var i = 0; i < keys.Length; i++)
			{
				results[i] = queryCache.Get(keys[i], queryParameters[i], returnTypes[i], spaces[i], session);
			}

			return results;
		}

		/// <summary>
		/// Attempt to cache objects, after loading them from the database.
		/// </summary>
		/// <param name="queryCache">The cache.</param>
		/// <param name="keys">The query keys.</param>
		/// <param name="queryParameters">The array of query parameters matching <paramref name="keys"/>.</param>
		/// <param name="returnTypes">The array of query result row types matching <paramref name="keys"/>.</param>
		/// <param name="results">The array of query results matching <paramref name="keys"/>.</param>
		/// <param name="session">The session for which the queries were executed.</param>
		/// <returns>An array of boolean indicating if each query was successfully cached.</returns>
		/// <exception cref="CacheException"></exception>
		public static bool[] PutMany(
			this IQueryCache queryCache,
			QueryKey[] keys,
			QueryParameters[] queryParameters,
			ICacheAssembler[][] returnTypes,
			IList[] results,
			ISessionImplementor session)
		{
			if (queryCache is IBatchableQueryCache batchableQueryCache)
			{
				return batchableQueryCache.PutMany(
					keys,
					queryParameters,
					returnTypes,
					results,
					session);
			}

			var puts = new bool[keys.Length];
			for (var i = 0; i < keys.Length; i++)
			{
				puts[i] = queryCache.Put(keys[i], queryParameters[i], returnTypes[i], results[i], session);
			}

			return puts;
		}
	}
}