// WideMargin. Simple fast bible software.
// Copyright (C) 2011  Daniel Hughes
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using WideMargin.MVCInterfaces;

namespace WideMargin.Database
{
	public class QueryDefinition
	{
		public QueryDefinition(Func<object> queryToRun, Action<object> callback)
		{
			QueryToRun = queryToRun;
			Callback = callback;
		}
		
		public Func<object> QueryToRun
		{
			get;
			private set;
		}
		
		public Action<object> Callback
		{
			get;
			private set;
		}
	}
	
	public class QueryQueue
	{
		private object _queueLock = new object();
		private Queue<QueryDefinition> _queue;
		private ManualResetEvent _waitForEnqueue;
		private Thread _worker;
		
		public QueryQueue ()
		{
			_queue = new Queue<QueryDefinition>();
			_waitForEnqueue = new ManualResetEvent(false);
			_worker = new Thread(_ => Worker());
			_worker.Start();
		}
		
		public void Queue<Result>(Func<Result> queryToRun, Action<Result> callback)
		{
			lock(_queueLock)
			{
				_queue.Enqueue(new QueryDefinition(() => queryToRun(), result => callback((Result)result)));
				//add to queue
			    //set wait handle so worker can continue.
				_waitForEnqueue.Set();
			}
		}
		
		private void Worker()
		{
			int count = 0;
			while(!Disposing)
			{
				
				lock(_queueLock)
				{
					count = _queue.Count;
					if(count == 0)
					{
						_waitForEnqueue.Reset();
					}
				}
				//is anything waiting on queue
				if(count > 0)
				{
					Process();
				}
				_waitForEnqueue.WaitOne();
			}
		}

		private void Process()
		{
			QueryDefinition queryDefinition = null;
			lock(_queueLock)
			{
				//keep dequeuing until nothing in queue
				//keep only the last dequeued item older queries are stale
				while(_queue.Count > 0)
				{
					queryDefinition = _queue.Dequeue();
				}
			}
			//process query
			var result = queryDefinition.QueryToRun();
			
			//call callback
			queryDefinition.Callback(result);
		}
		
		private bool Disposing
		{
			get;
			set;
		}
		
		public void Dispose(bool disposing)
		{
			if(disposing)
			{
				Disposing = true;
				_waitForEnqueue.Set();
				_worker.Join();
			}
		}
		
		public void Dispose()
		{
			GC.SuppressFinalize(this);
			Dispose(true);
		}
	}
}

