/*
 * Simple config parser
 * Teodor Sigaev <teodor@sigaev.ru>
 */
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "postgres.h"

#include "dict.h"
#include "common.h"

#define CS_WAITKEY	0
#define CS_INKEY	1
#define CS_WAITEQ	2
#define CS_WAITVALUE	3
#define CS_INVALUE	4
#define CS_IN2VALUE 5
#define CS_WAITDELIM	6
#define CS_INESC	7
#define CS_IN2ESC	8

static char *
nstrdup(char *ptr, int len)
{
	char	   *res = palloc(len + 1),
			   *cptr;

	memcpy(res, ptr, len);
	res[len] = '\0';
	cptr = ptr = res;
	while (*ptr)
	{
		if (*ptr == '\\')
			ptr++;
		*cptr = *ptr;
		ptr++;
		cptr++;
	}
	*cptr = '\0';

	return res;
}

void
parse_cfgdict(text *in, Map ** m)
{
	Map		   *mptr;
	char	   *ptr = VARDATA(in),
			   *begin = NULL;
	char		num = 0;
	int			state = CS_WAITKEY;

	while (ptr - VARDATA(in) < VARSIZE(in) - VARHDRSZ)
	{
		if (*ptr == ',')
			num++;
		ptr++;
	}

	*m = mptr = (Map *) palloc(sizeof(Map) * (num + 2));
	memset(mptr, 0, sizeof(Map) * (num + 2));
	ptr = VARDATA(in);
	while (ptr - VARDATA(in) < VARSIZE(in) - VARHDRSZ)
	{
		if (state == CS_WAITKEY)
		{
			if (isalpha((unsigned char) *ptr))
			{
				begin = ptr;
				state = CS_INKEY;
			}
			else if (!isspace((unsigned char) *ptr))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("syntax error"),
					 errdetail("Syntax error in position %d near \"%c\"",
							   (int) (ptr - VARDATA(in)), *ptr)));
		}
		else if (state == CS_INKEY)
		{
			if (isspace((unsigned char) *ptr))
			{
				mptr->key = nstrdup(begin, ptr - begin);
				state = CS_WAITEQ;
			}
			else if (*ptr == '=')
			{
				mptr->key = nstrdup(begin, ptr - begin);
				state = CS_WAITVALUE;
			}
			else if (!isalpha((unsigned char) *ptr))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("syntax error"),
					 errdetail("Syntax error in position %d near \"%c\"",
							   (int) (ptr - VARDATA(in)), *ptr)));
		}
		else if (state == CS_WAITEQ)
		{
			if (*ptr == '=')
				state = CS_WAITVALUE;
			else if (!isspace((unsigned char) *ptr))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("syntax error"),
					 errdetail("Syntax error in position %d near \"%c\"",
							   (int) (ptr - VARDATA(in)), *ptr)));
		}
		else if (state == CS_WAITVALUE)
		{
			if (*ptr == '"')
			{
				begin = ptr + 1;
				state = CS_INVALUE;
			}
			else if (!isspace((unsigned char) *ptr))
			{
				begin = ptr;
				state = CS_IN2VALUE;
			}
		}
		else if (state == CS_INVALUE)
		{
			if (*ptr == '"')
			{
				mptr->value = nstrdup(begin, ptr - begin);
				mptr++;
				state = CS_WAITDELIM;
			}
			else if (*ptr == '\\')
				state = CS_INESC;
		}
		else if (state == CS_IN2VALUE)
		{
			if (isspace((unsigned char) *ptr) || *ptr == ',')
			{
				mptr->value = nstrdup(begin, ptr - begin);
				mptr++;
				state = (*ptr == ',') ? CS_WAITKEY : CS_WAITDELIM;
			}
			else if (*ptr == '\\')
				state = CS_INESC;
		}
		else if (state == CS_WAITDELIM)
		{
			if (*ptr == ',')
				state = CS_WAITKEY;
			else if (!isspace((unsigned char) *ptr))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("syntax error"),
					 errdetail("Syntax error in position %d near \"%c\"",
							   (int) (ptr - VARDATA(in)), *ptr)));
		}
		else if (state == CS_INESC)
			state = CS_INVALUE;
		else if (state == CS_IN2ESC)
			state = CS_IN2VALUE;
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("bad parser state"),
					 errdetail("%d at position %d near \"%c\"",
							   state, (int) (ptr - VARDATA(in)), *ptr)));
		ptr++;
	}

	if (state == CS_IN2VALUE)
	{
		mptr->value = nstrdup(begin, ptr - begin);
		mptr++;
	}
	else if (!(state == CS_WAITDELIM || state == CS_WAITKEY))
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("unexpected end of line")));
}
