/*
 * Written by Tomas Dosoudil <tomas.dosoudil@gmail.com>
 * This file is distributed under BSD-style license.
 *
 *
 * LL(1) grammar
 * A -> numberB | ..C
 * B -> e | ,A | ..D
 * C -> numberE
 * D -> numberE | E
 * E -> ,A | e
 *
 * Default value of 'from' and 'to' in struct LinkedList is -1. Possible values are:
 * X   'from' is set and 'to' is -1 -> Show one item where number of item is 'from'
 * X.. 'from' is set and 'to' is 0 -> Show range from 'from' to end
 * ..X 'from' is 0 and 'to' is set -> Show range from first item to 'to'
 * X..X both is 0 -> From first item to last
 */

#include 	<stdio.h>
#include 	<stdlib.h>
#include 	<string.h>
#include 	<sys/errno.h>
#include	<ctype.h>
#include 	"input_parser.h"


#define		DEBUG				0
#define		MAX_STRING_LENGTH	100	/* max length of string */
#define		POINT(c) 			((c) == '.')
#define		COMMA(c) 			((c) == ',')
#define		NUMBER(c) 			(isdigit(c))
#define		InputError(c)		(printf("Parser error: Invalid syntax (%s)\n", symbName[c]))

/* acceptable symbols */
typedef enum {
		POINT,		/* . */
		NUMBER,		/* 0-9 */
		COMMA,		/* , */
		EOI,		/* end of input */
		ERRIN = -1	/* error of input */
} LexSymbol;

/* name of acceptable symbols */
static char *symbName[] = { "point", "number", "comma", "eoi" };


static LinkedList 	*newNode;		/* new item */ 
static LinkedList 	*tail;			/* tail of ll */
static LexSymbol 	lexSymb;		/* lexical symbol */
static int			number;			/* number from input string */
static char 		*input;			/* input string */
static int			minimalFrom;	/* smallest number which can be in 'from' */


static int ReadInputSymbol(void);
static int GrammarA(void);
static int GrammarB(void);
static int GrammarC(void);
static int GrammarD(void);
static int GrammarE(void);
static int CommitNode(void);
static int NodeValidation(LinkedList*);

/* ve finalni verzi nebude ani main() */
#if DEBUG
int
main(int argc, char *argv[])
{
	LinkedList *r;
	int counter;
	if (argc == 1) {
		return 1;
	}

	r = GetLinkedList(argv[1], 1);
	
	if (r == NULL) {
		printf("stoped in main()\n");
		return 1;
	}

	counter = 0;	
	do {
		printf("node %2d: ", counter);
		counter++;

		printf("%d",r->from);
		
		if (r->to != -1)
			printf("..%d\n", r->to);
		else
			printf("\n");

	} while ((r = r->next));

	return 0;

}
#endif

/*
 * function that returns pointer to linked list
 * min - Minimal number value which can be in from. Pages are counting from 0 
 * but tuples from 1.
 */
LinkedList* 
GetLinkedList(char *arg, int min)
{
	int 		i;
	LinkedList	*head;

	minimalFrom = min;

	/* 
	 * validate input. only points, commas and numbers are valid input 
	 * i will find invalid characters in grammar too but it will be larger
	 * overhead than there.
	 */
	i = 0;
	while (arg[i]) {
		if (!(POINT(arg[i]) || COMMA(arg[i]) || NUMBER(arg[i]))) {
			fprintf(stderr, "Parser error: Invalid input '%c'\n", arg[i]);
			return NULL;
		}
		i++;
	}

	/* without input */
	if (i == 0) {
		fprintf(stderr, "Parser error: No input\n");
		return NULL;
	}

	/* check maximal length */
	if (MAX_STRING_LENGTH < i) {
		fprintf(stderr, "Parser error: Max length of argument is %d\n", MAX_STRING_LENGTH);
		return NULL;
	}

	input = arg;
	newNode = AllocateNewNode();
	if (newNode == NULL)
		return NULL;

	head = newNode;
	tail = NULL;

	if (GrammarA() == -1) {
		FreeLinkedList(&head);
	}

	return head;
}

/*
 * read first symbol from input
 */
static int
ReadInputSymbol(void)
{
	char	character;

	/* first character in word */
	character = *input;

	/* 
	 * lexical symbol is POINT
	 */
	if (POINT(character)) {
		input++;
		character = *input;

		/* second point must follow */
		if (!POINT(character)) {
			fprintf(stderr, "Parser error: Use '..' in range\n");
			return ERRIN;
		}

		lexSymb = POINT;
		input++;
	} 
	/* 
	 * lexical symbol is COMMA
	 */
	else if (COMMA(character)) {
		if (COMMA(*(input + 1))) {
			fprintf(stderr, "Parser error: Invalid syntax ',,'\n");
			return ERRIN;
		}

		lexSymb = COMMA;
		input++;
	}
	/* 
	 * lexical symbol is NUMBER
	 */
	else if (NUMBER(character)) {
		/*
		 * strtol reads numerals and return number
		 * input pointing to first non numeral character
		 */
		number = (int) strtol(input, &input, 10);
			
		lexSymb = NUMBER;
	}
	/* 
	 * error - comma at the end
	 */
	else {
		if (COMMA(*(input - 1))) {
			fprintf(stderr, "Parser error: Remove ',' at the end\n");
			return ERRIN;
		}
		lexSymb = EOI;
		input++;
	}

	return 0;
}

/*
 * syntax validation
 */
static int
GrammarA(void)
{
	if (ReadInputSymbol())
		return ERRIN;

	switch (lexSymb) {
		case NUMBER:
			newNode->from = number;
			return GrammarB();
		case POINT:
			newNode->from = minimalFrom;			/* minimal value in 'from' */
			return GrammarC();
		default:
			InputError(lexSymb);
			return ERRIN;
	}
}

static int
GrammarB(void)
{
	if (ReadInputSymbol())
		return ERRIN;

	switch (lexSymb) {
		case COMMA:
			if (CommitNode() == ERRIN)
				return ERRIN;

			newNode = AllocateNewNode();
			if (newNode == NULL)
				return ERRIN;

			return GrammarA();
		case POINT:
			return GrammarD();
		case EOI:
			return CommitNode();
		default:
			InputError(lexSymb);
			return ERRIN;
	}
}

static int
GrammarC(void)
{
	if (ReadInputSymbol())
		return ERRIN;

	switch (lexSymb) {
		case NUMBER:
			newNode->to = number;
			return GrammarE();
		default:
			InputError(lexSymb);
			return ERRIN;
	}
}

static int
GrammarD(void)
{
	if (ReadInputSymbol())
		return ERRIN;

	switch (lexSymb) {
		case NUMBER:
			newNode->to = number;
			return GrammarE();
		case COMMA:
			newNode->to = 0;		/* to in range must be 0 and front of allocate */
			if (CommitNode() == ERRIN)
				return ERRIN;

			newNode = AllocateNewNode();
			if (newNode == NULL)
				return ERRIN;

			return GrammarA();
		case EOI:
			newNode->to = 0;
			return CommitNode();
		default:
			InputError(lexSymb);
			return ERRIN;
	}
}

static int
GrammarE(void)
{
	if (ReadInputSymbol())
		return ERRIN;

	switch (lexSymb) {
		case COMMA:
			if (CommitNode() == ERRIN)
				return ERRIN;

			newNode = AllocateNewNode();
			if (newNode == NULL)
				return ERRIN;

			return GrammarA();
		case EOI:
			return CommitNode();
		default:
			InputError(lexSymb);
			return ERRIN;
	}
}

/* 
 * f makes list
 */
static int
CommitNode(void)
{
	if (NodeValidation(newNode) == -1)
		return -1;

	if (tail == NULL) {
		tail = newNode;
	}
	else {
		tail->next = newNode;
		tail = newNode;
	}
	
	return 0;
}

/* 
 * makes and returns allocated structure LinkedList 
 */
LinkedList*
AllocateNewNode(void)
{
	LinkedList *list;

	list = (LinkedList*) malloc(sizeof(LinkedList));
	if (list == NULL) {
		perror("Parser error");
		return NULL;
	}

	list->next = NULL;
	list->from = -1;
	list->to = -1;

	return list;
}

/*
 * function orders values
 * from smaller to greater, except to = 0 where 0 means to end 
 * unentered value is -1
 */
static int
NodeValidation(LinkedList *node)
{
	int	num;

	/* array starts from 0 */
	if (node->from > 0)
		;//node->from--;
	if (node->to > 0)
		;//node->to--;

	if ((node->from > node->to) && (node->to != 0) && (node->to != -1)) {
		num = node->from;
		node->from = node->to;
		node->to = num;
	}

	if (node->from < minimalFrom) {
		fprintf(stderr, "Parser error: Input value is too small (%d) you must use (%d)\n",
				node->from, minimalFrom);
		return -1;
	}
	
	return 0;
}

/* if error occurred clean memory and set head to NULL*/
void
FreeLinkedList(LinkedList **head)
{
		LinkedList	*node;

		while (*head != NULL) {
			node = *head;
			*head = (*head)->next;
			free(node);
		}	
}
