/*
 * protocol.c
 *
 *  Created on: Sep 4, 2018
 *      Author: root
 */

#include "dispatch.h"
#include "tcpio.h"
#include <stdio.h>
#include <ctype.h>
#include <string.h>


// protocol errors (these have the NAK and terminator baked in)
const char *NAK_BufferOverrun = "NAK Receive buffer overrun.\n";
const char *NAK_NotTerminated = "NAK Command not terminated.\n";
const char *NAK_NullCommand = "NAK Null command.\n";
const char *NAK_IllegalChar = "NAK Illegal character 0x%02X at index %d.\n";
const char *NAK_TrailingJunk = "NAK Unexpected characters after terminator.\n";

const char TERMINATOR = '\n';

typedef enum //state
{
	in_itial, // :-P
	in_token,
	in_space,
	in_error,
//	over_run,
	finished,
} State;

typedef enum //char_class
{
	bogus,
	token,
	space,
	bingo,
} CharClass;


CharClass charClassTable[] =
{
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, space, bingo, bogus, bogus, space, bogus, bogus, // 9 TAB, 10 LF, 13 CR
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,

	space, token, token, token, token, token, token, token,   token, token, token, token, token, token, token, token, // 32 space
	token, token, token, token, token, token, token, token,   token, token, token, token, token, token, token, token,
	token, token, token, token, token, token, token, token,   token, token, token, token, token, token, token, token,
	token, token, token, token, token, token, token, token,   token, token, token, token, token, token, token, token,
	token, token, token, token, token, token, token, token,   token, token, token, token, token, token, token, token,
	token, token, token, token, token, token, token, token,   token, token, token, token, token, token, token, bogus, // 127 DEL illegal

	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
	bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,   bogus, bogus, bogus, bogus, bogus, bogus, bogus, bogus,
};

//void ProcessCommand(char *command, char *response){}



//void ProcessData(const char * data, int count)
void ParseComamnd(char * recv_buffer, int nBytes, char * send_buffer)
{
	int i = 0;
	int nTokens = 0;
	char *tokens[64];
	State state = in_itial;


	for (i = 0; i < nBytes; i++)
	{
		recv_buffer[i] = tolower(recv_buffer[i]);
	    CharClass class = charClassTable[(int)(recv_buffer[i])];

		if (bogus == class)
		{
			//sprintf_s(send_buffer, SENDBUFLEN, NAK_IllegalChar, recv_buffer[i], i);
			sprintf(send_buffer, NAK_IllegalChar, recv_buffer[i], i);
			//response = send_buffer;
			state = in_error;
			return;
		}

		switch (state)
		{
		// We need this to avoid warnings, but bogus is handled outside this context; therefore
		// consider making two tables, one bool for bogossity, and one for class without bogus.
			case in_error:
				break;

			case in_itial:
				switch (class)
				{
					case space:
					case bogus:
						break;

					case token:
						tokens[nTokens++] = recv_buffer + i;
						state = in_token;
						break;
					case bingo:
						//response = NAK_NullCommand;
						sprintf(send_buffer, NAK_NullCommand);
						state = in_error;
						return;
				}
				break;

			case in_token:
				switch (class)
				{
					case bogus:
					case token:
						break;

					case space:
						recv_buffer[i] = 0;
						state = in_space;
						break;
					case bingo:
						recv_buffer[i] = 0;
						state = finished;
						break;
				}
				break;

			case in_space:
				switch (class)
				{
					case space:
					case bogus:
						break;

					case token:
						tokens[nTokens++] = recv_buffer + i;
						state = in_token;
						break;
					case bingo:
						recv_buffer[i] = 0; // superfluous
						state = finished;
						break;
				}
				break;

			case finished:
				//response = NAK_TrailingJunk;
				sprintf(send_buffer, NAK_TrailingJunk);
				state = in_error;
				return;

		} // switch

		if (state == in_error)
			break;

	} // for

	if (state == finished)
	{
		int success = DispatchCommand(recv_buffer, tokens, nTokens, send_buffer + 4);

		if (success)
		{
			send_buffer[0] = 'A';
			send_buffer[1] = 'C';
			send_buffer[2] = 'K';
			send_buffer[3] = ' ';
		}
		else
		{
			send_buffer[0] = 'N';
			send_buffer[1] = 'A';
			send_buffer[2] = 'K';
			send_buffer[3] = ' ';
		}
		int n = strlen(send_buffer);
		send_buffer[n] = TERMINATOR;
		send_buffer[n + 1] = 0;

		//response = send_buffer;
	}
	else if (state != in_error)
	{
		//response = NAK_NotTerminated;
		sprintf(send_buffer, NAK_NotTerminated);
	}
}



char* strchr_count(const char * str, char x, int count)
{
	int i = 0;

	for (i = 0; i < count; i++)
	{
		if (str[i] == x)
			return str + i;
	}

	return NULL;
}

int ProcessData(const char * recv_buffer, int nBytes, char * response)
{
	char command_buffer[RECVBUFLEN];
	static int count = 0;
	int i = 0;

	if (nBytes + count > RECVBUFLEN)
	{
		//printf("Buffer overrun.");
		sprintf(response, NAK_BufferOverrun);
		count = 0;
		return 1;
	}

	char * termptr =  strchr_count(recv_buffer, TERMINATOR, nBytes);

	if (termptr == recv_buffer + nBytes - 1)
	{
		//printf("Recieved command in one piece.");

		while (i < nBytes)
			command_buffer[count++] = recv_buffer[i++];

		ParseComamnd(command_buffer, count, response);
		count = 0;
		return 1;
	}
	else if (termptr == NULL)
	{
		//printf("Recieved partial command.");

		while (i < nBytes)
			command_buffer[count++] = recv_buffer[i++];

		response[0] = 0;
		return 0;
	}
	else if (termptr < recv_buffer + nBytes - 1)
	{
		//printf("Recieved packet with data after terminator.");
		sprintf(response, NAK_TrailingJunk);
		count = 0;
		return 1;
	}

	// really need an assert here ...
	sprintf(response, "NAK Internal error.\n");
	return 42;
}
