/**************************************************************************
* text.c:	Functions for text input/output.
*		Part of MP, the MIDI Playground.
*
* Author:	Daniel Barrett
* Version:	See the file "version.h".
* Copyright:	None!  This program is in the Public Domain.
*		Please share it with others.
***************************************************************************/

	
#include "mp.h"


/* Given the currently read character and the current state, compute
 * the new state and the MIDI value. */
	
STATE_T NewState(int c, STATE_T state, MIDI_VALUE *answer)
{
	static BOOL inString = FALSE;

	if (inString)
		return(DoInString(c, state, answer, &inString));
	else if (c == HELP_SYMBOL)
	{
		InputHelp();
		return(state);
	}
	else if (state == STATE_NORMAL && c == '\"')
	{
		inString = TRUE;
		return(STATE_NORMAL);	/* Doesn't matter. */
	}
	else
		return(NonStringState(c, state, answer));
}

/****************************************************************************
* Our finite automaton for non-strings.  Process the character and return
* the new state.
****************************************************************************/
	
STATE_T NonStringState(int c, STATE_T state, MIDI_VALUE *answer)
{
	switch (state)
	{
		case STATE_NORMAL:	return(DoNormal(c, answer));
		case STATE_INCOMMENT:	return(DoComment(c));
		case STATE_INDECIMAL:	return(DoDecimal(c, answer));
		case STATE_INOCTAL:	return(DoOctal(c, answer));
		case STATE_STARTHEX:	return(DoHex(c, answer));
		case STATE_INHEX:	return(DoInHex(c, answer));
		case STATE_LEADZERO:	return(DoLeadZero(c, answer));
		case STATE_INCHAR:	return(DoInChar(c, answer));
		case STATE_STARTBINARY:	return(DoBinary(c, answer));
		case STATE_INBINARY:	return(DoInBinary(c, answer));
		case STATE_EXPECTQUOTE:	return(DoExpectQuote(c, answer));
		case STATE_BACKSLASHINCHAR:
			return(DoBackslash(c, answer, STATE_EXPECTQUOTE));
		case STATE_SUCCESS:	
		case STATE_OVERFLOW:	
		case STATE_ERROR:	return(state);
	}
}


/****************************************************************************
* We are not currently in a number.  A character is read; decide what to
* do with it.
****************************************************************************/
	
STATE_T DoNormal(int c, MIDI_VALUE *answer)
{
	if (isspace(c))
		return(STATE_NORMAL);
	else if (c == START_COMMENT)
		return(STATE_INCOMMENT);
	else if (c == START_BINARY)
		return(STATE_STARTBINARY);
	else if (c == '\'')
		return(STATE_INCHAR);
	else if (c == '0')
		return(STATE_LEADZERO);
	else if (toupper(c) == 'H')
		return(STATE_STARTHEX);
	else if (isdigit(c))
	{
		*answer = D_TO_INT(c);
		return(STATE_INDECIMAL);
	}
	else if (isxdigit(c))
	{
		*answer = H_TO_INT(c);
		return(STATE_INHEX);
	}
	else
		return(STATE_ERROR);
}

	
/****************************************************************************
* Base 10 (decimal) numbers.
****************************************************************************/
	
STATE_T DoDecimal(int c, MIDI_VALUE *answer)
{
	if (isdigit(c))
		return(IncreaseIfPossible(answer, D_TO_INT(c), 10, 
			STATE_INDECIMAL));
	else if (isspace(c))
		return(STATE_SUCCESS);
	else
		return(STATE_ERROR);
}

	
/****************************************************************************
* Octal numbers.
****************************************************************************/
	
STATE_T DoOctal(int c, MIDI_VALUE *answer)
{
	if (isoctal(c))
		return(IncreaseIfPossible(answer, D_TO_INT(c), 8, 
			STATE_INOCTAL));
	else if (isspace(c))
		return(STATE_SUCCESS);
	else
		return(STATE_ERROR);
}


/****************************************************************************
* Hexadecimal numbers.
****************************************************************************/
	
STATE_T DoHex(int c, MIDI_VALUE *answer)
{
	if (isxdigit(c))
	{
		*answer = H_TO_INT(c);
		return(STATE_INHEX);
	}
	else
		return(STATE_ERROR);
}


STATE_T DoInHex(int c, MIDI_VALUE *answer)
{
	if (isxdigit(c))
		return(IncreaseIfPossible(answer, H_TO_INT(c), 16, 
			STATE_INHEX));
	else if (isspace(c))
		return(STATE_SUCCESS);
	else
		return(STATE_ERROR);
}


/****************************************************************************
* Binary numbers.
****************************************************************************/
	
STATE_T DoBinary(int c, MIDI_VALUE *answer)
{
	if ((c == '0') || (c == '1'))
	{
	    	*answer = D_TO_INT(c);
		return(STATE_INBINARY);
	}
	else
		return(STATE_ERROR);
}

	
STATE_T DoInBinary(int c, MIDI_VALUE *answer)
{
	if ((c == '0') || (c == '1'))
		return(IncreaseIfPossible(answer, D_TO_INT(c), 2, 
			STATE_INBINARY));
	else if (isspace(c))
		return(STATE_SUCCESS);
	else
		return(STATE_ERROR);
}

	
/****************************************************************************
* Hook for negative numbers.  Commented out right now.
****************************************************************************/
	
#ifdef ALLOW_NEGATIVE
STATE_T DoNegative(int c, MIDI_VALUE *answer)
{
	if (c == '0')
		return(STATE_LEADZERO);
	else if (isdigit(c))
	{
		return(STATE_INDECIMAL);
	}
	else
		return(STATE_ERROR);
}
#endif /* ALLOW_NEGATIVE */

	
/****************************************************************************
* Leading zero was found:  Do octal or hexadecimal as required.
****************************************************************************/
	
STATE_T DoLeadZero(int c, MIDI_VALUE *answer)
{
	if (toupper(c) == 'X')
		return(STATE_STARTHEX);
	else if (isoctal(c))
	{
		*answer = D_TO_INT(c);
		return(STATE_INOCTAL);
	}
	else if (isspace(c))
		return(STATE_SUCCESS);
	else
		return(STATE_ERROR);
}

	
/****************************************************************************
* Append the digit "newNum" onto the right of the number *answer.
* Don't allow overflow.  Works for any base.  Return newState on success.
****************************************************************************/
	
STATE_T IncreaseIfPossible(MIDI_VALUE *answer, int newNum, int base,
			   STATE_T newState)
{
	if ((*answer) > (LARGEST_VALUE / base))
		return(STATE_OVERFLOW);
	else
	{
		*answer *= base;

		if ((*answer) > (LARGEST_VALUE - newNum))
			return(STATE_OVERFLOW);
		else
		{
			*answer += newNum;
			return(newState);
		}
	}
}


/****************************************************************************
* Character-oriented routines.
****************************************************************************/
	
STATE_T DoInChar(int c, MIDI_VALUE *answer)
{
	if (c == '\\')
		return(STATE_BACKSLASHINCHAR);
	else
	{
		*answer = c;
		return(STATE_EXPECTQUOTE);
	}
}

	
STATE_T DoExpectQuote(int c, MIDI_VALUE *answer)
{
	return((c == '\'') ? STATE_SUCCESS : STATE_ERROR);
}


STATE_T DoBackslash(int c, MIDI_VALUE *answer, STATE_T newState)
{
	switch (c)
	{
		case '0':	*answer = '\0';		break;
		case 'a':	*answer = '\a';		break;
		case 'b':	*answer = '\b';		break;
	    	case 'f':	*answer = '\f';		break;
		case 'n':	*answer = '\n';		break;
		case 'r':	*answer = '\r';		break;
		case 't':	*answer = '\t';		break;
		case 'v':	*answer = '\v';		break;
		case '\\':
		case '\'':
		case '\"':	*answer = c;		break;
		default:	return(STATE_ERROR);
	}
	return(newState);
}


/****************************************************************************
* String-oriented routines.
****************************************************************************/
	
STATE_T DoInString(int c, STATE_T state, MIDI_VALUE *answer, BOOL *inString)
{
	switch (state)
	{
		case STATE_NORMAL:
			if (c == '\"')
			{
				*inString = FALSE;
			    	return(STATE_NORMAL);
			}
			else if (c == '\\')
				return(STATE_BACKSLASHINSTRING);
			else
			{
				*answer = isspace(c) ? ' ' : c;
				return(STATE_SUCCESS);
			}
			break;
		case STATE_BACKSLASHINSTRING:
			return(DoBackslash(c, answer, STATE_SUCCESS));
	}
}


/****************************************************************************
* Handling comments.  Everything from comment symbol to the end of the
* line is a comment.
****************************************************************************/

STATE_T DoComment(int c)
{
	return( (c == '\n') ? STATE_NORMAL : STATE_INCOMMENT );
}
	
/****************************************************************************
* Getting help.
****************************************************************************/
	

void InputHelp()
{
	fprintf(stderr, "INPUT\tMEANING\n"
			"1-9...\tDecimal number.\n"
			"0...\tOctal number.\n"
			"0x...\tHexadecimal number (case-insensitive).\n"
			"H...\tHexadecimal number (case-insensitive).\n"
			"A-F...\tHexadecimal number (case-insensitive).\n"
			"#...\tBinary number.\n"
			"'x'\tThe character 'x'."
			"  C character syntax like \\n and \\t is valid.\n"
			"\"...\"\tA text string."
			"  (Newlines turn into space characters.)\n"
			"Largest legal input value is %ld (base 10).\n"
			"All values must be separated by whitespace.\n\n",
		LARGEST_VALUE);
}


/****************************************************************************
* Text output functions.
****************************************************************************/
	
void PrintNumber(MIDI_VALUE number, FILE *out)
{
	fprintf(out, "Dec %3ld, Oct %3lo, Hex %3lX",
		number, number, number);

	PrintBinary(number, out);

	if (isprint((char)number))
		fprintf(out, ", Char '%c'", number);

	putc('\n', out);
}


void PrintBinary(MIDI_VALUE number, FILE *out)
{
	char buf[BITS_IN_MIDI_VALUE + 1];
	int i, on;

	for (i=0; i<BITS_IN_MIDI_VALUE; i++)
	{
		on = number & (1 << i);
		buf[BITS_IN_MIDI_VALUE-i-1] = '0' + (char)(on && 1);
	}

	buf[BITS_IN_MIDI_VALUE] = '\0';
	fprintf(out, ", Bin %s", buf);
}
