#include "wmcliphist.h"
#include <sys/stat.h>

#define	RC_BUF_SIZE	256

/* automat state */
typedef enum {
	STATE_BEGINING,
	STATE_COMMENT,
	STATE_DIRECTIVE,
	STATE_VALUE,
	STATE_EXPRESSION_START,
	STATE_EXPRESSION,
	STATE_EXPRESSION_END,
	STATE_ACTION,
	STATE_COMMAND
} STATE;


GList	*action_list = NULL;


/* add character to buffer */
#define	add_to_buff(buf, pos, chr)	do { \
	buf[pos++] = chr; \
	buf[pos] = '\0'; \
	if (pos + 1 == RC_BUF_SIZE) \
		error = 7; \
} while (0)


#define	ismywhite(c)	(c == '\t' || c == ' ')


/*
 * returns config/data file name in user's home
 */
char *
rcconfig_get_name(char *append)
{
	static gchar	fname[PATH_MAX];
	const gchar	*home;

	begin_func("rcconfig_get_name");

	if (!(home = g_getenv("HOME")))
		return_val(NULL);

	memset(fname, 0, PATH_MAX);
	snprintf(fname, PATH_MAX, "%s/.wmcliphist%s", home, append);

	return_val(fname);
}




/*
 * appends parsed action to action list
 */
int
action_append(char *expr_buf, char *action_buf, char *cmd_buf)
{
	ACTION		*action;

	begin_func("action_append");

	action = g_new0(ACTION, 1);

	if (regcomp(&action->expression, expr_buf,
				REG_EXTENDED|REG_ICASE|REG_NOSUB) != 0) {
		g_free(action);
		return_val(-101);
	}

	if (strcmp(action_buf, "exec") == 0)
		action->action = ACT_EXEC;
	else if (strcmp(action_buf, "submenu") == 0)
		action->action = ACT_SUBMENU;
	else if (strcmp(action_buf, "ignore") == 0)
		action->action = ACT_IGNORE;
	else {
		g_free(action);
		return_val(-102);
	}

	action->command = g_strdup(cmd_buf);

	action_list = g_list_append(action_list, action);

	return_val(0);
}


/*
 * read and parse rcconfig
 */
int
rcconfig_get(char *fname)
{
	int		f_rc;
	char		tmp[1024], c;
	int		byte_cnt;
	STATE		state = STATE_BEGINING;
	char		direc_buf[RC_BUF_SIZE],
			expr_buf[RC_BUF_SIZE],
			action_buf[RC_BUF_SIZE],
			cmd_buf[RC_BUF_SIZE];
	int		buf_index = 0;
	int		error = 0, eof = 0;
	int		i;
	int		line = 0;
	int		res;

	begin_func("rcconfig_get");

	close(open(fname,  O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));

	if ((f_rc = open(fname, O_RDONLY)) < 0) {
		fprintf(stderr, ".wmcliphistrc not found\n");
		return_val(1);
	}

	i = byte_cnt = 0;
	while (1) {
		if (i == byte_cnt) {
			byte_cnt = read(f_rc, tmp, 1024);
			if (byte_cnt == -1) {
				fprintf(stderr, "cannot read .wmcliphistrc\n");
				break;
			} else if (byte_cnt < 1024) {
				tmp[byte_cnt++] = 0;
				eof = 1;
			}
			i = 0;
		}

		c = tmp[i++];
		switch (state) {
			case STATE_BEGINING:
				line++;
				if (isalnum(c)) {
					state = STATE_DIRECTIVE;
					buf_index = 0;
					add_to_buff(direc_buf, buf_index, c);
				} else if (c == '#')
					state = STATE_COMMENT;
				else if (ismywhite(c))
					line--;
				else if (c == '\n')
					state = STATE_BEGINING;
				else
					error = 1;
				break;

			case STATE_COMMENT:
				if (c == '\n' || c == '\0')
					state = STATE_BEGINING;
				break;

			case STATE_DIRECTIVE:
				if (ismywhite(c)) {
					if (strcmp(direc_buf, "action") == 0) {
						state = STATE_EXPRESSION_START;
						buf_index = 0;
					} else if (strcmp(direc_buf, "menukey") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "prev_item_key") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "exec_item_key") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "keep") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "lcolor") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "clipboard") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "autosave") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf,
								"confirm_exec") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "exec_immediately") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "exec_middleclick") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "exec_hotkey") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else if (strcmp(direc_buf, "auto_take_up") == 0) {
						state = STATE_VALUE;
						buf_index = 0;
					} else {
						error = 8;
					}
				} else if (isalpha(c) || c == '_') {
					add_to_buff(direc_buf, buf_index, c);
				} else {
					error = 1;
				}
				break;

			case STATE_VALUE:
				if (c == '\n' || ismywhite(c)) {
					if (strcmp(direc_buf, "menukey") == 0) {
						memset(menukey_str, 0, 32);
						strncpy(menukey_str,
								expr_buf, 31);
					} else if (strcmp(direc_buf, "prev_item_key") == 0) {
						memset(prev_item_key_str, 0, 32);
						strncpy(prev_item_key_str,
								expr_buf, 31);
					} else if (strcmp(direc_buf, "exec_item_key") == 0) {
						memset(exec_item_key_str, 0, 32);
						strncpy(exec_item_key_str,
								expr_buf, 31);
					} else if (strcmp(direc_buf, "keep") == 0) {
						num_items_to_keep =
							atoi(expr_buf);
					} else if (strcmp(direc_buf, "lcolor")
							== 0) {
						memset(locked_color_str, 0, 32);
						strncpy(locked_color_str,
								expr_buf, 31);
					} else if (strcmp(direc_buf, "clipboard")
							== 0) {
						memset(clipboard_str, 0, 32);
						strncpy(clipboard_str,
								expr_buf, 31);
					} else if (strcmp(direc_buf, "autosave") == 0) {
						autosave_period =
							atoi(expr_buf);
					} else if (strcmp(direc_buf,
								"confirm_exec") == 0) {
						if (strcasecmp(expr_buf, "yes") == 0) {
							confirm_exec = 1;
						}
					} else if (strcmp(direc_buf, "exec_immediately") == 0) {
						if (strcasecmp(expr_buf, "no") == 0) {
							exec_immediately= 0;
						}
					} else if (strcmp(direc_buf, "exec_middleclick") == 0) {
						if (strcasecmp(expr_buf, "no") == 0) {
							exec_middleclick = 0;
						}
					} else if (strcmp(direc_buf, "exec_hotkey") == 0) {
						if (strcasecmp(expr_buf, "no") == 0) {
							exec_hotkey = 0;
						}
					} else if (strcmp(direc_buf, "auto_take_up") == 0) {
						if (strcasecmp(expr_buf, "no") == 0) {
							auto_take_up = 0;
						}
					} else
						error = 1;
				} else if (isgraph(c))
					add_to_buff(expr_buf, buf_index, c);
				else
					error = 2;

				if (error == 0) {
					if (c == '\n' || c == '\0') {
						buf_index = 0;
						state = STATE_BEGINING;
					} else if (ismywhite(c)) {
						buf_index = 0;
						state = STATE_COMMENT;
					}
				}

				break;

			case STATE_EXPRESSION_START:
				if (c == '"')
					state = STATE_EXPRESSION;
				else
					error = 1;
				break;

			case STATE_EXPRESSION:
				if (c == '"')
					state = STATE_EXPRESSION_END;
				else if (c == '\n')
					error = 1;
				else
					add_to_buff(expr_buf, buf_index, c);
				break;

			case STATE_EXPRESSION_END:
				if (c != ' ' && c != '\t')
					error = 1;
				if (strcmp(direc_buf, "action") == 0) {
					state = STATE_ACTION;
					buf_index = 0;
				} else
					error = 1;

				break;

			case STATE_ACTION:
				if (c == ' ' || c == '\t') {
					state = STATE_COMMAND;
					buf_index = 0;
				} else if (c == '\0' || c == '\n') {
					if (strcmp(action_buf, "ignore") == 0) {
						state = STATE_BEGINING;
						buf_index = 0;
						*cmd_buf = '\0';
						res = action_append(
								expr_buf,
								action_buf,
								cmd_buf);
						if (res < 0)
							error = abs(res);
					} else
						error = 1;
				} else if (isalpha(c))
					add_to_buff(action_buf, buf_index, c);
				else
					error = 1;
				break;

			case STATE_COMMAND:
				if (c == '\n' || c == '\0') {
					state = STATE_BEGINING;
					buf_index = 0;
					res = action_append(
							expr_buf,
							action_buf,
							cmd_buf);
					if (res < 0)
						error = abs(res);
				} else
					add_to_buff(cmd_buf, buf_index, c);
				break;
		}

		if (!error && (!eof || i < byte_cnt))
			continue;

		switch (state) {
			case STATE_DIRECTIVE:
				if (error == 7)
					fprintf(stderr, "Directive is too long "
							"(line %d)\n", line);
				else if (error == 8)
					fprintf(stderr, "Unknown directive "
							"(line %d)\n", line);
				else
					fprintf(stderr, "Only letters are "
							"allowed in directive "
							"name (line %d)\n",
							line);
				break;

			case STATE_EXPRESSION_START:
			case STATE_EXPRESSION:
				if (error == 7)
					fprintf(stderr, "Expression is too long "
							"(line %d)\n", line);
				else
					fprintf(stderr, "Expression must be "
							"enclosed with quotes "
							"\" (line %d)\n", line);
				break;

			case STATE_EXPRESSION_END:
				fprintf(stderr, "One space/tab and "
						"action must follow "
						"each expression"
						" (line %d)\n", line);
				break;

			case STATE_ACTION:
				if (error == 1)
					fprintf(stderr, "Only letters are "
							"allowed in action "
							"name (line %d)\n",
							line);
				else if (error == 101)
					fprintf(stderr, "Invalid expression "
							"(line %d)\n", line);
				else if (error == 7)
					fprintf(stderr, "Action is too long "
							"(line %d)\n", line);
				else
					fprintf(stderr, "Invalid action "
							"(line %d)\n", line);
				break;

			case STATE_VALUE:
				if (error == 1)
					fprintf(stderr, "Invalid directive "
							"(line %d)\n", line);
				else if (error == 7)
					fprintf(stderr, "Value is too long "
							"(line %d)\n", line);
				else
					fprintf(stderr, "Invalid value "
							"(line %d)\n", line);
				break;

			case STATE_COMMAND:
				if (error == 101)
					fprintf(stderr, "Invalid expression "
							"(line %d)\n", line);
				else if (error == 7)
					fprintf(stderr, "Command is too long "
							"(line %d)\n", line);
				else
					fprintf(stderr, "Invalid action "
							"(line %d)\n", line);
				break;

			case STATE_COMMENT:
				if (!eof)
					fprintf(stderr, "Unknown error "
							"(line %d)\n", line);
				else
					error = 0;
				break;

			case STATE_BEGINING:
				/* everything is OK */
				error = 0;
				break;
		}

		break;
	}

	close(f_rc);

	return_val(error);
}


/*
 * free rcconfig data
 */
void
rcconfig_free()
{
	GList		*list_node;

	begin_func("rcconfig_free");

	list_node = action_list;
	while (list_node) {
		ACTION		*action = list_node->data;

		g_free(action->command);
		regfree(&action->expression);
		g_free(action);
		list_node = list_node->next;
	}
	g_list_free(action_list);

	return_void();
}