<!-- Copyright © 2009 ExIdeas, Inc.*
// version 1.80
/****************************************************************************************************************/	
//		Global Variables for MessagEase algorithm 
/****************************************************************************************************************/
//Constants
	var ME_const_min_move		= 4;						// mouse moves with less distance of these many pixels will be ignored
	var ME_const_min_flat_ecc	= 4;						// strokes with eccentricity less will be circles, more will be flats
	var ME_const_high_ecc		= ME_const_min_flat_ecc +1;	// initial high eccentricity
	
	var ME_cons_tan1 			= 0.414; 
	var ME_const_tan2 			= 2.414;
	
	var ME_const_kb_width 		= 320;
	var ME_const_kb_height 		= 320;
	var ME_const_cell_width 	= ME_const_kb_width/4;
	var ME_const_cell_height 	= ME_const_kb_height/4;
	
	// limits:criteria for distinguishing strokes
	var ME_const_min_move		= 4;						// mouse moves with less distance of these many pixels will be ignored
	
	var ME_min_drag_threshold	= ME_const_cell_width/4;	// min drag is set to 20 or 1/4 of the width of the cell   
	var ME_const_min_flat_ecc	= 4;						// strokes with eccentricity less will be circles, more will be flats
	var ME_const_min_dist_ratio	= 2;						// if the ratio of path /net distance is more than 2, then a shape is drawn

	var ME_const_high_ecc		= ME_const_min_flat_ecc +1;	// initial high eccentricity
	
// imageArray holds the keyboard images. Depending on the state or mode, a different bitmap for the keyboard will be
	//var	ImageArray =  ["uppercase.jpg", "uppercaseBusy.jpg"," lowercase.jpg"," lowercasebusy.jpg", "number.jpg", "lowercasebusy.jpg", "lowercasebusy.jpg", "lowercasebusy.jpg", "lowercasebusy.jpg",]
	
	var ME_keyboards 			= new Array();				// this multi-dim associative array keeps all the characters of the keyboards
	var ME_mouse_down_p 		= new Object();				// point mouse down has x and y
	var ME_mouse_prev 			= new Object();				// point prvevious mouse location
	var ME_mouse_up_p	 		= new Object();				// point mouse up
	var ME_mouse_max_p	 		= new Object();				// point in the path with max distance from mouse down

	var ME_total_path_length;								// the length of the total path that the mouse has travelled
	var ME_net_distance;									// net distance from the down point
	var ME_dist_ratio;										// the ratio of total path over net distance
	var ME_max_distance;									// max distance from the down point
	var ME_total_area;										// A measure of the total area that the path has covered 
	var ME_circle_eccentricity;								// measure of flatness of the circle

	var ME_Language_Name	= "English";					// variable to hold the name of the current keyboards and languages, English
	var ME_other_language	= "Katakana";					// hard coded to  one other language for now
	
	var ME_Keyboard_Side	= "LetterSide";					// it's either "LetterSide" or "NumberSide"
	var ME_Keyboard_Case 	= "lowercase";					// either "uppercase" or "lowercase"
	var ME_Keyboard_Busy 	= "normal";						// "normal" or "busy"
	var ME_Keyboard_Image; 									// holds the image jpg
	var ME_row;												// row of the keyboard on down point 0, 1, 2 ( or 3 for number key)
	var ME_col;												// col of the keyboard on down point 0, 1, 2
	var ME_direction;										// one of 4 directions that the storke points to E, N, W, S, or  center
	var ME_section;											// one of 8 sections that the storke points to E, NE, N, NW, W, SW, S, SE, or  Center
	var ME_stroke_type;										// one of: Tap, Drag, DragReturn, Circle 
	var ME_stroke_command 	= "Lowercase_Command"			// variable holding the command being processed
	
	var ME_drag_flag;										// boolean flag indicating that the pen/finger is down.
	ME_AutoCombine_Flag = false;							// if set then the previous two characters have combined: it's used by backspace to undo
	ME_ult_char 		= "";								// last two chars to be saved in auto combine to undo
	ME_penult_char 		= "";
 
/*********************************************************************************************************************/
function ME_initilize(){
	display_initial_message(initialMessage);				// HTML text displayed on the screen the first time "Tap or drag on the buttons to enter.."
	tx = cursor;
	change_keyboard_bitmap();
}
/*********************************************************************************************************************/
function ME_MouseDown(p){			// MessagEase's mouse down routine
	ME_mouse_down_p 		= p;
	ME_mouse_prev			= p;		
	ME_mouse_max_p			= p;
	ME_total_path_length	= 0;							// reset total length that the mouse has traveled
	ME_total_area			= 0;							// reset ME_total_area
	ME_max_distance			= 0;							// reset me_max_distance
	ME_net_distance			= 0;
	ME_drag_flag	 		= true;
}
/*********************************************************************************************************************/
function ME_MouseMove(p){			// MessagEase's mouse down routine
	if (ME_drag_flag){										// only do this if mouse is down and drag has started
		var inc_dist = distancePP(p, ME_mouse_prev)			// calculate the distance of this point from previous mouse point
		if (inc_dist > ME_const_min_move) {					// filter out spurious mouse move; only act if it has really moved
			ME_total_path_length 	+= inc_dist;			// update the path length of mouse travel
			ME_total_area			+= (p.x - ME_mouse_prev.x) * (p.y + ME_mouse_prev.y);				// integrate the area under the path curve
			ME_mouse_prev		 	 = p;
			if (distancePP (p, ME_mouse_down_p) >  distancePP (ME_mouse_max_p, ME_mouse_down_p) )		// if this point is further away from mouse down
				ME_mouse_max_p = p;																		// update ME_mouse_max_p
		}
	}
}
/*********************************************************************************************************************/
function ME_MouseUp(p){									// MessagEase's mouse up routine
	if (ME_drag_flag){	
		ME_total_path_length += distancePP(p, ME_mouse_prev);											// update total path traveled	
		ME_mouse_up_p	= p;
		ME_total_area			+= (p.x - ME_mouse_prev.x) * (p.y + ME_mouse_prev.y);					// integrate the area under the path curve
		ME_total_area			+= (ME_mouse_down_p.x - p.x) * (ME_mouse_down_p.y + p.y);				// integrate the area under the path curve
		if (distancePP (p, ME_mouse_down_p) >  distancePP (ME_mouse_max_p, ME_mouse_down_p) )			// if this point is further away from mouse down
			ME_mouse_max_p = p;
		ME_max_distance = distancePP (ME_mouse_max_p, ME_mouse_down_p);
		ME_circle_eccentricity = ME_const_high_ecc; 													// initialized for a high number
		
		if (ME_total_area < 0) ME_total_area = -ME_total_area;											// taking absolute value of ME_total_area
		if (ME_total_area > 0)			 // if it has moved, then measure circle eccentricity
			ME_circle_eccentricity = (ME_max_distance * ME_max_distance)/ME_total_area;					//  4/pi for a circle, larger for more elliptic shapes
		ME_net_distance = distancePP (ME_mouse_down_p, ME_mouse_up_p);
		ME_dist_ratio = 
		ME_drag_flag = false;
		ME_process_stroke();
	}
}
/*********************************************************************************************************************/
function distancePP (p1, p2){							// returns distance as an integer
	var delta_x = p1.x - p2.x;
	var delta_y = p1.y - p2.y;
	return Math.floor(Math.sqrt((delta_x * delta_x)+( delta_y * delta_y)));				
}
/*********************************************************************************************************************/
function ME_process_stroke(){
	ME_stroke_command = "";
	ME_col 			= "Col" + Math.ceil(ME_mouse_down_p.x/ME_const_cell_width);			// create string row index: Col1, Col2, Col3, Col4
	ME_row 			= "Row" + Math.ceil(ME_mouse_down_p.y/ME_const_cell_height);		// create string col index: Col1, Col2, Col3, Col4
	ME_dist_ratio = 0;
	if (ME_net_distance>0)
		ME_dist_ratio 	= ME_total_path_length/ME_net_distance;
																				
	ME_section = 'Center'; ME_Direction='Center';						// assume CENTER
	if  ( (ME_net_distance < ME_min_drag_threshold) && ( ME_dist_ratio < ME_const_min_dist_ratio))			// it's a tap
		ME_stroke_type = "Tap";			
	else 	if ( ME_dist_ratio < ME_const_min_dist_ratio){													// it's a drag, a regular stroke (not a return stroke)		
				find_ME_SectionDirection(ME_mouse_up_p);													// find section/direction based on mouse up
				ME_stroke_type = "Drag";	
			}
			else {
				ME_stroke_type = 1;
				if (ME_circle_eccentricity > ME_const_min_flat_ecc){											//drag and return
					find_ME_SectionDirection(ME_mouse_max_p);												// calculate direction/section based on max point
					ME_stroke_type = "DragReturn";	
				}
				else
					ME_stroke_type = "Circle";	//  it's a circle and return
			}
	assignCharOrCommandToStorke();				// determine which command or char is entered
	//debug();									// comment this out to hide the debug comments on a yellow background
	processCommandOrEnterChar();				// execute command or 
}
/*********************************************************************************************************************/
function assignCharOrCommandToStorke(){ // determine which command or char is entered
	if (ME_row == "Row4")		// process the last row (back space and space and 0 in number kb
		if(ME_col == "Col1")	// backspace and delete
			if (ME_Direction == "E")  	ME_stroke_command = "Delete_Command";
			else 					 	ME_stroke_command = "BackSpace_Command";
		else
			if(ME_col == "Col2" && ME_Keyboard_Side == "NumberSide")	
				ME_stroke_command ="0";
			else
				if (ME_Direction == "N")
					ME_stroke_command = "Busy_Toggle_Command";
				else
					if (ME_Direction == "S")
						ME_stroke_command = "CR_Command";
					else
						ME_stroke_command = "Space_Command";
	
	else if (ME_col == "Col4"){	// process the last column, editing commands, toggle letter/number, and carriage return
		if (ME_row == "Row1")
			ME_stroke_command = "Edit_Command";
		else 
			if (ME_row == "Row2")
				ME_stroke_command = "Letter_Number_Toggle_Command";
			else 
				if (ME_row == "Row3")
					ME_stroke_command = "CR_Command";
	}
	else
		lookup_ME_stroke_command();
}
/*********************************************************************************************************************/
function lookup_ME_stroke_command(){
	var	inverseStrokeType =  {"Tap":"Circle", "Circle" : "Tap", "Drag":"DragReturn", "DragReturn" : "Drag"};			// assoc. array to reverse type when cap is on for letter pairs
	var letterPair = ME_keyboards[ME_Language_Name][ME_Keyboard_Side][ME_row][ME_col][ME_section]["LetterPair"];
	if (letterPair && ME_Keyboard_Case 	== "uppercase")
		ME_stroke_type = inverseStrokeType[ME_stroke_type];
	ME_stroke_command  = ME_keyboards[ME_Language_Name][ME_Keyboard_Side][ME_row][ME_col][ME_section][ME_stroke_type]; // get the char or command from the table
}
/*********************************************************************************************************************/
function processCommandOrEnterChar(){		// interprets the ME_stroke_command and either executes a command or enters a character
	if (update_keyboard_state()) 			// return if the stroke was meant to update the keyboard only 
		return;
	switch(ME_stroke_command){					// parse commands and include routines to execute them.
		case "Combine_Command":
		  	compbine();
		  	break;    
		case "Edit_Command":
		  	if (ME_stroke_type == 'Circle')
				wipe();							// erase display
			//else								//send a notice to the user about edit
		  	break;
		case "CR_Command":
		  	ME_stroke_command = "\n";			//execute Combine routine;
		  	break;
		case "Space_Command":
		  	ME_stroke_command = " ";			// just add space
		  	break;
		case "BackSpace_Command":				// check to see if we need to undo autocombine else call bspace
			if (ME_AutoCombine_Flag)
				undo_AutoCombine();
			else
		  		bspace(); 							
		  	break;
		case "Delete_Command":
		  										//include delete routine here;
		  	break;
		case "CursorRight":
		  										//include cursor right routine here;
		  	break;
		case "CursorLeft_Command":
												//include cursor right routine here;		 
		default:
		  if (ME_stroke_command.length >2)
		  		ME_stroke_command == "";		// erase and ignore all other commands for now
	}// case
			
	if (ME_stroke_command.length <9){				// filter out long command lines. 
		appendtext(ME_stroke_command);				// include routine that sends the stroke to input stream
		do_Autocombine();							// check to see if the last two chars need to be auto compbined e.g. a + ^
	}
}

/*********************************************************************************************************************/
function update_keyboard_state(){ //  any of these commands can change the state of the keyboard: KB_Command: Lowercase_Command, Cap_Command, Busy_Toggle_Command, Letter_Number_Toggle_Command
	var keyboard_state_updated = false						// return value
	
	if (ME_stroke_command =="Letter_Number_Toggle_Command"){
		if(ME_Keyboard_Side	== "LetterSide")  ME_Keyboard_Side = "NumberSide";
		else 								  ME_Keyboard_Side = "LetterSide";
		keyboard_state_updated = true;
	}
	else 
	if (ME_stroke_command =="Busy_Toggle_Command"){
		if(ME_Keyboard_Busy	== "normal")  ME_Keyboard_Busy = "busy";
		else 							  ME_Keyboard_Busy = "normal";
		keyboard_state_updated = true;
	}
	else 
	if (ME_stroke_command =="Cap_Command"){
		ME_Keyboard_Case 	= "uppercase";
		keyboard_state_updated = true;
	}
	else
	if (ME_stroke_command =="Lowercase_Command"){
		ME_Keyboard_Case 	= "lowercase";
		keyboard_state_updated = true;
	}
	var kb = ME_keyboards[ME_Language_Name];
	if (keyboard_state_updated)
		change_keyboard_bitmap();
	return(keyboard_state_updated);
}
/*********************************************************************************************************************/
function change_keyboard_bitmap(){
	var kb = ME_keyboards[ME_Language_Name];
	if (ME_Keyboard_Side == "LetterSide")
			ME_Keyboard_Image = kb["KeyboardImages"][ME_Keyboard_Side][ME_Keyboard_Case][ME_Keyboard_Busy];
		else
			ME_Keyboard_Image = kb["KeyboardImages"][ME_Keyboard_Side];
	showKeyboardImage("../bitmaps/" + ME_keyboards[ME_Language_Name]["Name"] +"/" + ME_Keyboard_Image);
}
/*********************************************************************************************************************/
function change_keyboard_language(language_name){
	ME_Language_Name = language_name;							// change the global name of the language
	change_keyboard_bitmap();									// show the bitmap of the new keyboard/language.
}
/*********************************************************************************************************************/
function find_ME_SectionDirection(p){ 										// assignes the globals ME_Direction and ME_section
	var distx 	= (p.x - ME_mouse_down_p.x);
	var disty 	= -(p.y  - ME_mouse_down_p.y ); 							// disy is inverted since y increases downward 
	var Tangent = disty/distx;	
		
	if (distx == 0)				// if distx is 0, do not divide!
		if (disty >= 0)
			{ ME_section = 'N'; ME_Direction= 'N'}			// north  up
		else
			{ME_section = 'S'; ME_Direction = 'S'}			// points down
	else{                               // distx !=0 so we can divide		
		if ((Tangent > (- ME_cons_tan1)) && (Tangent <= ME_cons_tan1))		// section 0; 4
			if (distx >= 0) ME_section = 'E'; else ME_section = 'W';
		
		if ((Tangent > ME_cons_tan1) && (Tangent <= ME_const_tan2))		// section 1; 5
			if (distx >= 0) ME_section = 'NE'; else ME_section = 'SW';
		
		if ((Tangent > ME_const_tan2) || (Tangent <= (- ME_const_tan2)))	// section 2; 6
			if (disty >= 0) ME_section = 'N'; else ME_section = 'S';
		
		if ((Tangent > (- ME_const_tan2)) && (Tangent <= (-ME_cons_tan1)))	// section 3; 7
			if (disty >= 0) ME_section = 'NW'; else ME_section = 'SE';

										// determining one of four directions (0= right -3)
		if ((Tangent > -1) && (Tangent <= 1))		// directions 0, 2
			if (distx >= 0) ME_Direction = 'E'; else ME_Direction = 'W';
		else
			if (disty >= 0) ME_Direction = 'N'; else ME_Direction = 'S';		
	}
}
/*************************************************************************************************************************/
//  this routine is invoked when a left-top) NW stroke is applied on the A cell, to combine the last two characters (or one) behind  the cursor into one
function compbine(){  												// check the last two chars if qualified remove add another
	var found_flag, i, Base_char;
	var ult_char 	= getCharBehindCursor(0);		// get the last two characters on the target
	var penult_char	= getCharBehindCursor(1);
	var compbineCharSet =  ME_keyboards[ME_Language_Name]['CombineChars'];
	
	found_flag=false;i=0;									// first round: combine two chars into one: example c + o = ©
	while(!found_flag && i < compbineCharSet.length){
		if ((penult_char == compbineCharSet[i]["FirstChar"])&&(ult_char == compbineCharSet[i]["SecondChar"])){ 
			removeThenAddChars(2, compbineCharSet[i]["ResultChar"]);
			found_flag = true;
		}
		i++;
	}
	
	if(!found_flag){										// Second round: combine the last char with a true RotFlag e.g. e = è
		i=0;													
		while(!found_flag && i < compbineCharSet.length){
			if ((ult_char == compbineCharSet[i]["FirstChar"])&& compbineCharSet[i]["RotFlag"]){ 
				removeThenAddChars(1, compbineCharSet[i]["ResultChar"]);
				found_flag = true;
			}
			i++;
		}
		
		i= 0;												// // Third round: still not found? enter the next combined char: e.g.: è = é
		while ((!found_flag) && (i < compbineCharSet.length)){			// rotate through another form
			if (ult_char == compbineCharSet[i]["ResultChar"]  && compbineCharSet[i]["RotFlag"]){
				Base_char = compbineCharSet[i]["FirstChar"];					// a result form was found now rotate to another
				while ((!found_flag) && (++i < compbineCharSet.length)){
					if (compbineCharSet[i]["FirstChar"] == Base_char  && compbineCharSet[i]["RotFlag"]){		// another char with the same first was found 
						removeThenAddChars(1, compbineCharSet[i]["ResultChar"]);
						found_flag = true;
					}
				}// end while  						if still not found (fist character matched then put first character 
				if(!found_flag){
						removeThenAddChars(1, Base_char);
						found_flag = true;
				}	
			}
			i++;
		}// while
	}// if(!found_flag) 
} 
/****************************************************************************************************************************/
function do_Autocombine(){			// check the last two chars if qualified remove add another											
	var found_flag, i;
	var ult_char 	= getCharBehindCursor(0);		// get the last two characters on the target
	var penult_char	= getCharBehindCursor(1);
	var compbineCharSet =  ME_keyboards[ME_Language_Name]['CombineChars'];
	
	found_flag=false;i=0;									// first round: combine two chars into one: example c + o = ©
	while(!found_flag && i < compbineCharSet.length){
		if ((penult_char == getCharUnicode(compbineCharSet[i]["FirstChar"]))&&(ult_char == getCharUnicode(compbineCharSet[i]["SecondChar"]))  && compbineCharSet[i]["AutoCombineFlag"]){ // if it matches and has autocombine flag set
			removeThenAddChars(2, compbineCharSet[i]["ResultChar"]);
			found_flag 			= true;
		}
		i++;
	}
	if (found_flag){			// keep the last two chars for backspace undo
		ME_ult_char 		= ult_char;			
		ME_penult_char 		= penult_char;
	}
		
	ME_AutoCombine_Flag = found_flag;					// set or reset the flag accordingly
}
/****************************************************************************************************************************/
function undo_AutoCombine(){
	if (ME_AutoCombine_Flag) {							// an autocombine was just done reverse it
		removeThenAddChars(1, ME_penult_char);			// remove one char (the combined one) then add the saved ME_penult_char
		removeThenAddChars(0, ME_ult_char);				// add the other char
		ME_AutoCombine_Flag = false;
		return(true);									// 
	}
}
/****************************************************************************************************************************/
//The following are the  device specific code and need to be rewritten. These routines now work for HTML environment
//also please note that Carriage return in HTML does not show up, it's messy to make it work with <br> .


/*********************************************************************************************************************/
function debug(){
	if (!debugDivStyleOn){
		document.getElementById('testdiv').className ="testStyle"
		debugDivStyleOn = true;
	}
	
	var str = "Debug Values:<br><br>";
	str += 'mouse down: ' + dispPoint(ME_mouse_down_p) + '<br>';
	str += 'mouse up: ' + dispPoint(ME_mouse_up_p) + '<br>';
	str += 'mouse max: ' + dispPoint(ME_mouse_max_p) + '<br>';
	str += 'ME_total_area: ' + ME_total_area + '<br>';
	str += 'ME_max_distance: ' + ME_max_distance + '<br>';
	str += 'ME_circle_eccentricity: ' + dispNumber(ME_circle_eccentricity) + '<br>';
	str += 'ME_net_distance: ' + ME_net_distance + '<br>';
	str += 'ME_total_path_length: ' + ME_total_path_length + '<br>';
	str += 'ME_dist_ratio: ' + dispNumber(ME_dist_ratio) + '<br>';
	str += 'ME_dist_ratio: ' + dispNumber(ME_dist_ratio) + '<br>';
	str += 'ME_row: ' + ME_row + '<br>';
	str += 'ME_col: ' + ME_col + '<br>';
	str += 'ME_section: ' + ME_section + '<br>';
	str += 'ME_stroke_type: ' + ME_stroke_type + '<br>';
	if ( ME_row <4 && ME_col < 4){
		var letterPair = ME_keyboards[ME_Language_Name][ME_Keyboard_Side][ME_row][ME_col][ME_section]["LetterPair"] ;
		str += 'letterPair: ' + letterPair + '<br>';
	}
	str += 'ME_Keyboard_Case: ' + ME_Keyboard_Case + '<br>';
	
	str +=  '<br>' + ME_stroke_command ;
	document.getElementById('testdiv').innerHTML = str;		// show in the debug window
}
/*********************************************************************************************************************/
function dispPoint(p){		// create and return a string like (x, y)// used in debug
	return ('(' + p.x + ', ' + p.y + ')');
}
/*********************************************************************************************************************/
function dispNumber(n){			// displays the number with  3 decimmal points used in debug
	return (Math.floor (n*1000)/1000)
}
