/*
 * Configurable ps-like program.
 * Routines to sleep while reading commands while ips is in loop mode.
 *
 * Copyright (c) 2014 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 */

#include "ips.h"


/*
 * Some state for numeric argument parsing.
 */
static	double	numberValue;
static	double	scaleValue;
static	BOOL	isNumberSeen;
static	BOOL	isPeriodSeen;


/*
 * Local routines.
 */
static	void	ClearParsedNumber(void);
static	double	GetParsedNumber(double);


/*
 * Sleep for the required amount of time while handling any commands.
 * Returns only when the sleep time is used up or a command was read
 * which wants us to start the next loop immediately.
 */
void
WaitForCommands(long milliSeconds)
{
	long		remainingMilliSeconds;
	long		elapsedMilliSeconds;
	struct	timeval	beginTime;

	/*
	 * If there is no sleep then just collect any keyboard events
	 * that are ready right now and execute commands for them.
	 */
	if (milliSeconds <= 0)
	{
		DpyEventWait(0);
		ReadCommands();

		return;
	}

	/*
	 * We want to sleep.  Get the beginning time so we can tell
	 * when our sleep has finished.
	 */
	GetTimeOfDay(&beginTime);

	remainingMilliSeconds = milliSeconds;

	/*
	 * Loop until either the remaining time has elapsed, the display
	 * needs redrawing, or until a  command was executed which wants
	 * the sleep to terminate early.
	 */
	do
	{
		/*
		 * Handle any events for the display device while sleeping
		 * for the remaining amount of time.  If the routine says
		 * that an update is needed now, then arrange to leave
		 * the loop (after checking for commands).
		 */
		if (DpyEventWait(remainingMilliSeconds))
			milliSeconds = 0;

		/*
		 * Handle any commands that may have been typed while we
		 * were sleeping.  If the command is one which wants us to
		 * return early, then do so.
		 */
		if (ReadCommands())
			break;

		/*
		 * No command that needed to terminate the sleep was read.
		 * Get the elapsed time since the beginning time so we can
		 * tell how long we slept for.
		 */
		elapsedMilliSeconds = ElapsedMilliSeconds(&beginTime, NULL);

		/*
		 * If the time went negative (e.g., was reset) then leave.
		 * The time should behave better on the next call.
		 */
		if (elapsedMilliSeconds < 0)
			break;

		/*
		 * Calculate the amount of time left to sleep.
		 */
		remainingMilliSeconds = milliSeconds - elapsedMilliSeconds;
	}
	while (remainingMilliSeconds > 0);
}


/*
 * Read any commands that the user may have typed while we were sleeping.
 * Reading is done in character mode so that multi-character commands
 * might not be complete in one call.  Also, reading is non-blocking.
 * Returns TRUE if the command is one which requires our sleep to terminate.
 */
BOOL
ReadCommands(void)
{
	BOOL	isWakeup;
	BOOL	isClear;
	BOOL	isError;
	int	ch;

	isWakeup = FALSE;
	isClear = FALSE;
	isError = FALSE;

	/*
	 * Loop reading commands until there are no more characters.
	 */
	while (DpyInputReady())
	{
		ch = DpyReadChar();

		if (ch == EOF)
			break;

		switch (ch)
		{
			case 'q':
			case '\033':
				/*
				 * Quit from the program.
				 */
				isRunning = FALSE;
				isWakeup = TRUE;
				isClear = TRUE;

				break;

			case '\r':
			case '\n':
			case ' ':
				/*
				 * Do an immediate update.
				 */
				isWakeup = TRUE;
				isUpdateForced = TRUE;
				isClear = TRUE;

				break;

			case 'a':
				/*
				 * Set autoscroll time.
				 */
				scrollSeconds = (int)
					GetParsedNumber(DEFAULT_SCROLL_SEC);

				if (isInfoShown)
					isWakeup = TRUE;

				break;

			case 't':
				/*
				 * Move to top page.
				 */
				TopPage();
				isWakeup = TRUE;
				isClear = TRUE;

				break;

			case 'h':
				/*
				 * Turn on or off the header line.
				 */
				noHeader = (GetParsedNumber(noHeader) == 0);
				isWakeup = TRUE;

				break;

			case 'f':
				/*
				 * Freeze or unfreeze the display.
				 */
				isFrozen = (GetParsedNumber(!isFrozen) != 0);
				ResetScrollTime();
				isWakeup = TRUE;

				break;

			case 'i':
				/*
				 * Turn on or off the info line.
				 */
				isInfoShown = (GetParsedNumber(!isInfoShown) != 0);
				isWakeup = TRUE;

				break;

			case 'b':
				/*
				 * Move to bottom page.
				 */
				BottomPage();
				isWakeup = TRUE;
				isClear = TRUE;

				break;

			case 'M':
				/*
				 * Set the myprocs option.
				 */
				myProcs = (GetParsedNumber(!myProcs) != 0);
				isWakeup = TRUE;
				TopPage();

				break;

			case 'n':
				/*
				 * Move to next page of data.
				 */
				NextPage();
				isWakeup = TRUE;
				isClear = TRUE;

				break;

			case 'o':
				/*
				 * Set overlap lines between screens.
				 */
				overlapLines = (int)
					GetParsedNumber(DEFAULT_OVERLAP_LINES);

				isWakeup = TRUE;

				break;

			case 'p':
				/*
				 * Move to previous page of data.
				 */
				PreviousPage();
				isWakeup = TRUE;
				isClear = TRUE;

				break;

			case 's':
				/*
				 * Set sleep time.
				 */
				sleepTimeMs = (int) (1000.0 *
					GetParsedNumber(DEFAULT_SLEEP_SEC));

				isWakeup = TRUE;

				break;

			case 'S':
				/*
				 * Set the no self option.
				 */
				noSelf = (GetParsedNumber(!noSelf) != 0);
				isWakeup = TRUE;
				TopPage();

				break;

			case '\177':
			case '\b':
				/*
				 * Clear the numeric argument.
				 */
				isClear = TRUE;

				break;

			case 'r':
				/*
				 * Refresh the screen.
				 */
				isRefreshNeeded = TRUE;
				isWakeup = TRUE;
				isClear = TRUE;

				break;

			case 'R':
				/*
				 * Set the no root option.
				 */
				noRoot = (GetParsedNumber(!noRoot) != 0);
				isWakeup = TRUE;
				TopPage();

				break;

			case '.':
				/*
				 * Parse part of a floating point argument.
				 */
				if (isPeriodSeen)
					isError = TRUE;

				isPeriodSeen = TRUE;

				if (!isNumberSeen)
				{
					isNumberSeen = TRUE;
					numberValue = 0.0;
					scaleValue = 1.0;
				}

				break;

			default:
				/*
				 * If the character isn't a digit then it is
				 * an error.
				 */
				if ((ch < '0') || (ch > '9'))
				{
					isError = TRUE;
					break;
				}

				/*
				 * Parse part of a floating point value.
				 */
				if (!isNumberSeen)
				{
					isNumberSeen = TRUE;
					numberValue = 0.0;
					scaleValue = 1.0;
				}

				numberValue = numberValue * 10.0 + (ch - '0');

				if (isPeriodSeen)
					scaleValue *= 10.0;

				break;
		}
	}

	/*
	 * If there was a command error then ring the bell.
	 */
	if (isError)
		DpyRingBell();

	/*
	 * Clear any parsed number if we need to.
	 */
	if (isError || isClear)
		ClearParsedNumber();

	/*
	 * Return indication of whether we need to wake up.
	 */
	return isWakeup;
}


/*
 * Get the numeric value (if any) that had previously been parsed and saved,
 * defaulting its value to the specifed one if it hadn't been entered,
 * and clearing the saved value so that a new number can be entered for
 * the next command.
 */
static double
GetParsedNumber(double value)
{
	if (isNumberSeen)
		value = numberValue / scaleValue;

	ClearParsedNumber();

	return value;	
}


/*
 * Clear any parsed number.
 */
static void
ClearParsedNumber(void)
{
	numberValue = 0;
	scaleValue = 0;
	isNumberSeen = FALSE;
	isPeriodSeen = FALSE;
}


/* END CODE */
