//////////////////////////////////////////////////////////////////////////////////////////////////////
//
//                           User Controlled Button on NXT Brick -- Advanced Example.
//
// This is an advanced programmaing example showing user program control of the four buttons on the
// NXT. A user program can take over complete control of the buttons.
//
// THis example shows how you can use the buttons to configure options in a user program. There are
// three levels of screen displays in this example:
//
// 1. Top Level
//    Screen is controlled by user program without button access. Or the default NXT screen format
//    can be used
//
// 2. Parameter Selection Level
//    In this screen the user uses the left and right buttons to select from one of four configurable
//    options.
//
// 3. Modify single option
//    In this screen the left and right buttons are used to modify the value of a parameter.
//
// The program switches between the three levels using the 'Enter' and 'Exit' buttons. Each button moves
// up or down a screen. When in the top level, the 'Exit' button will terminate the program as per
// "standard" practice.
//
// By default the NXT firmware has standard processing for the four buttons on the NXT. This
// program illustrates how the standard processing can be over-ridden and user defined actions
// can be processed within user's Robotc program.
//
//                                     *  ********  *
//                                   * *  *      *  * *
//                                 * L *  * Enter*  * R *
//                                   * *  *      *  * *
//                                     *  ********  *
//
//                                        ********
//                                        * Exit *
//                                        ********
//
// The four buttons are shown in the above graphic: Top line is left, enter, right buttons
//                                                  second line is 'exit' button.
//
// There are two approaches to perform button management in a user program.
//  1. The user program can continuously poll the button states and look for changes.
//  2. The user program can be notified whenever a button is pushed. The notification is by
//     starting a user specified task running.
//  For both methods, you have to set some variables to override the default system button
//  handler.
//
//  There are three intrinsic variables defined for NXT button management.
//
//  nNxtButtonPressed   This read-only variable contains the currently pressed button. [The NXT
//                      firmware only supports recognition of one button at a time]. The value
//                      is one of the following:
//                           typedef enum
//                           {
//                             kNoButton      = 0xFF,
//                             kExitButton    = 0,
//                             kRightButton   = 1,
//                             kLeftButton    = 2,
//                             kEnterButton   = 3
//                           } TButtonMasks;
//
//  nNxtButtonTask  This variable contains the task number that should be started whenever any NXT
//                   button is pushed. There are three value ranges:
//                       -1       Indicates that the normal system processing for exit button should
//                                be used. Anything else will over-ride the system actions. Note if
//                                overwritten, you will not be able to stop program running with button
//                                control.
//                      1..9      Indicates that the indicated task (1..9) should be started whenever
//                                the exit button is pressed. Task will only be started once per
//                                keypress.
//                      Other			Standard processing is overwritten. No task will be started when exit
//                                button is pushed; user program must used nNxtButtons to check for
//
//  nNxtExitClicks  Contains the number of 'clicks' that must be received to abort a running program.
//                  The default value is one. But if you want to use the EXIT button in your program
//                  you can set this to 2 (for a double click) or 3 (triple click) or higher so that
//                  there is always an escape sequence, independent of your user program, for existing
//                  your program.
//
//                  This variable is reset to 1 before the start of any program.
//
//                  If you really want to override the 'abort' check, you can continually modify this
//                  variable. The count of current EXIT clicks is reset whenever this variable is
//                  written.
//
//
// It may be easier to use the method of starting a task whenever a button is pressed. The user program
// can look at the 'nNxtButtonPressed' variable to determine which button has been pressed.
//
// How to Terminate/Abort A Program when Buttons are Hijacked
// ==========================================================
// When user program has gained control of the NXT buttons, the regular processing of the buttons is stopped.
// This means the EXIT button will not be recognized as a command to stop the end user program. So there
// must be an escape sequence to allow for this. Several different schemes were tried and the following
// was finally adopted for RobotC.
//
// A program is aborted by counting the number of "clicks" on the EXIT button. The normal default case is
// that a single click will abort a program. Setting this to a higher value (double or tripe clicks) will
// still allow user program to use EXIT button, but firmware will still have the capability to abort
// program
//
//
// Notes on NXT Hardware:
// ======================
//  1. Buttons on the NXT are not individually read. Instead there is are several resistors in series that
//     are shorted by pressing the individual keys. This is fed into a single input pin where an A-to-D
//     conversion is performed. Analyzing the result indicates which key is pressed.
//  2. A byproduct of this is that the NXT can only properly handle a single keypress at a time. If
//     multiple keys are pressed, then the firmware recognizes the key that generates the lowest resistance.
//     Only this key is reported to the user program.
//  3. A byproduct is that you cannot design a user interface that tries to use two keys depressed at the
//     same time.
//  4. The firmware recognizes button depresses in the following order(low to high): left, right, enter, exit.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

//
// This sample program will show how four diferent values can be adjusted via NXT buttons.
//

//
// For demo purposes, I've simply labeled the values being modified as 'parmN'. In a real program you may
// want to substiture more descriptive names.
//
typedef enum
{
	parm0,
	parm1,
	parm2,
	parm3
} TParmNames;

const int kNumbOfParms = 4;

//
// Two arrays of constants to define the min and max values allowed.
//
int kMinValues[kNumbOfParms] = {0, 0, 0, 0};
int kMaxValues[kNumbOfParms] = {15, 10, 10, 12};

//
// Array to hold the parameter values. On startup, it is initialized to the default values. Fot this
// example, arbitrary values have been chosen.
//
int nParmValue[kNumbOfParms]= {3, 5, 6, 8};

//
// 'enum' to define the three screen levels.
//
typedef enum
{
	nProgramGraphics = 0,     // Used for program to display its normal graphics on the LCD
	nSelectParm,              // LCD contains a selection menu for the parm to be adjusted
	nAdjustParm               // Used to adjust the value for a single parm
} TMenuLevel;

TMenuLevel nMenuLevel;      // Keeps track of the current screen level

TParmNames nCurrItem;       // the index of the parameter being modified

///////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Procedure Prototype Declarations
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

void drawProgramGraphics();
void drawSelectionMenu();
void drawCurrItemValue();
void refreshMenuLevelDisplay();

void adjustSelectionMenu(const bool bIncrement);
void adjustItemValue(const bool bIncrement);
void incrementMenuLevel();

void ErrorBeep()
{
	PlaySound(soundBeepBeep);
}


void keyClick()
{
	PlaySoundFile("! Click.rso");
}


///////////////////////////////////////////////////////////////////////////////////////////////////////
//
//                                    refreshMenuLevelDisplay()
//
// Redraw the NXT LCD based on the current 'nMenuLevel'
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

void refreshMenuLevelDisplay()
{
  keyClick();
	switch (nMenuLevel)
	{
		case nProgramGraphics:    eraseDisplay(); drawProgramGraphics(); break;
		case nSelectParm:         eraseDisplay(); drawSelectionMenu();   break;
		case nAdjustParm:                         drawCurrItemValue();   break;
	}
	return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
//
//                                    drawProgramGraphics()
//
// Draws the 'normal" graphics on the LCD. i.e. the LCD contains the graphics content when a parameter
// selection modification is not in progress.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

void drawProgramGraphics()
{
	const bool bUseNxtDefaultScreen = true; // Should use 'default' screen or 'user' programmed

	if (bUseNxtDefaultScreen)
	{
		//
		// Restore to the system default display. "Running" icon, status on top line, etc.
		//
		nViewStateNXT = scrnDefault;
	}
	else
  {
		//
		// User customized state. This is just an example
		//
  	nxtDisplayTextLine(2, " Hit 'Enter' to ");
    nxtDisplayTextLine(3, "  adjust parms  ");
  }
  return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
//
//                                    drawSelectionMenu()
//
// Draws the menu currently being selected. There's one item per text line. Column 0 contains a '>'
// for one of the lines to indicate that it is the current selection
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

void drawSelectionMenu()
{
	//
	// String support in RobotC is currently a little cumbersome so we have to do the following in
	// 'longhand'. Future enhancement would allow for a 'for' loop and use of string constants.
	//
  nxtDisplayTextLine(2, "  Parm 0 Descrip");
  nxtDisplayTextLine(3, "  Parm 1 Descrip");
  nxtDisplayTextLine(4, "  Parm 2 Descrip");
  nxtDisplayTextLine(5, "  Parm 3 Descrip");

	//
	// Highlight the selected item
	//
	switch (nCurrItem)
	{
		case parm0:        nxtDisplayString(2, ">");   break;
		case parm1:        nxtDisplayString(3, ">");   break;
		case parm2:        nxtDisplayString(4, ">");   break;
		case parm3:        nxtDisplayString(5, ">");   break;
	}


	drawCurrItemValue();
	return;
}

void adjustSelectionMenu(const bool bIncrement)
{
	if (bIncrement)
	{
		if (nCurrItem >= parm3)
		{
			ErrorBeep();
			return;
		}
		++nCurrItem;
	}
	else
	{
		if (nCurrItem <= parm0)
		{
			ErrorBeep();
			return;
		}
		--nCurrItem;
	}
	keyClick();
	drawSelectionMenu();
	return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
//
//                                    drawCurrItemValue()
//
//
// Displays the value of the current selection on the screen
//
// NOTE: this doesn't have to be a numeric value. "string" values (e.g. 'easy', 'hard')
//       could also be displayed -- Just do a 'switch' on the 'nCurrItem' and then have
//       separate functions or switch statement for each 'parm type' that would display
//       the string.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

void drawCurrItemValue()
{
	if (nMenuLevel != nAdjustParm)
	  nxtDisplayTextLine(7, "   %d", nParmValue[nCurrItem]);
	else
	  nxtDisplayTextLine(7, ">>>%d", nParmValue[nCurrItem]);
	return;
}


void adjustItemValue(const bool bIncrement)
{
	if (bIncrement)
	{
		if (nParmValue[nCurrItem] >= kMaxValues[nCurrItem])
		{
			ErrorBeep();
			return;
		}
		++nParmValue[nCurrItem];
	}
	else
	{
		if (nParmValue[nCurrItem] <= kMinValues[nCurrItem])
		{
			ErrorBeep();
			return;
		}
		--nParmValue[nCurrItem];
	}
	keyClick();
	drawSelectionMenu();
	return;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////
//
//                                    LeftRightEnterButtonTask()
//
// Process left and right keys. Used to increment or decrement item selection or item values.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

task ButtonTask()
{
  //
  // Set task to highest priority so that it will run to completion. This will minimze the small
  // chance of task execution beeing delayed until after button is released; if this happens, then
  // the keypress will be lost.
  //
  nSchedulePriority	 = kHighPriority;

  //
  // Update count of times each button is pushed. If using this program as a template, replace these
  // statements with your own custom action.
  //
  switch (nNxtButtonPressed)
  {
  	case kExitButton:
		  //
		  // Set task to highest priority so that it will run to completion
		  //
		  nSchedulePriority	 = kHighPriority;

		  if (nMenuLevel == nProgramGraphics)
		  {
		  	//
		  	// Top level menu. Pushing 'exit' button means stop the program
		  	//
		  	keyClick();
		  	wait1Msec(100); // For key click to finish
		  	StopAllTasks();
		  }

		  //
		  // We must be modifying a parameter value. Back up to selection of parameters
		  //
		  --nMenuLevel;
		  refreshMenuLevelDisplay();
			break;

  	case kLeftButton:
		  switch (nMenuLevel)
			{
				case nSelectParm:      adjustSelectionMenu(false); break;
				case nAdjustParm:      adjustItemValue(false);     break;
			}
			break;

		case kRightButton:
		  switch (nMenuLevel)
			{
				case nProgramGraphics:                             break;
				case nSelectParm:      adjustSelectionMenu(true);  break;
				case nAdjustParm:      adjustItemValue(true);      break;
			}
			break;

		case kEnterButton:
	    incrementMenuLevel();
	}


	//
	// The enter button is used to switch to "adjust value" mode
	//
	// An enhancement might be to store the adjusted values in a temporary
	// holding register and only update the value when enter key is pressed. But this is just a simple
	// example and the parm values are updated immediately.
	//
	return;
}

void incrementMenuLevel()
{
	if (nMenuLevel >= nAdjustParm)
	{
		ErrorBeep();
		return;
	}
	++nMenuLevel;
	refreshMenuLevelDisplay();
	return;
}



///////////////////////////////////////////////////////////////////////////////////////////////////////
//
//                                    main Task
//
// Process left and right keys. Used to increment or decrement item selection or item values.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

task main()
{
	//
	// "Hi-jack" buttons for user program control.
	//
	// Note: program cannot be terminated if we hijack the 'exit' button. So there has to be an escape sequence
	//       that will return buttons to system control!
	//
	nNxtExitClicks    = 20;
	nNxtButtonTask    = ButtonTask;
	nMenuLevel        = nProgramGraphics;
	nCurrItem         = parm0;
	drawProgramGraphics();
	//
	// Do nothing. Just keep waiting
	//
	while (true)
	{
		//
		// Some dummy code to do some work.
		//
	  wait1Msec(50);
  }
	return;
}