

// The Dial-a-Phrase JavaScript Program
// (C)Stephen Battey, February 2004
// This software may not be copied or used without permission from the author.




// The KeySets Array
//  - a series of maps from digits to alphabetical characters
//	- the value at index 10 is a Boolean indicating if the key set is editable
//	- the string at index 11 is the name of the key set

var KeySets = new Array();
KeySets[0] = new Array( " 0", "1.", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz",	true,  "Custom" );
KeySets[1] = new Array( " ", " ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz",		false, "ITU Standard E.161" );
KeySets[2] = new Array( "oq", " ", "abc", "def", "ghi", "jkl", "mn", "prs", "tuv", "wxy",			false, "UK Classic" );
KeySets[3] = new Array( " ", " ", "abc", "def", "ghi", "jkl", "mno", "prs", "tuv", "wxy",			false, "North American Classic" );
KeySets[4] = new Array( " ", "qz", "abc", "def", "ghi", "jkl", "mno", "prs", "tuv", "wxy",		false, "Australia (Former Austel Standard)" );
KeySets[5] = new Array( "oqz", " ", "abc", "def", "ghi", "jkl", "mn", "prs", "tuv", "wxy",		false, "Mobile Phone Keypad 1" );
KeySets[6] = new Array( " ", "abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "uvw", "xyz", 	false, "Mobile Phone Keypad 11" );
KeySets[7] = new Array( " ", "abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz",		false, "Mobile Phone Keypad 111" );


function copyKeySets( srcSets, destSets )
{
	// loop through each key set
	var srcSet;
	var destSet;
	for (var set=0; set<srcSets.length; set++)
	{
		destSets[set] = new Array();

		srcSet = srcSets[set];
		destSet = destSets[set];

		// copy each digit mapping
		for (var map=0; map<10; map++)
		{
			destSet[map] = new String( srcSet[map] );
		}

		// copy the editable flag and name
		destSet[10] = srcSet[10];
		destSet[11] = new String( srcSet[11] );
	}
}




// The OddCharSeq Array
//  - ranks 'how odd' two consecutive characters appear together
//  For example, 'a' followed by 'b' has an oddity of 0
//               'a' followed by 'e' has an oddity of 1
//               'a' followed by 'a' has an oddity of 3

var VERY_ODD = 10;

var firstAdjLttrStat = new Array();
firstAdjLttrStat['a'] = new Array( "bcdfglmnprstu", "eivwxz", "hkoqy", "aj", "", "", "", "", "", "" );
firstAdjLttrStat['b'] = new Array( "aeiloru", "y", "h", "dk", "bcpst", "fgmnvwxz", "", "", "", "jq" );
firstAdjLttrStat['c'] = new Array( "aehiloruy", "", "tz", "cnps", "dfgmw", "bkqv", "", "", "", "jx" );
firstAdjLttrStat['d'] = new Array( "aeioru", "y", "hw", "j", "bcdgklmstvz", "fnpx", "", "", "", "q" );
firstAdjLttrStat['e'] = new Array( "lmnprsux", "acdfgqtvy", "beijo", "kw", "hz", "", "", "", "", "" );
firstAdjLttrStat['f'] = new Array( "aeiloru", "", "", "y", "cdgjnpstw", "bfhmvz", "", "", "", "kqx" );
firstAdjLttrStat['g'] = new Array( "aeiloru", "ny", "h", "w", "cdjpt", "gmsv", "", "", "", "bfkqxz" );
firstAdjLttrStat['h'] = new Array( "aeiouy", "", "", "", "cdglrtw", "bfhmnpqsv", "", "", "", "jkxz" );
firstAdjLttrStat['i'] = new Array( "mnrs", "cdlot", "ag", "bfkpvwxz", "hiju", "eqy", "", "", "", "" );
firstAdjLttrStat['j'] = new Array( "au", "eio", "", "", "chny", "gmrst", "", "", "", "bdfjklpqvwxz" );
firstAdjLttrStat['k'] = new Array( "aei", "noru", "hly", "vw", "bcgms", "jpt", "", "", "", "dfkqxz" );
firstAdjLttrStat['l'] = new Array( "aeiou", "y", "", "l", "bcdhmnrsvw", "fgptx", "", "", "", "jkqz" );
firstAdjLttrStat['m'] = new Array( "aeiouy", "", "", "nst", "bcdfghklmpr", "vwxz", "", "", "", "jq" );
firstAdjLttrStat['n'] = new Array( "aeiou", "", "y", "", "gpr", "bcdjlmnstv", "", "", "", "fhkqwxz" );
firstAdjLttrStat['o'] = new Array( "bpruv", "cdflmnostx", "aegikwyz", "h", "", "jq", "", "", "", "" );
firstAdjLttrStat['p'] = new Array( "aehilorsuy", "nt", "", "fp", "cdgkmw", "bjqv", "", "", "", "xz" );
firstAdjLttrStat['q'] = new Array( "u", "", "", "ai", "et", "hlmnopqrsvy", "", "", "", "bcdfgjkwxz" );
firstAdjLttrStat['r'] = new Array( "aehiou", "", "", "y", "cfglmpstw", "bdnrv", "", "", "", "jkqxz" );
firstAdjLttrStat['s'] = new Array( "acehiklnoptuwy", "mq", "", "frv", "bdgjsz", "", "", "", "", "x" );
firstAdjLttrStat['t'] = new Array( "aehioru", "wy", "s", "cz", "bgjlmnp", "dfktvx", "", "", "", "q" );
firstAdjLttrStat['u'] = new Array( "npr", "lmst", "gv", "bdikx", "acfhz", "ju", "", "", "", "eoqwy" );
firstAdjLttrStat['v'] = new Array( "aeio", "", "u", "r", "ly", "bcdgmpstv", "", "", "", "fhjknqwxz" );
firstAdjLttrStat['w'] = new Array( "aehio", "r", "uy", "", "l", "bcdfgjkmpst", "", "", "", "nqvwxz" );
firstAdjLttrStat['x'] = new Array( "", "e", "aiy", "", "corvx", "dmsuw", "", "", "", "bfghjklnpqtz" );
firstAdjLttrStat['y'] = new Array( "", "aeo", "iu", "t", "cfglpr", "bdhmnsw", "", "", "", "jkqvxyz" );
firstAdjLttrStat['z'] = new Array( "", "aeioy", "", "u", "lw", "hns", "", "", "", "bcdfgjkmpqrtvxz" );

var lastAdjLttrStat = new Array();
lastAdjLttrStat['a'] = new Array( "delmnrsty", "cghkpuwx", "bfio", "ajvz", "q", "", "", "", "", "" );
lastAdjLttrStat['b'] = new Array( "", "aesy", "io", "bltu", "cdgkmnrv", "fhjpwxz", "", "", "", "q" );
lastAdjLttrStat['c'] = new Array( "ehksty", "ao", "i", "dlu", "bcfmr", "gnpqvw", "", "", "", "jxz" );
lastAdjLttrStat['d'] = new Array( "es", "aoy", "i", "dhnrtu", "gjlmpz", "bcfkqvx", "", "", "", "w" );
lastAdjLttrStat['e'] = new Array( "delnrsty", "ampwx", "bcfghikouz", "v", "q", "j", "", "", "", "" );
lastAdjLttrStat['f'] = new Array( "y", "efst", "a", "dou", "bghilmrxz", "cnpvw", "", "", "", "jkq" );
lastAdjLttrStat['g'] = new Array( "esy", "aho", "in", "gmrtu", "dl", "fkpv", "", "", "", "bcjqwxz" );
lastAdjLttrStat['h'] = new Array( "sty", "aei", "mo", "lnru", "bdghp", "fkqvwz", "", "", "", "cjx" );
lastAdjLttrStat['i'] = new Array( "acdelnpst", "morx", "bfgik", "hvz", "qu", "jwy", "", "", "", "" );
lastAdjLttrStat['j'] = new Array( "", "", "a", "eio", "u", "cdgjklrsty", "", "", "", "bfhmnpqvwxz" );
lastAdjLttrStat['k'] = new Array( "es", "ay", "io", "htu", "gw", "bcfklmnrv", "", "", "", "djpqxz" );
lastAdjLttrStat['l'] = new Array( "aelsy", "dikot", "fmp", "bcnu", "grvxz", "hw", "", "", "", "jq" );
lastAdjLttrStat['m'] = new Array( "aesy", "p", "bino", "mtu", "dfhkl", "cgrvw", "", "", "", "jqxz" );
lastAdjLttrStat['n'] = new Array( "adegsty", "iko", "nx", "cu", "fhjlmvz", "bpr", "", "", "", "qw" );
lastAdjLttrStat['o'] = new Array( "dlmnrst", "fgkopwxy", "abceiu", "hvz", "q", "", "", "", "", "j" );
lastAdjLttrStat['p'] = new Array( "ehsy", "t", "aio", "dlmpru", "bfgkn", "cqvwx", "", "", "", "jz" );
lastAdjLttrStat['q'] = new Array( "", "", "", "", "su", "adehilmnpqrtvy", "", "", "", "bcfgjkowxz" );
lastAdjLttrStat['r'] = new Array( "adekmsty", "ino", "bfglpru", "c", "hvwxz", "j", "", "", "", "q" );
lastAdjLttrStat['s'] = new Array( "ehmst", "ay", "ikopu", "cn", "dfglrwz", "bqv", "", "", "", "jx" );
lastAdjLttrStat['t'] = new Array( "aehsy", "io", "tuz", "dglnr", "cmx", "bfkpvw", "", "", "", "jq" );
lastAdjLttrStat['u'] = new Array( "elmrs", "npt", "abdgikxy", "cfhz", "ovw", "jqu", "", "", "", "" );
lastAdjLttrStat['v'] = new Array( "e", "", "aoy", "is", "dgnrtu", "bchlpvw", "", "", "", "fjkmqxz" );
lastAdjLttrStat['w'] = new Array( "", "ns", "aekly", "dimt", "fop", "bcghru", "", "", "", "jqvwxz" );
lastAdjLttrStat['x'] = new Array( "", "y", "t", "aei", "osx", "cdlpruvw", "", "", "", "bfghjkmnqz" );
lastAdjLttrStat['y'] = new Array( "s", "l", "aemn", "doprtx", "cgikuz", "hy", "", "", "", "bfjqvw" );
lastAdjLttrStat['z'] = new Array( "e", "", "aoy", "iz", "u", "dhlmnst", "", "", "", "bcfgjkpqrvwx" );

var internAdjLttrStat = new Array();
internAdjLttrStat['a'] = new Array( "bcdgilmnprstu", "efkvwxyz", "hjo", "aq", "", "", "", "", "", "" );
internAdjLttrStat['b'] = new Array( "aeilo", "brsu", "cdjmty", "fghnpvw", "k", "qz", "x", "", "", "" );
internAdjLttrStat['c'] = new Array( "aehikortu", "cly", "", "nq", "bdmps", "fgwz", "vx", "", "", "j" );
internAdjLttrStat['d'] = new Array( "aeior", "dglnuy", "bfhjmsvw", "cpt", "kz", "q", "x", "", "", "" );
internAdjLttrStat['e'] = new Array( "acdefgilmnoprstu", "bhvwxy", "jkqz", "", "", "", "", "", "", "" );
internAdjLttrStat['f'] = new Array( "ei", "aflortu", "y", "s", "bcdghmnpw", "k", "vz", "", "", "jqx" );
internAdjLttrStat['g'] = new Array( "aeilr", "ghnou", "msy", "bdfptw", "ck", "jvz", "q", "", "", "x" );
internAdjLttrStat['h'] = new Array( "aeiory", "lntu", "bfmsw", "cdghp", "jkvz", "q", "", "", "", "x" );
internAdjLttrStat['i'] = new Array( "abcdefglmnoprstvz", "ku", "hiqx", "jwy", "", "", "", "", "", "" );
internAdjLttrStat['j'] = new Array( "", "", "aeou", "i", "", "djnr", "bhklmpstvwy", "", "", "cfgqxz" );
internAdjLttrStat['k'] = new Array( "ei", "al", "bhmnorstuwy", "dfkp", "cgj", "v", "qxz", "", "", "" );
internAdjLttrStat['l'] = new Array( "aeilou", "cdfmnpstvy", "bghkw", "r", "jqz", "", "x", "", "", "" );
internAdjLttrStat['m'] = new Array( "abeiop", "mu", "flnsy", "cdhrtvw", "gjk", "qz", "", "", "", "x" );
internAdjLttrStat['n'] = new Array( "acdefginost", "bhklmpruvw", "jqyz", "x", "", "", "", "", "", "" );
internAdjLttrStat['o'] = new Array( "bcdgilmnoprstuw", "aefhkvx", "qyz", "j", "", "", "", "", "", "" );
internAdjLttrStat['p'] = new Array( "aehiloprt", "su", "ny", "bcdfgkmw", "j", "v", "qz", "", "", "x" );
internAdjLttrStat['q'] = new Array( "u", "", "", "", "", "ir", "aefglopqstw", "", "", "bcdhjkmnvxyz" );
internAdjLttrStat['r'] = new Array( "acdeimnorstu", "bfghklpvwy", "", "jqz", "", "x", "", "", "", "" );
internAdjLttrStat['s'] = new Array( "acehiopstu", "klmny", "bdfgqrw", "jv", "", "z", "", "", "", "x" );
internAdjLttrStat['t'] = new Array( "aehilortu", "cmsy", "bfgnpw", "djkvz", "q", "", "x", "", "", "" );
internAdjLttrStat['u'] = new Array( "abcilmnprst", "defgo", "kvxz", "hjuy", "qw", "", "", "", "", "" );
internAdjLttrStat['v'] = new Array( "aei", "o", "u", "r", "vy", "dgklns", "cmptwz", "", "", "bfhjqx" );
internAdjLttrStat['w'] = new Array( "", "aeio", "bdhlnrs", "cfkmptw", "guyz", "j", "qv", "", "", "x" );
internAdjLttrStat['x'] = new Array( "", "aeipt", "couy", "hls", "bdfmw", "gnqrv", "kxz", "", "", "j" );
internAdjLttrStat['y'] = new Array( "p", "acdeilmnorst", "bfgw", "hkuxz", "v", "jqy", "", "", "", "" );
internAdjLttrStat['z'] = new Array( "", "aeio", "lz", "uy", "bp", "cdghkmnqrstvw", "fj", "", "", "x" );




// The Phrase Class
//  - stores information about a phrase

function Phrase( text )
{
	this.text = text;
	this.rank = "n/a";
}




// The Search for Phrases Functionality
//  - takes a string of digits and generates an array of possible phrases using the KeySets mapping

var TelephoneNumber = null;			// array of digits in the number to be processed
var KeySetInUse = null;			// reference to the key-set mapping being used
var CurrentPhrase = null;			// text string in which the phrase is built

// The search algorithm in call-back format.
// This routine will find the next complete phrase or, if all search avenues have been exhausted, call the start_ranking function.
function search_phrases_background()
{
	var digitIndex = CurrentPhrase.length - 1;
	var digit;
	var keyMap;
	var lastChar;
	var lastCharIndex;
	var nextCharIndex;

	// remove exhausted characters from the current phrase
	while (digitIndex > -1)
	{
		// extract digit being dealt with, the character last used to take the place of this digit
		// and the index of that character within the key set
		digit = TelephoneNumber[digitIndex];
		keyMap = KeySetInUse[digit];
		lastChar = CurrentPhrase.substring( digitIndex, (digitIndex+1) );
		lastCharIndex = keyMap.indexOf( lastChar );

		// are there any more characters that can replace this digit?
		nextCharIndex = lastCharIndex + 1;
		if (nextCharIndex < keyMap.length)
		{
			// replace this character with another that can be tried and then continue processing
			CurrentPhrase = CurrentPhrase.substring( 0, digitIndex ) + keyMap.substring( nextCharIndex, (nextCharIndex+1) );
			break;
		}
		else
			// go back and remove another character from the current phrase
			digitIndex --;
	}

	// check if all search routes have been explored
	if (   (digitIndex == -1)
		&& (ProcessStep != 0)  )
	{
		ProcessTimer = window.setTimeout( "start_ranking()", ProcessPeriod );
	}
	else
	{
		// add characters for the remaining digits
		digitIndex ++;
		while (digitIndex < TelephoneNumber.length)
		{
			digit = TelephoneNumber[digitIndex];
			keyMap = KeySetInUse[digit];
			CurrentPhrase += keyMap.substring( 0, 1 );

			digitIndex ++;
		}

		// add the phrase to the list
		PhraseList[PhraseList.length] = new Phrase( CurrentPhrase );

		// update progress meter and call for another search
		ProcessStep ++;
		ProcessProgress += ProcessProgressInc;
		ProcessProgressBar.set_progress( ProcessProgress );
		ProcessTimer = window.setTimeout( "search_phrases_background()", ProcessPeriod );
	}
}

// The search algorithm in recursive format.
// Much simpler than the call-back routine, but no good for background running in JavaScript.
function search_phrases( keySet, telNum, digitIndex )
{
	// check if we have processed all digits in the telephone number
	var strLen = telNum.length;
	if (digitIndex >= strLen)
	{
		// add phrase to list and return back-up the call stack
		PhraseList[PhraseList.length] = new Phrase( CurrentPhrase );
		return;
	}

	// extract this digit
	var digit = parseInt( telNum.substring( digitIndex, (digitIndex+1) ) );

	// convert to possible characters
	var charSet = keySet[digit];
	for (var i=0; i<charSet.length; i++)
	{
		CurrentPhrase += charSet.substring( i, (i+1) );
		search_phrases( keySet, telNum, (digitIndex+1) );
		CurrentPhrase = CurrentPhrase.substring( 0, (CurrentPhrase.length-1) );
	}
}




// The Rank List of Phrases Functionality
//  - assigns a ranking to each phrase in a list of phrases

function rank_phrases()
{
	for (var i=0; i<PhraseList.length; i++)
		rank_phrase( PhraseList[i] );
}

function rank_phrases_background()
{
	// rank the next phrase in the list
	rank_phrase( PhraseList[ProcessStep] );

	// update progress meter
	ProcessStep ++;
	ProcessProgress += ProcessProgressInc;
	ProcessProgressBar.set_progress( ProcessProgress );

	// call for next step in processing
	if (ProcessStep < PhraseList.length)
		ProcessTimer = window.setTimeout( "rank_phrases_background()", ProcessPeriod );
	else
		ProcessTimer = window.setTimeout( "sort_phrases_by_rank_background()", ProcessPeriod );
}

function rank_phrase( phrase )
{
	// variable to count the number of odd consecqutive characters in the phrase string
	var oddityCount = 0;

	// create local references to the phrase text
	var phraseText = phrase.text;
	var phraseTextLen = phraseText.length;

	if (phraseTextLen > 1)
	{
		// lookup oddity of the first pair of characters
		oddityCount += oddity_ranking( firstAdjLttrStat, phraseText.substring( 0, 1 ), phraseText.substring( 1, 2 ) );

		// run through the phrase and extract each pair of adjacent characters
		var thisChar = phraseText.substring( 1, 2 );
		var prevChar;
		for (var charI=2; charI<(phraseTextLen-1); charI++)
		{
			prevChar = thisChar;
			thisChar = phraseText.substring( charI, charI+1 );

			// lookup oddity of this internal pair of characters
			oddityCount += oddity_ranking( internAdjLttrStat, prevChar, thisChar );
		}

		// lookup oddity of the last pair of characters
		oddityCount += oddity_ranking( lastAdjLttrStat, phraseText.substring( phraseTextLen-2, phraseTextLen-1 ), phraseText.substring( phraseTextLen-1, phraseTextLen ) );
	}

	// assign the ranking to this phrase (add 1 to ensure the range of the ranking system is "1 ...")
	phrase.rank = 1 + oddityCount;
}

function oddity_ranking( rankingMap, leftChar, rightChar )
{
	var oddityMapping = rankingMap[leftChar];
	if (typeof( oddityMapping ) == "undefined")
		// this character does not have an entry in the oddity mapping
		return VERY_ODD;

	// find the oddity rank for this character compared to the previous character
	for (var searchI=0; searchI<oddityMapping.length; searchI++)
	{
		if (oddityMapping[searchI].indexOf( rightChar ) > -1)
			return searchI;
	}

	// no oddity mapping was found for this character, assume it to be very odd
	return VERY_ODD;
}




// The Phrase Sorting Functionality
//  - sorts a list of phrases in preferrential order

function sort_phrases_by_rank_background()
{
	// sort the phrases and then initiate the next step, which is to display them
	sort_phrases_by_rank();
	ProcessTimer = window.setTimeout( "show_phrases( true )", ProcessPeriod );
}

function sort_phrases_by_rank()
{
	aQuickSort = PhraseList;
	quick_sort( 0, aQuickSort.length-1 );
}

var aQuickSort;   // Array of elements (strings, values, etc)

function quick_sort_swap( one, tother )
{
	// ignore if swapping indicies are the same
	if (one != tother)
	{
		var temp = aQuickSort[one];
		aQuickSort[one] = aQuickSort[tother];
		aQuickSort[tother] = temp;
	}
}

function quick_sort( left, right )
{
	var i, splitPos;

	// count number of elements
	i = right - left;

	if (i <= 0)
		// one element in the array - do nothing
		return;

	if (i == 1)
	{
		// two elements - just swap them if in wrong order
		if (aQuickSort[left].rank < aQuickSort[right].rank)
			quick_sort_swap( left, right );
		return;
	}

	// swap start for middle in case of an 'almost sorted' array
	quick_sort_swap( left, Math.floor( (left+right)/2 ) );

	// split the array so that any elements ordered before the split-value are to the left of splitPos
	for (i=left+1, splitPos=left; i<=right; i++)
	{
		if (aQuickSort[i].rank < aQuickSort[left].rank)
			quick_sort_swap( ++splitPos, i );
	}

	// swap back the start and middle elements
	quick_sort_swap( left, splitPos );

	quick_sort( left, splitPos-1 );		//  sort left-hand portion
	quick_sort( splitPos+1, right );		//  sort right-hand portion
}




// Processor Settings
//  - user configurable settings that alter the nature of the algorithm

var KeySetToUseId = 0;			// index of the key set to be used to generate phrases
var ProcessPeriod = 5;			// delay in msecs between processing steps




// The Top-Level Functionality
//  - called by the HTML to show a list of phrases on the HTML input form

var IDLE_PROCESS = 0;
var SEARCH_PROCESS = 1;
var RANKING_PROCESS = 2;

var ProcessTimer = null;			// reference to timed callback
var ProcessInProgress = IDLE_PROCESS;	// id of the process currently running
var ProcessStep = 0;				// stage id of process currently running
var ProcessProgress = 0;			// progress meter (0-100)
var ProcessProgressInc = 1;			// increment to add to the progress meter after each completed step
var ProcessProgressBar = new SimpleProgressBar( 300, 5, 50, "dial-a-phrase-files/GreyDot.gif", "dial-a-phrase-files/BlueDot.gif", "dial-a-phrase-files/BlackDot.gif" );
						// progress bar object used to display progress on the web-page
var PhraseList = null;			// list of phrases generated

function process()
{
	get_phrases( document.dap.number.value );

	rank_phrases();

	sort_phrases_by_rank();

	show_phrases();
}

function process_background()
{
	if (ProcessInProgress == IDLE_PROCESS)
	{
		start_search();
	}
	else if (ProcessInProgress == SEARCH_PROCESS)
	{
		window.clearTimeout( ProcessTimer );

		if (PhraseList.length > 0)
		{
			// ask user if they'd like to see the phrases found by the search before it was cancelled
			if (confirm( "Would you like to see the phrases found up to this point?" ))
				start_ranking();
			else
				start_idle();
		}
		else
			// no phrases were found, automatically cancel the search
			start_idle();
	}
	else if (ProcessInProgress == RANKING_PROCESS)
	{
		window.clearTimeout( ProcessTimer );

		if (PhraseList.length > 0)
		{
			// ask user if they'd like to see the phrases found without being sorted by rank
			if (confirm( "Would you like to see an un-sorted list of phrases found?" ))
				show_phrases();
			else
				start_idle();
		}
		else
			// no phrases were found, automatically cancel the search
			start_idle();

	}
}




// The State Entry Methods
//  - called from various points of the code to change state (IDLE, SEARCH, RANK)

function start_idle()
{
	ProcessInProgress = IDLE_PROCESS;
	ProcessProgressBar.set_progress( SimpleProgressBar.INACTIVE );
	document.dap.processcontrol.value = "Search for Phrases";
}

function start_search()
{
	// set the key-set here since it is needed to perform validation
	TelephoneNumber = new Array();
	KeySetInUse = KeySets[KeySetToUseId];
	CurrentPhrase = "";

	// validate the telephone number entered;
	// at the same time determine the number of characters that will be searched
	var numberToSearch = document.dap.number.value;
	if (numberToSearch.length == 0)
	{
		alert( "Please enter a telephone number into the 'Telephone Number' text box." );
		document.dap.number.focus();
	}
	else
	{
		var isValid = true;
		var charSearchCount = 1;

		var digit;
		for (var i=0; i<numberToSearch.length; i++)
		{
			digit = parseInt( numberToSearch.substring( i, (i + 1) ) );
			if (isNaN( digit ))
				isValid = false;
			else
			{
				TelephoneNumber[TelephoneNumber.length] = digit;
				charSearchCount = charSearchCount * KeySetInUse[digit].length;
			}
		}

		if (isValid)
		{
			// start the seach process
			ProcessInProgress = SEARCH_PROCESS;
			ProcessProgress = 0;
			ProcessProgressInc = 100 / charSearchCount;
			ProcessStep = 0;
			PhraseList = new Array();
			ProcessTimer = window.setTimeout( "search_phrases_background()", ProcessPeriod );
			document.dap.processcontrol.value = "Cancel Search";
		}
		else
		{
			alert( "The number entered ('" + numberToSearch + "') is not a valid telephone number.\nOnly numerical digits can be processed.\n\nPlease modify your entry and re-submit." );
			document.dap.number.select();
		}
	}
}

function start_ranking()
{
	var numOfPhrases = PhraseList.length;
	if (numOfPhrases == 0)
		// nothing to rank, just move onto the display phase
		show_phrases();
	else
	{
		ProcessInProgress = RANKING_PROCESS;
		ProcessProgress = 0;
		ProcessProgressInc = 100 / numOfPhrases;
		ProcessStep = 0;
		ProcessTimer = window.setTimeout( "rank_phrases_background()", ProcessPeriod );
		document.dap.processcontrol.value = "Cancel Ranking";
	}
}




// Display Functionality
//  - takes the output from the search algorithm and creates separate, filtered lists of phrases

var DisplayData = new Array();

function PhraseData( menuName, phraseListText )
{
	this.menuName = menuName;
	this.phraseListText = phraseListText;
}

function show_phrases( areSorted )
{
	var numOfPhrases = PhraseList.length;
	if (numOfPhrases == 0)
		// Nothing to display, leave display as-is.
		// Do not display a message - for no phrases to be returned, the user must have cancelled the search.
		;
	else
	{
		// clear list of display data ready for new results
		DisplayData = new Array();

		var phraseIndex = 0;
		var phraseList;

		if (areSorted)
		{
			// compile list of best ranking phrases
			phraseList = "";
			var bestRank = PhraseList[0].rank;
			while (   (phraseIndex < numOfPhrases)
				   && (PhraseList[phraseIndex].rank == bestRank)  )
			{
				phraseList += PhraseList[phraseIndex].text + "\n";
				phraseIndex ++;
			}
			DisplayData[DisplayData.length] = new PhraseData( "Best phrases (rank " + bestRank + ")", phraseList );

			if (phraseIndex < numOfPhrases)
			{
				// compile list of any other good phrases
				var goodRanking = PhraseList[phraseIndex].text.length;
				var nextBestRank = PhraseList[phraseIndex].rank;
				if (parseInt( nextBestRank ) < goodRanking)
				{
					phraseList = "";
					var phraseRank;
					var upperRange = "";
					while (phraseIndex < numOfPhrases)
					{
						phraseRank = PhraseList[phraseIndex].rank;
						if (parseInt( PhraseList[phraseIndex].rank ) >= goodRanking)
							break;

						phraseList += PhraseList[phraseIndex].text + "   (rank: " + phraseRank + ")\n";
						if (phraseRank != nextBestRank)
							upperRange = " - " + phraseRank;

						phraseIndex ++;
					}
					DisplayData[DisplayData.length] = new PhraseData( "Good phrases (ranked " + nextBestRank + upperRange + ")", phraseList );
				}
				else
				{
					alert( "Sorry, no good phrases were found for this telephone number.\nHowever, there may be some memorable character sequences in the phrases that have been found." );
				}
			}

			if (phraseIndex < numOfPhrases)
			{
				// compile a list of the remaining phrases
				phraseList = "";
				while (phraseIndex < numOfPhrases)
				{
					phraseList += PhraseList[phraseIndex].text + "   (rank: " + PhraseList[phraseIndex].rank + ")\n";
					phraseIndex ++;
				}
				DisplayData[DisplayData.length] = new PhraseData( "Other phrases", phraseList );
			}

			// compile a list of all phrases
			phraseList = "";
			for (var i=0; i<DisplayData.length; i++)
			{
				phraseList += DisplayData[i].phraseListText;
			}
			DisplayData[DisplayData.length] = new PhraseData( "All phrases", phraseList );
		}
		else
		{
			// build a list of un-sorted phrases
			phraseList = "";
			for (var i=0; i<numOfPhrases; i++)
				phraseList += PhraseList[i].text + "   (rank: " + PhraseList[i].rank + ")\n";
			DisplayData[DisplayData.length] = new PhraseData( "Un-sorted phrases", phraseList );
		}

		var displayMenu = document.dap.displayopt;

		// remove old items from display menu
		while (displayMenu.options.length > 0)
			displayMenu.options[displayMenu.options.length-1] = null;

		// add new items from latest search
		for (var i=0; i<DisplayData.length; i++)
		{
			displayMenu.options[displayMenu.options.length] = new Option( DisplayData[i].menuName, "" );
		}

		// default to select first (usually the best) option
		displayMenu.selectedIndex = 0;

		update_phrase_display();
	}

	// all done, move into idle mode
	start_idle();
}

function update_phrase_display()
{
	document.dap.phraselist.value = DisplayData[document.dap.displayopt.selectedIndex].phraseListText;
}




// The Startup Feature
//  - prepares (clears) the form ready for use

function startup()
{
	document.dap.number.value = "";
	document.dap.phraselist.value= "";

	// remove options used to expand pull-down list on old browsers
	while (document.dap.displayopt.options[1] != null)
		document.dap.displayopt.options[1] = null;

	document.dap.number.focus();
}

window.onload = startup;




// The Options Controller
//  - opens and communicates with the options sub-window

var OptionsWin = null;

function open_options()
{
	// take focus (dotted outline) away from button image
	document.dap.number.focus();

	if (   (OptionsWin != null)
		&& (!OptionsWin.closed)  )
	{
		// don't open another window, just bring the one that's open to the front
		OptionsWin.focus();
		return;
	}

	OptionsWin = window.open( "dial-a-phrase-files/options.html", "dial_a_phrase_options", "screenX=100,screenY=50,left=100,top=50,width=350,height=450,menubar=no,location=no,resizable=yes,status=no,titlebar=no,toolbar=no" );
}

function get_key_sets( optKeySets )
{
	copyKeySets( KeySets, optKeySets );
}
function set_key_sets( optKeySets )
{
	copyKeySets( optKeySets, KeySets );
}
document.get_key_sets = get_key_sets;
document.set_key_sets = set_key_sets;

function get_key_set_id()
{
	return KeySetToUseId;
}
function set_key_set_id( id )
{
	KeySetToUseId = id;
}
document.get_key_set_id = get_key_set_id;
document.set_key_set_id = set_key_set_id;

function get_process_period()
{
	return ProcessPeriod;
}
function set_process_period( period )
{
	ProcessPeriod = period;
}
document.get_process_period = get_process_period;
document.set_process_period = set_process_period;