/* 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/>.
*/

#include <reflow-controller/settings/settings-sd-card.h>
#include <stm-periph/unique-id.h>
#include <fatfs/ff.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <config-parser/config-parser.h>

static char workbuff[256];

static void get_controller_folder_path(char *path, size_t size)
{
	uint32_t high;
	uint32_t mid;
	uint32_t low;

	if (!path)
		return;

	unique_id_get(&high, &mid, &low);

	snprintf(path, size, "/%08X-%08X-%08X",
		 (unsigned int)high, (unsigned int)mid, (unsigned int)low);
}

static void get_controller_settings_path(char *path, size_t size, const char *setting)
{
	char folder[48];

	get_controller_folder_path(folder, sizeof(folder));
	snprintf(path, size, "%s/%s.conf", folder, setting);
}

/**
 * @brief Open or create the controller folder on the SD Card.
 * @param[in,out] controller_folder
 * @return 0 if opened, 1 if created and opened, -1 if error.
 */
static int create_controller_folder(void)
{
	char foldername[48];
	int ret = -1;
	FRESULT filesystem_result;
	FILINFO fno;

	get_controller_folder_path(foldername, sizeof(foldername));

	/* Check if folder is present */
	filesystem_result = f_stat(foldername, &fno);
	if (filesystem_result == FR_OK && fno.fattrib & AM_DIR) {
		ret = 0;
	} else {
		filesystem_result = f_mkdir(foldername);
		if (filesystem_result == FR_OK) {
			ret = 1;
		} else {
			ret = -1;
		}
	}

	return ret;
}



int sd_card_settings_save_calibration(float sens_deviation, float offset, bool active)
{
	char path[200];
	FRESULT res = FR_OK;
	int ret = 0;
	FIL file;

	get_controller_settings_path(path, sizeof(path), "calibration");

	if (create_controller_folder() < 0)
		return -2;

	if (!active) {
		res = f_unlink(path);
		goto check_fresult;
	}

	res = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
	if (res != FR_OK)
		goto check_fresult;

	snprintf(path, sizeof(path), "offset = %f\nsensitivity = %f\n", offset, sens_deviation);
	ret = f_puts(path, &file);
	if (ret < 0)
		goto close_file;

	ret = 0;

close_file:
	res = f_close(&file);
check_fresult:
	if (res != FR_OK)
		return -2;
	return ret;
}

int sd_card_settings_try_load_calibration(float *sens_deviation, float *offset)
{
	char path[128];
	int status = -1;
	struct config_parser parser;
	config_parser_handle_t p;
	enum config_parser_ret res;
	struct config_parser_entry entry;
	bool sens_loaded = false;
	bool offset_loaded = false;

	if (!sens_deviation || !offset)
		return -1000;

	get_controller_settings_path(path, sizeof(path), "calibration");
	p = config_parser_open_file(&parser, false, path, workbuff, sizeof(workbuff));

	if (!p)
		return status;
	do {
		res = config_parser_get_line(p, &entry, true);
		if (res == CONFIG_PARSER_OK) {
			if (!strcmp(entry.name, "offset") && entry.type == CONFIG_PARSER_TYPE_FLOAT) {
				offset_loaded = true;
				*offset = entry.value.float_val;
			} else if (!strcmp(entry.name, "sensitivity") && entry.type == CONFIG_PARSER_TYPE_FLOAT) {
				sens_loaded = true;
				*sens_deviation = entry.value.float_val;
			}
		}

	} while (!config_parser_ret_is_abort_condition(res));

	config_parser_close_file(p);

	if (sens_loaded && offset_loaded)
		status = 0;

	return status;
}

enum settings_load_result sd_card_settings_load_pid_oven_parameters(struct oven_pid_settings *settings)
{
	enum settings_load_result ret = SETT_LOAD_ERR;
	const char * const file_name = SETTINGS_PID_PARAMETER_FILE;
	struct config_parser parser;
	config_parser_handle_t p;
	enum config_parser_ret parse_result;
	struct config_parser_entry entry;
	bool kp_loaded = false, kd_loaded = false, ki_loaded = false, int_max_loaded = false, t_sample = false;
	bool kd_tau_loaded = false;

	if (!settings) {
		ret = SETT_LOAD_ERR;
		goto exit;
	}

	workbuff[0] = 0;
	p = config_parser_open_file(&parser, false, file_name, workbuff, sizeof(workbuff));
	if (!p)
		goto exit;
	do {
		parse_result = config_parser_get_line(p, &entry, true);
		if (parse_result == CONFIG_PARSER_OK) {
			if (!strcmp(entry.name, "kp")) {
				kp_loaded = true;
				settings->kp = entry.value.float_val;
			} else if (!strcmp(entry.name, "kd")) {
				kd_loaded = true;
				settings->kd = entry.value.float_val;
			} else if (!strcmp(entry.name, "ki")) {
				ki_loaded = true;
				settings->ki = entry.value.float_val;
			} else if (!strcmp(entry.name, "max_int")) {
				int_max_loaded = true;
				settings->max_integral = entry.value.float_val;
			} else if (!strcmp(entry.name, "sample_period")) {
				t_sample = true;
				settings->t_sample = entry.value.float_val / 1000.0f;
			} else if (!strcmp(entry.name, "kd_tau")) {
				settings->kd_tau = entry.value.float_val;
				kd_tau_loaded = true;
			}
		}

	} while (!config_parser_ret_is_abort_condition(parse_result));


	if (kp_loaded && kd_loaded && ki_loaded && t_sample && int_max_loaded && kd_tau_loaded)
		ret = SETT_LOAD_SUCCESS;

	config_parser_close_file(p);

exit:
	return ret;
}
