/* Reflow Oven Controller
 *
 * Copyright (C) 2020  Mario Hüttel <mario.huettel@gmx.net>
 *
 * This file is part of the Reflow Oven Controller Project.
 *
 * The reflow oven controller is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the reflow oven controller project.
 * If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @addtogroup config-parser
 * @{
 */

#include <config-parser/config-parser.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define CONFIG_PARSER_MAGIC 0x464a6e2bUL
#define CONFIG_PARSER(p) ((struct config_parser *)(p))

#define config_parser_check_handle(handle) do { if (!(handle) || \
						((struct config_parser *)(handle))->magic != CONFIG_PARSER_MAGIC) \
							return CONFIG_PARSER_PARAM_ERR; \
						} while (0)

config_parser_handle_t config_parser_open_file(struct config_parser *config_parser, bool write, const char *file_name,
					       char *working_buffer, size_t buff_size)
{
	FRESULT res;

	if (!config_parser || !file_name || !working_buffer)
		return NULL;

	config_parser->magic = CONFIG_PARSER_MAGIC;
	config_parser->write = write;
	config_parser->buffer = working_buffer;
	config_parser->buff_size = buff_size;

	res = f_open(&config_parser->file, file_name, (write ? FA_CREATE_ALWAYS | FA_WRITE : FA_READ));
	if (res != FR_OK)
		return NULL;

	return (config_parser_handle_t)config_parser;
}

static const char * const token_delim = " \t";

static int parse_value(struct config_parser_entry  *entry, char *value_start_token)
{
	char *dot;
	char *endptr;

	/* Check if token is a float number */
	dot = strstr(value_start_token, ".");
	if (dot) {
		/* Try parsing as float */
		entry->value.float_val = strtof(value_start_token, &endptr);
		if (endptr == value_start_token)
			return -1;
		entry->type = CONFIG_PARSER_TYPE_FLOAT;
		goto exit;
	}

	if (value_start_token[0] != '-') {
		/* Try parsing as ul */
		/* Try parsing as int */
		entry->value.uint_val = strtoul(value_start_token, &endptr, 0);
		if (endptr == value_start_token) {
			return -1;
		}
		entry->type = CONFIG_PARSER_TYPE_UINT;
		goto exit;
	} else {
		/* Try parsing as int */
		entry->value.int_val = strtod(value_start_token, &endptr);
		if (endptr == value_start_token) {
			return -1;
		}
		entry->type = CONFIG_PARSER_TYPE_INT;
	}

exit:
	return 0;
}

enum config_parser_ret config_parser_get_line(config_parser_handle_t handle, struct config_parser_entry *entry, bool force_float)
{
	struct config_parser *p;
	config_parser_check_handle(handle);
	p = CONFIG_PARSER(handle);
	char *token;
	int token_round = 0;

	if (!entry)
		return CONFIG_PARSER_PARAM_ERR;

	p->buffer[0] = '\0';
	if (f_gets(p->buffer, (int)p->buff_size, &p->file) == NULL)
		return CONFIG_PARSER_IOERR;

	token = strtok(p->buffer, token_delim);
	while (token != NULL) {
		/* Check for comment */
		if (token[0] == '#') {
			if (token_round == 0)
				return CONFIG_PARSER_LINE_COMMENT;
			else
				break;
		}

		switch (token_round) {
		case 0: /* KEY */
			entry->name = token;
			break;
		case 1: /* = Symbol */
			if (strcmp(token, "=")) {
				return CONFIG_PARSER_LINE_MALFORM;
			}
			break;
		case 2: /* VALUE */
			if (parse_value(entry, token))
				return CONFIG_PARSER_LINE_MALFORM;
			break;
		default:
			return CONFIG_PARSER_LINE_MALFORM;
		}

		token_round++;
		token = strtok(NULL, token_delim);
	}

	if (force_float) {
		if (entry->type == CONFIG_PARSER_TYPE_INT)
			entry->value.float_val = (float)entry->value.int_val;
		if (entry->type == CONFIG_PARSER_TYPE_UINT)
			entry->value.float_val = (float)entry->value.uint_val;

		entry->type = CONFIG_PARSER_TYPE_FLOAT;
	}

	return CONFIG_PARSER_OK;
}

enum config_parser_ret config_parser_reset_to_start(config_parser_handle_t handle)
{
	FRESULT res;
	struct config_parser *p;
	config_parser_check_handle(handle);
	p = CONFIG_PARSER(handle);

	res = f_rewind(&p->file);
	if (res != FR_OK)
		return CONFIG_PARSER_IOERR;

	return CONFIG_PARSER_OK;
}

enum config_parser_ret config_parser_write_entry(config_parser_handle_t handle, const struct config_parser_entry *entry)
{
	(void)entry;
	config_parser_check_handle(handle);

	return CONFIG_PARSER_OK;
}

enum config_parser_ret config_parser_close_file(config_parser_handle_t handle)
{
	struct config_parser *p;
	FRESULT res;
	config_parser_check_handle(handle);
	p = CONFIG_PARSER(handle);

	res = f_close(&p->file);

	return (res == FR_OK ? CONFIG_PARSER_OK : CONFIG_PARSER_IOERR);
}

bool config_parser_ret_is_abort_condition(enum config_parser_ret return_val)
{
	if (return_val == CONFIG_PARSER_END_REACHED ||
			return_val == CONFIG_PARSER_GENERIC_ERR ||
			return_val == CONFIG_PARSER_IOERR ||
			return_val == CONFIG_PARSER_PARAM_ERR)
		return true;

	return false;
}

/** @} */
