// 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.Generic;
using System.Linq;
using System.Text;
using WideMargin.Database;
using WideMargin.MVCInterfaces;
using WideMargin.Utilities;

namespace WideMargin.Controller
{
	/// <summary>
	/// Controller for a view capable of displaying
	/// search results, bible passages and reading plans.
	/// Also allowing search entry, passage navigation and 
	/// full navigation histroy.
	/// </summary>
	public class BibleController : IDisposable
	{
		private IBibleView _bibleView;
		private QueryQueue _queryQueue;
		private BibleBarDecoder _bibleBarDecoder;
		private BibleDatabase _bible;
		private ISearchResultsView _searchResultView;
		private IPassageView _passageView;
		private IGenericView _genericView;
		private BookList _bookList;
		private Book _currentBook;
		private Chapter _currentChapter;
		private int _currentVerse;
		private EventHandler<VerseEventArgs> _newPassagePageRequest;
		private EventHandler<RequestNewResultsPageEventArgs> _newResultPageRequest;
		private EventLocker _eventLocker = new EventLocker();
		private SearchPageResult _lastShownResult;
		private History _history;
		private BooksController _booksController;
		
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="bibleView">View for this controller</param>
		/// <param name="queryQueue">Thread safe queue for performing queries</param>
		/// <param name="bibleBarDecoder">Decodes bible bar text</param>
		/// <param name="bible">the bible database</param>
		public BibleController (IBibleView bibleView, QueryQueue queryQueue, BibleBarDecoder bibleBarDecoder, BibleDatabase bible)
		{
			_bibleView = bibleView;
			_bibleView.NavigateForwards += HandleBibleViewNavigateForwards;
			_bibleView.NavigateBackwards += HandleBibleViewNavigateBackwards;
			_bibleView.RequestAboutInfo += HandleBibleViewRequestAboutInfo;
			
			//setup search veiw
			_searchResultView = _bibleView.SearchView;
			_searchResultView.VerseLinkClicked += HandleVerseLinkClicked;
			_searchResultView.MoreResultsRequired += HandleSearchResultViewMoreResultsRequired;
			
			//setup passage view
			_passageView = _bibleView.PassageView;
			_passageView.RequestNext += HandlePassageViewRequestNext;
			_passageView.RequestPrevious += HandlePassageViewRequestPrevious;
			
			//setup generic view
			_genericView = _bibleView.GenericView;
			_genericView.VerseLinkClicked += HandleVerseLinkClicked;
			_genericView.ActionLinkClicked += HandleActionLinkClicked;
			
			//setup books view
			_booksController = new BooksController(action => ChangeView(action), _bibleView);
			_booksController.VerseLinkClicked += HandleVerseLinkClicked;
			_booksController.BookLinkClicked += HandleBookLinkClicked;
			
			_bibleView.SearchRequest += HandleBibleViewSearchRequest;
			_bibleView.RequestAutoCompleteList += HandleBibleViewRequestAutoCompleteList;
			
			_queryQueue = queryQueue;
			_bibleBarDecoder = bibleBarDecoder;
			_bible = bible;
			_bookList = BookList.Get();
			_history = new History();
		}
		
		/// <summary>
		/// Handles the book link clicked.
		/// </summary>
		/// <param name='sender'>Sender.</param>
		/// <param name='e'>
		/// contains details of the bookthat was clicked and the button which was used.
		/// </param>
		private void HandleBookLinkClicked (object sender, LinkClickedEventArgs<string> e)
		{
			NewBooksPageRequest.Fire(this, e);
		}
		
		/// <summary>
		/// An action link has been clicked.
		/// Performs the clicked action.
		/// </summary>
		private void HandleActionLinkClicked (object sender, LinkClickedEventArgs<string> e)
		{
			if(e.Button == 1)
			{
				ShowBooksList();
				return;
			}
			
			if(e.Button == 2)
			{
				NewBooksPageRequest.Fire(this, e);
				return;
			}
		}
		
		/// <summary>
		/// Bible view is requesting about info.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void HandleBibleViewRequestAboutInfo (object sender, EventArgs e)
		{
			_bibleView.ShowAboutInfo(AboutInfo.Get().GetAboutInfo());
		}
		
		/// <summary>
		/// Shows the readings for today.
		/// </summary>
		public void ShowDaylyReading()
		{
			var readings = _bible.GetDaylyReading(DateTime.Now).ToArray();
			
			ChangeView(() =>
			{
				string newLine = Environment.NewLine;
				StringBuilder builder = new StringBuilder();
				
				builder.Append("<Text>");
				
				builder.AppendLine("<ActionLink target='Browse'>Browse</ActionLink>");
				
				
				builder.Append("<Center>");
				
				
				builder.Append("<Size value='24'>");
				builder.AppendLine("Daily readings");
				builder.Append("</Size>");
				
				builder.AppendLine(string.Empty);
				
				if(readings.IsEmpty())
				{
					//no readings for today
					builder.Append("<Size value='20'>");
					builder.AppendLine("I couldn't find any readings for today, looks like you get the day off.");
					builder.Append("</Size>");
				}
				else
				{
					builder.Append("<Size value='14'>");
					builder.AppendLine("First Portion");
					builder.AppendFormat(" <VerseLink target='{0}'>{1}{2}</VerseLink> {2}",
					                     readings[0].FirstVerse,
					                     readings[0].Chapters,
					                     newLine);
					builder.AppendLine("Second Portion");
					builder.AppendFormat(" <VerseLink target='{0}'>{1}{2}</VerseLink> {2}",
					                     readings[1].FirstVerse,
					                     readings[1].Chapters,
					                     newLine);
					builder.AppendLine("Third Portion");
					builder.AppendFormat(" <VerseLink target='{0}'>{1}{2}</VerseLink> {2}",
					                     readings[2].FirstVerse,
					                     readings[2].Chapters,
					                     newLine);
					builder.Append("</Size>");
				}
				
				builder.AppendLine("</Center>");
				builder.AppendLine("</Text>");
				
				_genericView.Text = builder.ToString();
				
				_bibleView.ChangeTitle("Todays Readings");
				
				_bibleView.ShowGenericView();
			});
		}

		/// <summary>
		/// Occurs when the view requests to navigate backwards
		/// </summary>
		private void HandleBibleViewNavigateBackwards (object sender, EventArgs e)
		{
			_history.Back();
		}

		/// <summary>
		/// Occurs when the view requests to navigate forwards.
		/// </summary>
		private void HandleBibleViewNavigateForwards (object sender, EventArgs e)
		{
			_history.Forward();
		}

		/// <summary>
		/// View is requesting an auto completion list of book names.
		/// </summary>
		private void HandleBibleViewRequestAutoCompleteList (object sender, EventArgs e)
		{
			_bibleView.ShowAutoCompleteList(_bookList.AllBookNames());
		}

		/// <summary>
		/// Search Results view is requesting a new search page
		/// </summary>
		private void HandleSearchResultViewMoreResultsRequired (object sender, EventArgs e)
		{
			string searchText = _lastShownResult.SearchText;
			int pageSize = _lastShownResult.PageSize;
			var resultsCount = _lastShownResult.Matches;
			
			Func<SearchPageResult> searchAction = null;
			if(_lastShownResult is SearchPageResultFiltered)
			{	
				string book = ((SearchPageResultFiltered)_lastShownResult).Book;
				searchAction = () => _bible.Search(searchText, book, resultsCount, pageSize, _lastShownResult.PageNumber + 1);
			}
			else
			{	
				searchAction = () => _bible.Search(searchText, resultsCount, pageSize, _lastShownResult.PageNumber + 1);
			}
			_queryQueue.Queue(searchAction, 
			                  verses => ShowSearchResults(verses, true, false));
			return;
		}
		
		/// <summary>
		/// Verse link has been clicked
		/// </summary>
		private void HandleVerseLinkClicked (object sender, LinkClickedEventArgs<IVerseIdentifier> e)
		{
			if(e.Button == 1)
			{
				//left click open in current
				Book book = _bookList.GetBook(e.LinkTarget.Book);
				Chapter chapter = book.GetChapter(e.LinkTarget.Chapter);
				NavigatePassage(book, chapter, e.LinkTarget.VerseNumber);
				return;
			}
			
			if(e.Button == 2)
			{
				//middle click open in new
				_newPassagePageRequest.Fire(this, new VerseEventArgs(e.LinkTarget));
				return;
			}
		}
		
		/// <summary>
		/// Shows the books list.
		/// </summary>
		/// <param name='book'>
		/// Show with this book's chapters shown.
		/// </param>
		public void ShowBooksList(string book)
		{
			_booksController.ShowChapters(book);
		}
		
		/// <summary>
		/// Shows the books list with all books shown
		/// </summary>
		public void ShowBooksList()
		{
			_booksController.ShowBookList(_bookList.AllBookNames());
		}
		
		/// <summary>
		/// Occurs to request a new passage page
		/// </summary>
		public event EventHandler<VerseEventArgs> NewPassagePageRequest
		{
			add
			{
				_eventLocker.Add(ref _newPassagePageRequest, value);
			}
			remove
			{
				_eventLocker.Remove(ref _newPassagePageRequest, value);	
			}
		}
		
		/// <summary>
		/// Occurs to requesta new search results page
		/// </summary>
		public event EventHandler<RequestNewResultsPageEventArgs> NewResultPageRequest
		{
			add
			{
				_eventLocker.Add(ref _newResultPageRequest, value);	
			}
			remove
			{
				_eventLocker.Remove(ref _newResultPageRequest, value);
			}
		}
		
		/// <summary>
		/// fired to request a new books page.
		/// </summary>
		public event EventHandler<LinkClickedEventArgs<string>> NewBooksPageRequest;

		/// <summary>
		/// Handles request to see the previous passage.
		/// </summary>
		private void HandlePassageViewRequestPrevious (object sender, EventArgs e)
		{
			Chapter prevChapter = _currentBook.GetPreviousChapter(_currentChapter);
			Book prevBook = _currentBook;
			if(prevChapter == null)
			{
				prevBook = _bookList.PreviousBook(_currentBook);
				if(prevBook == null)
				{
					return;	
				}
				prevChapter = prevBook.LastChapter;
			}

			
			NavigatePassage(prevBook, prevChapter);
		}

		/// <summary>
		/// Handles request to see the next passage.
		/// </summary>
		private void HandlePassageViewRequestNext (object sender, EventArgs e)
		{
			Chapter nextChapter = _currentBook.GetNextChapter(_currentChapter);
			Book nextBook = _currentBook;
			if(nextChapter == null)
			{
				nextBook = _bookList.NextBook(_currentBook);
				if(nextBook == null)
				{
					return;	
				}
				nextChapter = nextBook.GetChapter(1);
			}

			NavigatePassage(nextBook, nextChapter);
		}
		
		/// <summary>
		/// Shows the passage containing the specified verse
		/// </summary>
		/// <param name="verse">verse contained in the passage to be shown.</param>
		public void ShowPassage(IVerseIdentifier verse)
		{
			Book book = _bookList.GetBook(verse.Book);
			NavigatePassage(book, book.GetChapter(verse.Chapter), verse.VerseNumber);
		}
		
		/// <summary>
		/// Navigates to a passage
		/// </summary>
		/// <param name="book">book to navigate to.</param>
		/// <param name="chapter">chapter of book to navigate to.</param>
		private void NavigatePassage(Book book, Chapter chapter)
		{		
			NavigatePassage(book, chapter, 1);
		}
		
		/// <summary>
		/// Navigates to a passage.
		/// </summary>
		/// <param name='book'>Book.</param>
		/// <param name='chapter'>Chapter.</param>
		/// <param name='verse'>Verse.</param>
		private void NavigatePassage(Book book, Chapter chapter, int verse)
		{
			string text = string.Format("{0} {1}",book.Name, chapter.Number);
			BibleBarResult result = _bibleBarDecoder.Decode(text);
			_queryQueue.Queue(() => result.GetVerses(_bible), passage => ChangePassage(passage, verse));
		}
		
		/// <summary>
		/// Bible view is requesting a search be done.
		/// </summary>
		/// <param name="e">Contains search text</param>
		private void HandleBibleViewSearchRequest (object sender, StringEventArgs e)
		{
			DoQuery(e.Text, false);
		}
		
		private void DoQuery(string text, bool keepFilters)
		{
			if(text.Contains ("\"") && text.Count (charactor => charactor == '\"') % 2 != 0)
			{
				text = text + "\"";	
			}
			
			BibleBarResult result = _bibleBarDecoder.Decode(text);

			if(result is SearchResult)
			{

				//Was a search query
				if(text.Length < 3)
				{
					return;	
				}
				SearchResult searchResult = (SearchResult)result;
				_queryQueue.Queue(() => searchResult.GetFirstPage(_bible), 
				                  verses => ShowSearchResults(verses, false, keepFilters));
				return;
			}
			
			if(result is PassageResult)
			{
				PassageResult pasageResult = (PassageResult)result;
				//was a passage query
				_queryQueue.Queue(() => result.GetVerses(_bible), 
				                  passage => ChangePassage(passage, pasageResult.Verse));
			}
		}
		
		/// <summary>
		/// Changes to the specified passage
		/// </summary>
		/// <param name="passage">verses making up the passage</param>
		private void ChangePassage(IEnumerable<IVerse> passage, int verse)
		{
			if(passage.IsEmpty())
			{
				return;
			}
			
			IVerse firstVerse = passage.First();
			Book newBook = BookList.Get().GetBook(firstVerse.Book);
			Chapter newChapter = newBook.GetChapter(firstVerse.Chapter);
			
			if(_currentBook == newBook && 
			   _currentChapter == newChapter &&
			   _currentVerse == verse)
			{
				ChangeView(() =>
				{
					_bibleView.ShowPassageView();
				});
				return;	
			}
			ChangeView(() =>
			{
				_currentBook = newBook;
				_currentChapter = newChapter;
				_currentVerse = verse;

				_bibleView.RunOnUiThread(() =>
				{
					_bibleView.ChangeTitle(string.Format("{0} {1}", newBook.Name, newChapter.Number));

					_bibleView.ShowPassageView();
					_passageView.ShowVerses(passage);
				});

				
				
				if(verse != 1)
				{
					IVerse verseToScrollTo = passage.Where(passageVerse => passageVerse.VerseNumber == verse)
				    					        .FirstOrDefault();
					if(verseToScrollTo == null)
					{
						verseToScrollTo = passage.Last();
					}
					_passageView.ScrollToVerse(verseToScrollTo);
				}
			});
		}
		
		/// <summary>
		/// Change the shown search result.
		/// </summary>
		/// <param name="searchResults">results of search</param>
		/// <param name="keepFilters">true to keep any displayed filters</param>
		private void ShowSearchResults(SearchPageResult searchResults, bool append, bool keepFilters)
		{
			if(searchResults.Verses.IsEmpty())
			{
				return;	
			}
			
			if(append)
			{
				AppendResultsToSearchView(searchResults);
				return;
			}
			
			IEnumerable<Filter> filters = null;
			if(keepFilters)
			{
				filters = _searchResultView.Filters;
			}
			else
			{
				var matches = searchResults.Matches;
				var bookList = BookList.Get();
				filters =
					from book in matches.Keys
					orderby bookList.GetBook(book).Index
					let queryText = string.Format ("{0} in:{1}", searchResults.SearchText, book)
					select new Filter(string.Format ("{0}({1})", book, matches[book]), () => DoQuery(queryText, true));
				var filterAll = new Filter(string.Format ("All({0})", matches.Values.Sum ()), () => DoQuery(searchResults.SearchText, true));
				filters = new Filter[]{filterAll}.Concat(filters);
			}
						
			//only a new search needs history, append does not because its trigered by
			//scrolling
			ChangeView(() =>
			{
				_lastShownResult = searchResults;
				_currentBook = null;
				_currentChapter = null;
				_searchResultView.SetResults(searchResults.Verses, filters);
				_bibleView.ChangeTitle("Search");
				_bibleView.ShowSearchView();
			});
		}
		
		/// <summary>
		/// Appends the results to search view without recording in history.
		/// </summary>
		private void AppendResultsToSearchView(SearchPageResult searchResults)
		{
			_lastShownResult = searchResults;
			_currentBook = null;
			_currentChapter = null;
			_searchResultView.AppendResults(searchResults.Verses);
			_bibleView.ShowSearchView();
			return;
		}
		
		/// <summary>
		/// Changes the currently shown view by
		/// running the passed in action.
		/// Also records the action in the history, to
		/// enable history navigtion.
		/// </summary>
		/// <param name="show">Action which changes the view</param>
		public void ChangeView(Action show)
		{
			_history.Perform(show);
		}
		
		/// <summary>
		/// Clean up unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			//nothing to do yet
		}
	}
}

