#region license
// Copyright (c) 2005 - 2007 Ayende Rahien (ayende@ayende.com)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Ayende Rahien nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

namespace Rhino.Commons
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using global::NHibernate;
	using global::NHibernate.Criterion;

    public class CriteriaBatch
    {
    	protected ISession session;
        private DetachedCriteria currentCriteria;
        private int currentIndex = -1;
        private readonly List<Proc<IList>> collectionDelegates = new List<Proc<IList>>();
        private readonly List<Proc<object>> uniqueResultDelegate = new List<Proc<object>>();
        private readonly List<Proc<IList, int>> collectionAndCountDelegate = new List<Proc<IList, int>>();
        readonly List<DetachedCriteria> criteriaList = new List<DetachedCriteria>();

		public static event ProcessCriteriaDelegate ProcessCriteria;

		public CriteriaBatch()
		{
		}

        public CriteriaBatch(ISession session)
        {
            this.session = session;
        }
		
        public virtual CriteriaBatch Add(DetachedCriteria criteria, Order order)
        {
            return Add(criteria.AddOrder(order));
        }

        public virtual CriteriaBatch Add(DetachedCriteria criteria)
        {
            currentIndex += 1;
            criteriaList.Add(criteria);
            currentCriteria = criteria;
            collectionDelegates.Add(null);
            uniqueResultDelegate.Add(null);
            collectionAndCountDelegate.Add(null);
            return this;
        }

        public CriteriaBatch OnRead<T>(Proc<T> read)
        {
            uniqueResultDelegate[currentIndex] = delegate(object obj)
            {
                read((T)obj);
            };
            return this;
        }

        public CriteriaBatch OnRead<T>(Proc<ICollection<T>, int> read)
        {
            collectionAndCountDelegate[currentIndex] = delegate(IList list, int count)
            {
                read(Collection.ToArray<T>(list), count);
            };
            Add(CriteriaTransformer.TransformToRowCount(currentCriteria));
            return this;
        }

        public CriteriaBatch OnRead<T>(Proc<ICollection<T>> read)
        {
            collectionDelegates[currentIndex] = delegate(IList list)
            {
                read(Collection.ToArray<T>(list));
            };
            return this;
        }

        public virtual IList Execute()
        {
            return Execute(session ?? UnitOfWork.CurrentSession);
        }

        public virtual IList Execute(ISession theSession)
        {
			// a sub class may need this at a later date.
			session = theSession;

            if (criteriaList.Count == 0) return new ArrayList();

            IMultiCriteria multiCriteria = theSession.CreateMultiCriteria();
            foreach (DetachedCriteria detachedCriteria in criteriaList)
            {
				multiCriteria.Add(CreateCriteria(theSession, detachedCriteria));
            }

            IList list = multiCriteria.List();
            int results = list.Count;

            for (int i = 0; i < results; i++)
            {
                if (collectionDelegates[i] != null)
                {
                    collectionDelegates[i]((IList)list[i]);
                }
                if (uniqueResultDelegate[i] != null)
                {
                    object single = Collection.Single((ICollection)list[i]);
                    uniqueResultDelegate[i](single);
                }
                if (collectionAndCountDelegate[i] != null)
                {
                    IList queryList = (IList)list[i];
                    int count = Convert.ToInt32(
                        Collection.Single((ICollection)list[i + 1])
                        );
                    collectionAndCountDelegate[i](queryList, count);
                    i += 1;//not a best practice, I will admit
                }
            }

            return list;
        }

        public CriteriaBatch Paging(int firstResult, int maxResults)
        {
            currentCriteria.SetFirstResult(firstResult);
            currentCriteria.SetMaxResults(maxResults);
            return this;
        }

		private ICriteria CreateCriteria(ISession theSession, DetachedCriteria detachedCriteria)
		{
			ICriteria criteria = detachedCriteria.GetExecutableCriteria(theSession);
			if (ProcessCriteria != null)
			{
				foreach (ProcessCriteriaDelegate process in ProcessCriteria.GetInvocationList())
				{
					criteria = process(criteria);
				}
			}
			return criteria;
		}
    }
}
