// Fusion Research Driver - Common Module - Keyboard Support
//
// Copyright 2021 Fusion Research, Inc.
// by Alexa Jade Fanti
//
// Comments:
//   This file supporting functions and classes for keyboard input.
//
// Depends: 
//   fusion_debugging.js
//   fusion_stringutils.js
//
//System.Print("Fusion Driver - Common Module - Keyboard Support loaded...\r\n");


/******************************************************************
 * START Keyboard Global Variables and Functions
 ******************************************************************/
// We need a global array of keyboard objects that are associated with views.
var g_KEYBOARD_ARRAY = new Array();

// We can get a keyboard for a view at any time?
function  getKeyboard_fromViewID(viewID)
{
	if (viewID === undefined) {
		DebugKeyboard("getKeyboard_fromViewID parameter 'viewID' was undefined. There can be NO keyboard!", g_DEBUG_LEVEL_CRITICAL);
		return; // 
	}

	DebugKeyboard("We need a Keyboard for RTI System View '" + viewID + "'", g_DEBUG_LEVEL_TRACE);
	for (var x = 0; x < g_KEYBOARD_ARRAY.length; x++) 
	{
		if (g_KEYBOARD_ARRAY[x]._viewID == viewID) {
			DebugGeneral("Returning Keyboard for RTI System View '" + viewID + "'", g_DEBUG_LEVEL_TRACE);
			return g_KEYBOARD_ARRAY[x];
		}
	}

	// If we get here we didn't have a browser for the view so we should create one!
	DebugKeyboard("Creating Keyboard for RTI System View '" + viewID + "'", g_DEBUG_LEVEL_TRACE);
	var keyboard = new Keyboard(viewID);
	g_KEYBOARD_ARRAY.push(keyboard);
	return keyboard;
}

function  getKeyboard_fromViewID_andOpen(viewID, defaultBuffer, functionCallSuccess, functionCallFailure)
{
	var keyboard = getKeyboard_fromViewID(viewID);
	if (keyboard !== undefined) keyboard.Open(defaultBuffer, functionCallSuccess, functionCallFailure);
}

// A functionCall is a string that looks like an actual line of code in javascript.
// Example: 
//   functionCallSuccess = 'Library_OnSearchComplete(' + this._viewID + ', "' + entry.path + '", "$1")'
//   Where this._viewID = 1 and entry.path = '/path'
//   This yeilds: Library_OnSearchComplete(1, "/path", "$1")
//   We can later replace $1 with something else (like the search result)
/******************************************************************
 * END   Keyboard Global Variables and Functions
 ******************************************************************/


/******************************************************************
 * START Keyboard Global Timer
 ******************************************************************/
// If we want to simulate a blinking cursor, then we have to set a timer and 
// track the state of the cursor so we know whether to turn it on or off...
var g_KEYBOARD_TIMER = new Timer();

function KeyboardTimer_Start()
{
	var blink = ReadConfig_AsBool("KeyboardCursorBlink", false);
	if (blink) g_KEYBOARD_TIMER.Start(OnKeyboardTimer, 500);
}

function KeyboardTimer_Stop()
{
	g_KEYBOARD_TIMER.Stop();
}

function OnKeyboardTimer()
{
	for (var x = 0; x < g_KEYBOARD_ARRAY.length; x++) {
		var keyboard = g_KEYBOARD_ARRAY[x];
		keyboard.OnTimer();
	}
	var blink = ReadConfig_AsBool("KeyboardCursorBlink", false);
	if (blink) g_KEYBOARD_TIMER.Start(OnKeyboardTimer, 500);
}
/******************************************************************
 * START Keyboard Global Timer
 ******************************************************************/


/******************************************************************
 * START Keyboard System Functions
 ******************************************************************/
// Called by the UI whenever the user presses a key (only the ones we allowed through the GUI)
function SysFunc_Keyboard_Keypress(key, view)
{
	var keyboard = getKeyboard_fromViewID(view);
	if (keyboard === undefined) {
		Debug_SysFuncs("SysFunc_Keyboard_Keypress : Can't perform press of '" + key + "' because the view is bad ('" + view + "')", g_DEBUG_LEVEL_ERROR);
		return;
	}
	keyboard.Keypress(key);
}

// Called by the UI whenever the user presses a key (only the ones we allowed through the GUI)
function SysFunc_Keyboard_KeypressEx(key, view)
{
	var keyboard = getKeyboard_fromViewID(view);
	if (keyboard === undefined) {
		Debug_SysFuncs("SysFunc_Keyboard_Keypress : Can't perform press of '" + key + "' because the view is bad ('" + view + "')", g_DEBUG_LEVEL_ERROR);
		return;
	}
	keyboard.KeypressEx(key);
}
/******************************************************************
 * END   Keyboard System Functions
 ******************************************************************/


/******************************************************************
 * START Keyboard Object
 ******************************************************************/
function Keyboard(viewID)

{
	this.Initialize();
	this._viewID = viewID;
}

Keyboard.prototype.Initialize = function()
{
	this._buffer = ""; // What the user has typed so far...

	// So we can do something when the keyboard is closed (enter) we need 
	// to have a "function to call" (a string to 'eval')  on successful close.
	// The user passes this in on the open and we store it here for the close!
	this._function_success = ""; // The function we will call on Enter
	this._function_failure = ""; // The function we will call on Cancel

	// This is the "shift state" of the keyboard.
	this._shift_state = 0; // 0 = NOT shifted, 1 = shifted ONCE, 2 = shift LOCK!

	// The cursor we are to use and whether to "blink" it for the user.
	this._cursor       = ReadConfig_AsString("KeyboardCursor", "");
	this._cursor_blink = ReadConfig_AsBool("KeyboardCursorBlink", false);

	// If we want to simulate a blinking cursor, then we have to set a timer and 
	// track the state of the cursor so we know whether to turn it on or off...
	this._timer = new Timer();

	// We have to keep track of whether to display the cursor on a "blink".
	this._cursor_isblinked = false;
}

Keyboard.prototype.getID = function()
{
	return "Keyboard (" + this._viewID + ")";
}

Keyboard.prototype.Open = function(defaultBuffer, functionCallSuccess, functionCallFailure)
{
	this.Initialize();
	if ((defaultBuffer !== undefined) && (defaultBuffer != null)) this._buffer = defaultBuffer;
	if ((functionCallSuccess !== undefined) && (functionCallSuccess != null)) this._function_success = functionCallSuccess;
	if ((functionCallFailure !== undefined) && (functionCallFailure != null)) this._function_failure = functionCallFailure;

	// Display our buffer and keyboard
	this.NotifyBuffer();
	SysVar_WriteBool("SysVar_Keyboard_B_Display%" + this._viewID, true);
	UIVisibility_Set(this._viewID, 1);
	DebugKeyboard(this.getID() + " Opened...", g_DEBUG_LEVEL_INFO);
}

// Called by the UI whenever the user presses a key (only the ones we allowed through the GUI)
Keyboard.prototype.Keypress = function(key)
{
	DebugKeyboard(this.getID() + " keypress = " + key, g_DEBUG_LEVEL_TRACE);
	// Handle the "key" they want to press
	switch(key.toLowerCase()) {
		case 'space'  : this._buffer += " ";   break; // The "space bar" key.
		case 'clear'  : this._buffer =  "";    break; // Clear what they've typed so far.
		case 'enter'  : this.Close(true);      break; // Enter what they've typed and close the keyboard.
		case 'cancel' : this.Close(false);     break; // Cancel what they've typed and close the keyboard.

		case 'backspace' : // They want to remove a letter from the end of the keyboard
			if (this._buffer.length > 0) this._buffer = this._buffer.substring(0, this._buffer.length -1);
			break;

		case 'shift' : // They want to toggle shift states (none -> shift -> locked -> none)
			switch (this._shift_state) {
				case 0 : this._shift_state = 1; break;
				case 1 : this._shift_state = 2; break;
				case 2 : this._shift_state = 0; break;
				default : this._shift_state = 0; break;
			}
			break;
	
		default: // They typed a letter (like 0-9 or A-Z)
			if (this._shift_state > 0) this._buffer += key.toUpperCase();
				else this._buffer += key.toLowerCase();
			break;
	}

	// If they didn't change the shift state and we're shifted, fall back to not shifted.
	if ((key.toLowerCase() != "shift") && (this._shift_state == 1)) this._shift_state = 0;

	// Tell the GUI about our current shift state and the buffer so it can show the user.
	this.NotifyShift();
	this.NotifyBuffer();
}

// Called by the UI whenever the user presses a key (only the ones we allowed through the GUI)
Keyboard.prototype.KeypressEx = function(key)
{
	DebugKeyboard(this.getID() + " keypress(ex) = " + key, g_DEBUG_LEVEL_TRACE);
	//key = ConvertToUTF8(key);
	//System.Print("KeyboardKeypressEx : " + key + "\r\n");

	// Handle the "key" they want to press
	if (this._shift_state > 0) this._buffer += key.toUpperCase();
		else this._buffer += key.toLowerCase();

	// If they didn't change the shift state and we're shifted, fall back to not shifted.
	if (this._shift_state == 1) this._shift_state = 0;

	// Tell the GUI about our current shift state and the buffer so it can show the user.
	this.NotifyShift();
	this.NotifyBuffer();
}

// This is a utility function to notify the GUI about the shift state of the keyboard.
Keyboard.prototype.NotifyShift = function()
{
	switch (this._shift_state) {
		case 0 :
			SysVar_WriteBool("SysVar_Keyboard_B_Shifted%" + this._viewID, false);
			SysVar_WriteBool("SysVar_Keyboard_B_ShiftLocked%" + this._viewID, false);
			break;
		case 1 :
			SysVar_WriteBool("SysVar_Keyboard_B_Shifted%" + this._viewID, true);
			SysVar_WriteBool("SysVar_Keyboard_B_ShiftLocked%" + this._viewID, false);
			break;
		case 2 :
			SysVar_WriteBool("SysVar_Keyboard_B_Shifted%" + this._viewID, true);
			SysVar_WriteBool("SysVar_Keyboard_B_ShiftLocked%" + this._viewID, true);
			break;
		default :
			SysVar_WriteBool("SysVar_Keyboard_B_Shifted%" + this._viewID, false);
			SysVar_WriteBool("SysVar_Keyboard_B_ShiftLocked%" + this._viewID, false);
			break;
	}
}

// This is a utility function to notify the GUI about buffer contents of the keyboard.
// Note that it accomodates appending a cursor if appropriate (and accounts for blinking)
Keyboard.prototype.NotifyBuffer = function()
{
	var buffer = this._buffer;

	if (this._cursor_blink) {
		if (this._cursor_isblinked) buffer += this._cursor;
		this._cursor_isblinked = !this._cursor_isblinked;
	} else {
		buffer += this._cursor;
	}

	SysVar_Write("SysVar_Keyboard_S_Buffer%" + this._viewID, buffer);
}

// This is used by our timer to re-display the keyboard effectively blinking the cursor.
Keyboard.prototype.OnTimer = function()
{
	//DebugKeyboard(this.getID() + " Timer Triggered", g_DEBUG_LEVEL_TRACE);
	this.NotifyBuffer();
}

// This is called when the user presses "Enter" or "Cancel" on the keyboard.
// The parameter (value) will be true (for Enter) or false (for Cancel).
// This cancels the blink timer, resets the shift state and hides the keyboard.
Keyboard.prototype.Close = function(value)
{
	this._timer.Stop();                      // No longer blink the cursor!
	this._shift_state = 0;                   // No longer shifted
	//KeyboardNotifyShift();                         // This is not strictly necessary since this is called from KeyboardKeypress before it's call to KeyboardNotifyShift.
	SysVar_WriteBool("SysVar_Keyboard_B_Display%" + this._viewID, false); // No longer display the keyboard
	UIVisibility_Set(this._viewID, 0);
	
	var call = ""; // What we're gonna call... 
	if (value) {
		DebugKeyboard(this.getID() + " Closed (returning '" + this._buffer + "'')", g_DEBUG_LEVEL_INFO);
		if ((this._function_success !== undefined) && (this._function_success != null) && (this._function_success != "")) call = this._function_success.replace("$1", this._buffer);
	} else {
		DebugKeyboard(this.getID() + " Cancelled", g_DEBUG_LEVEL_INFO);
		if ((this._function_failure !== undefined) && (this._function_failure != null) && (this._function_failure != "")) call = this._function_failure;
	}

	DebugKeyboard(this.getID() + " CALL '" + call + "'", g_DEBUG_LEVEL_TRACE);

	if (call != "") eval(call);
	this._buffer = ""; // Clear for the future!
}

/******************************************************************
 * END   Keyboard Object
 ******************************************************************/

