%eden
/*
functions.e

  This file contains a list of various functions and procedures used
  throughout the model.

  regKey  
  writeToOutputConsole
  getTimeInMilliseconds
  ascii_to_keyno
  keyno_to_char
  add_value
  calc_ave
  showkeypress
  showkeyrelease
  getDistance
  getBarHeight
  rankUserAve
  rankVector
  getColour
  rankGrid
  getValue
  getbgColour
  getuserbgColour
  
*/

/* handle a key press in the input window */
proc regkey : inputwindow_key_1 {
	/* get the key ID number */
	keyindex = ascii_to_keyno(inputwindow_key_1[1]);
	/* only process key presses of characters A-Z */
	if (keyindex >= 1 && keyindex <= 26) {
		/* key press */
		if (inputwindow_key_1[2]==2) {
			if (keydown[keyindex]==0) {
			  /* if key is not already down, store the time it was pressed and update the keyboard */
			  keypressed[keyindex] = getTimeInMilliseconds();
			  keydown[keyindex] = 1;
			  showkeypress(keyindex);
		    }
			else {
			  /* if key is already down, update the keyboard to show how long it has been held */
			  updatekeytime(keyindex, getTimeInMilliseconds() - keypressed[keyindex]);
			}
		}
		/* key release */
		if (inputwindow_key_1[2]==3) {
		  showkeyrelease(keyindex);
		  duration = getTimeInMilliseconds() - keypressed[keyindex];
		  keydown[keyindex] = 0;
		  /* only store durations measured as between 0 and 2 seconds */
		  if (duration > 0 && duration < 2000) {
			  totalkeystrokes = totalkeystrokes + 1;
			  add_value(keyindex,duration);
			  writeToOutputConsole(strcat("duration of '",keyno_to_char(keyindex),"' is ", str(duration)));
		  }
		}
	}
}

/* Outputs the specified string to the top of the output console */
func writeToOutputConsole {
 para stringtoappend;
 shift outputstring;
 append outputstring, stringtoappend;
 temp_str = outputstring[outputstring#];
 if (temp_str# > 0) {
 for (i = (outputstring# - 1); i >= 1; i--) {
  if (outputstring[i]# > 0) {
   temp_str = strcat(str(temp_str),"\n ",str(outputstring[i]));
  }
  else break;
 }
 }
 output_value = temp_str;
}


/* return the current time in milliseconds */
func getTimeInMilliseconds {
 temp_time = ftime();
 /* scale so data type int doesn't overflow */
 temp_time2 = int(temp_time[1]) - 1200000000; 
 return ((temp_time2*1000) + temp_time[2]);
}


/* convert an ascii keycode for letters a-z to an ID# 1-26 */
func ascii_to_keyno {
  if ($1 == 97) return 1;
  if ($1 == 98) return 2;
  if ($1 == 99) return 3;
  if ($1 == 100) return 4;
  if ($1 == 101) return 5;
  if ($1 == 102) return 6;
  if ($1 == 103) return 7;
  if ($1 == 104) return 8;
  if ($1 == 105) return 9;
  if ($1 == 106) return 10;
  if ($1 == 107) return 11;
  if ($1 == 108) return 12;
  if ($1 == 109) return 13;
  if ($1 == 110) return 14;
  if ($1 == 111) return 15;
  if ($1 == 112) return 16;
  if ($1 == 113) return 17;
  if ($1 == 114) return 18;
  if ($1 == 115) return 19;
  if ($1 == 116) return 20;
  if ($1 == 117) return 21;
  if ($1 == 118) return 22;
  if ($1 == 119) return 23;
  if ($1 == 120) return 24;
  if ($1 == 121) return 25;
  if ($1 == 122) return 26;
  return 999;
}

/* convert an ID# 1-26 to a character a-z */
func keyno_to_char {
  if ($1 == 1) return "a";
  if ($1 == 2) return "b";
  if ($1 == 3) return "c";
  if ($1 == 4) return "d";
  if ($1 == 5) return "e";
  if ($1 == 6) return "f";
  if ($1 == 7) return "g";
  if ($1 == 8) return "h";
  if ($1 == 9) return "i";
  if ($1 == 10) return "j";
  if ($1 == 11) return "k";
  if ($1 == 12) return "l";
  if ($1 == 13) return "m";
  if ($1 == 14) return "n";
  if ($1 == 15) return "o";
  if ($1 == 16) return "p";
  if ($1 == 17) return "q";
  if ($1 == 18) return "r";
  if ($1 == 19) return "s";
  if ($1 == 20) return "t";
  if ($1 == 21) return "u";
  if ($1 == 22) return "v";
  if ($1 == 23) return "w";
  if ($1 == 24) return "x";
  if ($1 == 25) return "y";
  if ($1 == 26) return "z";
  return "*#*";
}


/* add a keystroke duration to the user model */
func add_value {
  append datalist[$1], $2;
}

/* return the average of a keystroke duration feature to one decimal place */
func calc_ave {
  if ($1# > 0) return mround(average($1),0.1);
  else return 0;
}


/* highlight a currently pressed key on the keyboard */
func showkeypress {
  if ($1 == 1) kb_a_bgcolour="lightblue";
  if ($1 == 2) kb_b_bgcolour="lightblue";
  if ($1 == 3) kb_c_bgcolour="lightblue";
  if ($1 == 4) kb_d_bgcolour="lightblue";
  if ($1 == 5) kb_e_bgcolour="lightblue";
  if ($1 == 6) kb_f_bgcolour="lightblue";
  if ($1 == 7) kb_g_bgcolour="lightblue";
  if ($1 == 8) kb_h_bgcolour="lightblue";
  if ($1 == 9) kb_i_bgcolour="lightblue";
  if ($1 == 10) kb_j_bgcolour="lightblue";
  if ($1 == 11) kb_k_bgcolour="lightblue";
  if ($1 == 12) kb_l_bgcolour="lightblue";
  if ($1 == 13) kb_m_bgcolour="lightblue";
  if ($1 == 14) kb_n_bgcolour="lightblue";
  if ($1 == 15) kb_o_bgcolour="lightblue";
  if ($1 == 16) kb_p_bgcolour="lightblue";
  if ($1 == 17) kb_q_bgcolour="lightblue";
  if ($1 == 18) kb_r_bgcolour="lightblue";
  if ($1 == 19) kb_s_bgcolour="lightblue";
  if ($1 == 20) kb_t_bgcolour="lightblue";
  if ($1 == 21) kb_u_bgcolour="lightblue";
  if ($1 == 22) kb_v_bgcolour="lightblue";
  if ($1 == 23) kb_w_bgcolour="lightblue";
  if ($1 == 24) kb_x_bgcolour="lightblue";
  if ($1 == 25) kb_y_bgcolour="lightblue";
  if ($1 == 26) kb_z_bgcolour="lightblue";  
}

/* return a key to normal appearance on the keyboard when released */
func showkeyrelease {
  if ($1 == 1) {
   kb_a_bgcolour="white";
   kb_a_font="{courier 16 normal}";
   kb_a_value="a";
  }
  if ($1 == 2) {
   kb_b_bgcolour="white";
   kb_b_font="{courier 16 normal}";
   kb_b_value="b";
  }
  if ($1 == 3) {
   kb_c_bgcolour="white";
   kb_c_font="{courier 16 normal}";
   kb_c_value="c";
  }
  if ($1 == 4) {
   kb_d_bgcolour="white";
   kb_d_font="{courier 16 normal}";
   kb_d_value="d";
  }
  if ($1 == 5) {
   kb_e_bgcolour="white";
   kb_e_font="{courier 16 normal}";
   kb_e_value="e";
  }
  if ($1 == 6) {
   kb_f_bgcolour="white";
   kb_f_font="{courier 16 normal}";
   kb_f_value="f";
  }
  if ($1 == 7) {
   kb_g_bgcolour="white";
   kb_g_font="{courier 16 normal}";
   kb_g_value="g";
  }
  if ($1 == 8) {
   kb_h_bgcolour="white";
   kb_h_font="{courier 16 normal}";
   kb_h_value="h";
  }
  if ($1 == 9) {
   kb_i_bgcolour="white";
   kb_i_font="{courier 16 normal}";
   kb_i_value="i";
  }
  if ($1 == 10) {
   kb_j_bgcolour="white";
   kb_j_font="{courier 16 normal}";
   kb_j_value="j";
  }
  if ($1 == 11) {
   kb_k_bgcolour="white";
   kb_k_font="{courier 16 normal}";
   kb_k_value="k";
  }
  if ($1 == 12) {
   kb_l_bgcolour="white";
   kb_l_font="{courier 16 normal}";
   kb_l_value="l";
  }
  if ($1 == 13) {
   kb_m_bgcolour="white";
   kb_m_font="{courier 16 normal}";
   kb_m_value="m";
  }
  if ($1 == 14) {
   kb_n_bgcolour="white";
   kb_n_font="{courier 16 normal}";
   kb_n_value="n";
  }
  if ($1 == 15) {
   kb_o_bgcolour="white";
   kb_o_font="{courier 16 normal}";
   kb_o_value="o";
  }
  if ($1 == 16) {
   kb_p_bgcolour="white";
   kb_p_font="{courier 16 normal}";
   kb_p_value="p";
  }
  if ($1 == 17) {
   kb_q_bgcolour="white";
   kb_q_font="{courier 16 normal}";
   kb_q_value="q";
  }
  if ($1 == 18) {
   kb_r_bgcolour="white";
   kb_r_font="{courier 16 normal}";
   kb_r_value="r";
  }
  if ($1 == 19) {
   kb_s_bgcolour="white";
   kb_s_font="{courier 16 normal}";
   kb_s_value="s";
  }
  if ($1 == 20) {
   kb_t_bgcolour="white";
   kb_t_font="{courier 16 normal}";
   kb_t_value="t";
  }
  if ($1 == 21) {
   kb_u_bgcolour="white";
   kb_u_font="{courier 16 normal}";
   kb_u_value="u";
  }
  if ($1 == 22) {
   kb_v_bgcolour="white";
   kb_v_font="{courier 16 normal}";
   kb_v_value="v";
  }
  if ($1 == 23) {
   kb_w_bgcolour="white";
   kb_w_font="{courier 16 normal}";
   kb_w_value="w";
  }
  if ($1 == 24) {
   kb_x_bgcolour="white";
   kb_x_font="{courier 16 normal}";
   kb_x_value="x";
  }
  if ($1 == 25) {
   kb_y_bgcolour="white";
   kb_y_font="{courier 16 normal}";
   kb_y_value="y";
  }
  if ($1 == 26) {
   kb_z_bgcolour="white";
   kb_z_font="{courier 16 normal}";
   kb_z_value="z";
  }
}

func updatekeytime {
  if ($1 == 1) {
   kb_a_font="{courier 7 normal}";
   kb_a_value=str($2);
  }
  if ($1 == 2) {
   kb_b_font="{courier 7 normal}";
   kb_b_value=str($2);
  }
  if ($1 == 3) {
   kb_c_font="{courier 7 normal}";
   kb_c_value=str($2);
  }
  if ($1 == 4) {
   kb_d_font="{courier 7 normal}";
   kb_d_value=str($2);
  }
  if ($1 == 5) {
   kb_e_font="{courier 7 normal}";
   kb_e_value=str($2);
  }
  if ($1 == 6) {
   kb_f_font="{courier 7 normal}";
   kb_f_value=str($2);
  }
  if ($1 == 7) {
   kb_g_font="{courier 7 normal}";
   kb_g_value=str($2);
  }
  if ($1 == 8) {
   kb_h_font="{courier 7 normal}";
   kb_h_value=str($2);
  }
  if ($1 == 9) {
   kb_i_font="{courier 7 normal}";
   kb_i_value=str($2);
  }
  if ($1 == 10) {
   kb_j_font="{courier 7 normal}";
   kb_j_value=str($2);
  }
  if ($1 == 11) {
   kb_k_font="{courier 7 normal}";
   kb_k_value=str($2);
  }
  if ($1 == 12) {
   kb_l_font="{courier 7 normal}";
   kb_l_value=str($2);
  }
  if ($1 == 13) {
   kb_m_font="{courier 7 normal}";
   kb_m_value=str($2);
  }
  if ($1 == 14) {
   kb_n_font="{courier 7 normal}";
   kb_n_value=str($2);
  }
  if ($1 == 15) {
   kb_o_font="{courier 7 normal}";
   kb_o_value=str($2);
  }
  if ($1 == 16) {
   kb_p_font="{courier 7 normal}";
   kb_p_value=str($2);
  }
  if ($1 == 17) {
   kb_q_font="{courier 7 normal}";
   kb_q_value=str($2);
  }
  if ($1 == 18) {
   kb_r_font="{courier 7 normal}";
   kb_r_value=str($2);
  }
  if ($1 == 19) {
   kb_s_font="{courier 7 normal}";
   kb_s_value=str($2);
  }
  if ($1 == 20) {
   kb_t_font="{courier 7 normal}";
   kb_t_value=str($2);
  }
  if ($1 == 21) {
   kb_u_font="{courier 7 normal}";
   kb_u_value=str($2);
  }
  if ($1 == 22) {
   kb_v_font="{courier 7 normal}";
   kb_v_value=str($2);
  }
  if ($1 == 23) {
   kb_w_font="{courier 7 normal}";
   kb_w_value=str($2);
  }
  if ($1 == 24) {
   kb_x_font="{courier 7 normal}";
   kb_x_value=str($2);
  }
  if ($1 == 25) {
   kb_y_font="{courier 7 normal}";
   kb_y_value=str($2);
  }
  if ($1 == 26) {
   kb_z_font="{courier 7 normal}";
   kb_z_value=str($2);
  }
}


/* return the distance between the live user model and a specified sample model */
func getDistance {
 para userid, tempuserave, tempdatagrid, distmeasure;

 
 /* only calculate distance once the live user model contains at least 2 key presses */
 if (totalkeystrokes < 2) {
  return "-";
 }
 
 /* build model and sample vectors that only contain common > 0 features */
 samplevector = tempuserave;
 modelvector = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
 for (j = 2; j <= 27; j++) {
   modelvector[j-1] = tempdatagrid[userid][j];
 }
 n = 0;
 for (i = 1; i <= 26; i++) {
  if (samplevector[i] > 0 && modelvector[i] > 0) {
    n = n + 1;
  }
  else {
   samplevector[i] = 0;
   modelvector[i] = 0;
  }
 }
 
 if (n > 0) {  
 rankedmodelvector = rankVector(modelvector);
 rankedsamplevector = rankVector(samplevector);  
 
 
  ##calculate degree of disorder
  if (distmeasure == 1) {
  sumofdiffs = 0;
  for (i = 1; i <= 26; i++) {
   if (rankedsamplevector[i] > 0 && rankedmodelvector[i] > 0) {
    diff = abs(rankedsamplevector[i] - rankedmodelvector[i]);
	sumofdiffs = sumofdiffs + diff;
   }
  }
   max_disorder = 0;
		if (n % 2 == 0) {
            max_disorder = (n * n) / 2;
        } else {
            max_disorder = ((n * n) - 1) / 2;
        }

    distance = sumofdiffs / max_disorder;
   }
   
 ##calculate euclidean distance
  if (distmeasure == 2) {
  sumofdiffssquared = 0;
  for (i = 1; i <= 26; i++) {
   if (rankedsamplevector[i] > 0 && rankedmodelvector[i] > 0) {
    diff = rankedsamplevector[i] - rankedmodelvector[i];
	diff_squared = diff * diff;
	sumofdiffssquared = sumofdiffssquared + diff_squared;
   }
  }
    distance = sqrt(sumofdiffssquared) / n;
   }
  }
  else {
    distance = "-";
  }
  return distance;
}


/* calculate the height/position of a bar in the user model graph */
func getBarHeight {
 para charid, tempuserave, tempmaxvalue;
 ##possible y position is 160-290
 
 if (totalkeystrokes < 1) {
  return 290;
 }
 
 cur_val = float(tempuserave[charid]);
 if (cur_val == 0) {
  return 290;
 }
 ratio = float(cur_val) / float(tempmaxvalue);
 diff = ratio * (290.0 - 160.0);
 return (290 - round(diff));
}


/* get the ranked version of the userave vector */
func rankUserAve {
  tempvector = userave;
  rankedgrid = rankVector(tempvector);  
 return rankedgrid;
}


/* rank a vector of keystroke features */
func rankVector {
 para v1;
 rankedgrid = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
 /* count the number of elements equal to or less than the value of the
 current element - this allows multiple elements to have equal ranks */
 for (j = 1; j <= 26; j++) {
   curval = v1[j];
   lessthancount = 0;
   equalcount = -1; /* -1 so does not count when equal to self */
   for (k = 1; k <= 26; k++) {
    if (v1[k] == curval) {
	  equalcount++;
	}
    if (v1[k] < curval && v1[k] > 0) {
	  lessthancount++;
	} 
   }
   /* do not rank features that have no measurements */
   if (curval > 0) {
    rankedgrid[j] = mround(lessthancount + 1 + (0.5 * equalcount),0.1);
   }
   else {
    rankedgrid[j] = 0;
   }
 }
 return rankedgrid;
}

 /* determine the colour of a grid square based on the ratio of how 
 far its value is from the minimum and maximum values */
 func getColour {
    para ratio;
	if (ratio >= 0) {
	 red = round((mincolour[1] * (1 - ratio)) + (maxcolour[1] * ratio));
	 green = round((mincolour[2] * (1 - ratio)) + (maxcolour[2] * ratio));
	 blue = round((mincolour[3] * (1 - ratio)) + (maxcolour[3] * ratio));
	 if (red > 255) red = 255;
     if (green > 255) green = 255;
     if (blue > 255) blue = 255;
	 return rgb2colour(red,green,blue);
	}
	return rgb2colour(0,0,0);
 }

/* rank the keystroke features in each row of the data grid */
func rankGrid {
 para tempdatagrid;
 tempgrid = tempdatagrid;
 for (i = 1; i <= 10; i++) {
  tempvector = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
  for (j = 2; j <= 27; j++) {
   tempvector[j-1] = tempdatagrid[i][j];
  }
  rankedvector = rankVector(tempvector); 
  for (j = 2; j <= 27; j++) {
   tempgrid[i][j]=rankedvector[j-1];
  }
 }
 return tempgrid;
}

/* retrieve a value from the data grid corresponding to a specific row and character */
func getValue {
 para userid, charid,globalcheck;
 if (globalcheck == 1) {
   return str(round(rankeddatagrid[userid][charid+1]));
 }
 return str(round(datagrid[userid][charid+1]));
}

/* determine the background colour of a cell in the datagrid */
func getbgColour {
 para userid, charid, mincol, maxcol, globalcheck;
 minimum = 99999;
 maximum = 0;
 if (globalcheck == 1) {
  chosengrid = rankeddatagrid;
 }
 else {
  chosengrid = datagrid;
 }
 
 val = chosengrid[userid][charid+1];
 if (val > 0) {
 if (globalcheck <= 2) {
 for (i=2; i <= 27; i = i + 1) {
  if (chosengrid[userid][i] < minimum && chosengrid[userid][i] > 0) {
   minimum = chosengrid[userid][i];
  }
  if (chosengrid[userid][i] > maximum) {
   maximum = chosengrid[userid][i];
  }
 }
 }
 else {
 for (j=1; j <= 10; j = j + 1) {
  for (i=2; i <= 27; i = i + 1) {
  if (datagrid[j][i] < minimum && datagrid[j][i] > 0) {
   minimum = chosengrid[j][i];
  }
  if (datagrid[j][i] > maximum) {
   maximum = chosengrid[j][i];
  }
 }
 }
 }
 ratio = 1 - ((float(maximum) - float(val)) / (float(maximum) - float(minimum)));
	 red = round((mincol[1] * (1 - ratio)) + (maxcol[1] * ratio));
	 green = round((mincol[2] * (1 - ratio)) + (maxcol[2] * ratio));
	 blue = round((mincol[3] * (1 - ratio)) + (maxcol[3] * ratio));
	 if (red > 255) red = 255;
     if (green > 255) green = 255;
     if (blue > 255) blue = 255;
	 return rgb2colour(red,green,blue);
 }
 return "#ccc";
}

/* determine the background colour of a cell in the live user model row of the datagrid */
func getuserbgColour {
 para charid, mincol, maxcol, globalcheck, misc;
 minimum = 99999;
 maximum = 0;
 if (globalcheck == 1) {
  chosengrid = rankeduserave;
 }
 else {
  chosengrid = userave;
 }
 
 val = chosengrid[charid];
 if (val > 0) {
 if (globalcheck <= 2) {
 for (i=1; i <= 26; i = i + 1) {
  if (chosengrid[i] < minimum && chosengrid[i] > 0) {
   minimum = chosengrid[i];
  }
  if (chosengrid[i] > maximum) {
   maximum = chosengrid[i];
  }
 }
 diff = float(maximum) - float(minimum);
 if (diff > 0) {
  ratio = 1 - ((float(maximum) - float(val)) / diff);
 }
 else {
  ratio = 1;
 }
	 red = round((mincol[1] * (1 - ratio)) + (maxcol[1] * ratio));
	 green = round((mincol[2] * (1 - ratio)) + (maxcol[2] * ratio));
	 blue = round((mincol[3] * (1 - ratio)) + (maxcol[3] * ratio));
	 if (red > 255) red = 255;
     if (green > 255) green = 255;
     if (blue > 255) blue = 255;
	 return rgb2colour(red,green,blue);

 }
 }
 return "#ccc";
}