using System;
using System.Diagnostics;
using System.Collections;
using HtmlHelp.ChmDecoding;

namespace HtmlHelp
{
	/// <summary>
	/// Enumeration for specifying the index type
	/// </summary>
	public enum IndexType
	{
		/// <summary>
		/// Keyword links should be used
		/// </summary>
		KeywordLinks = 0,
		/// <summary>
		/// Associative links should be used
		/// </summary>
		AssiciativeLinks = 1
	}

	/// <summary>
	/// The class <c>Index</c> holds the (keyword links) KLinks and (associative links) ALinks of the htmlhelp 
	/// system. It implements methods for easy index-based searching.
	/// </summary>
	public class Index
	{
		private ArrayList _kLinks = new ArrayList();
		private ArrayList _aLinks = new ArrayList();

		/// <summary>
		/// Standard constructor
		/// </summary>
		public Index()
		{
		}

		/// <summary>
		/// Constructor of the class
		/// </summary>
		/// <param name="kLinks">arraylist with keyword links</param>
		/// <param name="aLinks">arraylist with associative links</param>
		public Index(ArrayList kLinks, ArrayList aLinks)
		{
			_kLinks= kLinks;
			_aLinks = aLinks;
		}

		/// <summary>
		/// Clears the current toc
		/// </summary>
		public void Clear()
		{
			if(_aLinks != null)
				_aLinks.Clear();
			if(_kLinks != null)
				_kLinks.Clear();
		}

		/// <summary>
		/// Gets the number of index items for a specific type
		/// </summary>
		/// <param name="typeOfIndex">type of index</param>
		/// <returns>Returns the number of index items for a specific type</returns>
		public int Count(IndexType typeOfIndex)
		{
			ArrayList _index = null;

			switch( typeOfIndex )
			{
				case IndexType.AssiciativeLinks: _index = _aLinks; break;
				case IndexType.KeywordLinks: _index = _kLinks; break;
			}

			if(_index != null)
				return _index.Count;

			return 0;
		}

		/// <summary>
		/// Gets the internal index list of keyword links
		/// </summary>
		public ArrayList KLinks
		{
			get 
			{
				if(_kLinks==null)
					_kLinks = new ArrayList();

				return _kLinks;
			}
		}

		/// <summary>
		/// Gets the internal index list of associative links
		/// </summary>
		public ArrayList ALinks
		{
			get 
			{
				if(_aLinks==null)
					_aLinks = new ArrayList();

				return _aLinks;
			}
		}

		/// <summary>
		/// Merges the the index list <c>arrIndex</c> into the current one
		/// </summary>
		/// <param name="arrIndex">indexlist which should be merged with the current one</param>
		/// <param name="typeOfIndex">type of index to merge</param>
		public void MergeIndex( ArrayList arrIndex, IndexType typeOfIndex )
		{
			ArrayList _index = null;

			switch(typeOfIndex)
			{
				case IndexType.AssiciativeLinks: _index = _aLinks;break;
				case IndexType.KeywordLinks: _index = _kLinks;break;
			}
			
			foreach(IndexItem curItem in arrIndex)
			{
				//IndexItem searchItem = ContainsIndex(_index, curItem.KeyWordPath);
				int insertIndex=0;
				IndexItem searchItem = BinSearch(0, _index.Count-1, _index, curItem.KeyWordPath, false, false, ref insertIndex);

				if(searchItem != null)
				{
					// extend the keywords topics
					foreach(IndexTopic curEntry in curItem.Topics)
					{
						searchItem.Topics.Add( curEntry );
					}
				} 
				else 
				{
					// add the item to the global collection
					//_index.Add( curItem );

					if(insertIndex > _index.Count)
						_index.Add(curItem);
					else
					_index.Insert(insertIndex, curItem);
				}
			}			
		}

		/// <summary>
		/// Searches an index entry using recursive binary search algo (divide and conquer).
		/// </summary>
		/// <param name="nStart">start index for searching</param>
		/// <param name="nEnd">end index for searching</param>
		/// <param name="arrIndex">arraylist containing sorted IndexItem entries</param>
		/// <param name="keywordPath">keyword path to search</param>
		/// <param name="searchKeyword">true if the keywordPath will only contain the keyword not the complete path</param>
		/// <param name="caseInsensitive">True if case should be ignored</param>
		/// <param name="insertIndex">out reference. will receive the index where the item with the
		/// keywordPath should be inserted if not found (receives -1 if the item was found)</param>
		/// <returns>Returns an IndexItem instance if found, otherwise null 
		/// (use insertIndex for inserting the new item in a sorted order)</returns>
		private IndexItem BinSearch(int nStart, int nEnd, ArrayList arrIndex, string keywordPath, 
			bool searchKeyword, bool caseInsensitive, ref int insertIndex)
		{
			if( arrIndex.Count <= 0 )
			{
				insertIndex=0;
				return null;
			}

			if(caseInsensitive)
				keywordPath = keywordPath.ToLower();

			if( (nEnd - nStart) > 1)
			{
				int nCheck = nStart + (nEnd-nStart)/2;

				IndexItem iC = arrIndex[nCheck] as IndexItem;

				string sCompare = iC.KeyWordPath;

				if(searchKeyword)
					sCompare = iC.KeyWord;

				if(caseInsensitive)
					sCompare = sCompare.ToLower();

				if( sCompare == keywordPath )
				{
					insertIndex=-1;
					return iC;
				}

				if( keywordPath.CompareTo(sCompare) < 0 )
				{
					return BinSearch(nStart, nCheck-1, arrIndex, keywordPath, searchKeyword, caseInsensitive, ref insertIndex);
				}

				if( keywordPath.CompareTo(sCompare) > 0 )
				{
					return BinSearch(nCheck+1, nEnd, arrIndex, keywordPath, searchKeyword, caseInsensitive, ref insertIndex);
				}
			}
			else if(nEnd-nStart == 1)
			{
				IndexItem i1 = arrIndex[nStart] as IndexItem;
				IndexItem i2 = arrIndex[nEnd] as IndexItem;

				string sCompare1 = i1.KeyWordPath;

				if(searchKeyword)
					sCompare1 = i1.KeyWord;

				if(caseInsensitive)
					sCompare1 = sCompare1.ToLower();

				string sCompare2 = i2.KeyWordPath;

				if(searchKeyword)
					sCompare2 = i2.KeyWord;

				if(caseInsensitive)
					sCompare2 = sCompare2.ToLower();

				if( sCompare1 == keywordPath)
				{
					insertIndex = -1;
					return i1;
				}

				if( sCompare2 == keywordPath)
				{
					insertIndex = -1;
					return i2;
				}

				if( sCompare1.CompareTo(keywordPath) > 0)
				{
					insertIndex = nStart;
					return null;
				} 
				else if( sCompare2.CompareTo(keywordPath) > 0)
				{
					insertIndex = nEnd;
					return null;
				} 
				else 
				{
					insertIndex = nEnd+1;
				}
			}

			IndexItem itm = arrIndex[nEnd] as IndexItem;

			string sCompareI = itm.KeyWordPath;

			if(searchKeyword)
				sCompareI = itm.KeyWord;

			if(caseInsensitive)
				sCompareI = sCompareI.ToLower();

			if( sCompareI.CompareTo(keywordPath) > 0)
			{
				insertIndex = nStart;
				return null;
			} 
			else if( sCompareI.CompareTo(keywordPath) < 0)
			{
				insertIndex = nEnd+1;
				return null;
			}
			else
			{
				insertIndex = -1;
				return arrIndex[nEnd] as IndexItem;
			}
		}

		/// <summary>
		/// Checks if a keyword exists in a index collection
		/// </summary>
		/// <param name="arrIndex">index to search (arraylist of IndexItems)</param>
		/// <param name="keywordPath">keywordpath to search</param>
		/// <returns>Returns the found IndexItem, otherwise null</returns>
		private IndexItem ContainsIndex(ArrayList arrIndex, string keywordPath)
		{
			foreach(IndexItem curItem in arrIndex)
			{
				if(curItem.KeyWordPath == keywordPath)
					return curItem;
			}

			return null;
		}

		/// <summary>
		/// Searches the alinks- or klinks-index for a specific keyword/associative
		/// </summary>
		/// <param name="search">keyword/associative to search</param>
		/// <param name="typeOfIndex">type of index to search</param>
		/// <returns>Returns an ArrayList which contains IndexTopic items or null if nothing was found</returns>
		public IndexItem SearchIndex(string search, IndexType typeOfIndex)
		{
			ArrayList _index = null;

			switch( typeOfIndex )
			{
				case IndexType.AssiciativeLinks: _index = _aLinks;break;
				case IndexType.KeywordLinks: _index = _kLinks;break;
			}

			int insertIdx=0;
			IndexItem foundItem = BinSearch(0, _index.Count, _index, search, true, true, ref insertIdx);

			return foundItem;
		}
	}
}