/* 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-eeprom.h>
#include <reflow-controller/settings/spi-eeprom.h>
#include <stm-periph/crc-unit.h>
#include <string.h>

#define EEPROM_HEADER_MAGIC 0xC5
#define EEPROM_HEADER_COMP_VER 0x01

static const uint8_t expected_header[2] = {EEPROM_HEADER_MAGIC, EEPROM_HEADER_COMP_VER};

#define EEPROM_CALIBRATION_BASE_ADDR 0x2
struct eeprom_calibration_settings {
	uint32_t active;
	float offset;
	float sens_dev;
	uint32_t crc;
};

#define EEPROM_OVER_TEMP_CONFIG_BASE_ADDR (EEPROM_CALIBRATION_BASE_ADDR + sizeof(struct eeprom_calibration_settings))
struct eeprom_over_temp_config {
	float over_temperature;
	uint32_t over_temp_crc;
};

static bool check_eeprom_header(void)
{
	uint8_t header[2] = {0};

	/* Try to read the magic header and the compatible version */
	spi_eeprom_read(0x0, header, 2);
	if (memcmp(header, expected_header, 2))
		return false;
	else
		return true;
}

static void settings_eeprom_zero()
{
	settings_eeprom_save_calibration(0.0f, 0.0f, false);
	settings_eeprom_save_overtemp_limit(0.0, false);
}

bool settings_eeprom_detect_and_prepare(void)
{
	bool eeprom_ready = false;;

	int res;

	crc_unit_init();
	res = spi_eeprom_init();
	if (res)
		goto ret_deinit_crc;

	if (!spi_eeprom_check_connected())
		goto ret_deinit_crc;

	if (check_eeprom_header() == false) {
		/* Try to write a new header and check it again */
		spi_eeprom_write(0, expected_header, sizeof(expected_header));
		while (spi_eeprom_write_in_progress());
		if (check_eeprom_header() == false) {
			goto ret_deinit_crc;
		} else {
			/* Sucessfully written new header
			 * Zero out the rest of the settings
			 */
			settings_eeprom_zero();
			eeprom_ready = true;
		}
	} else {
		eeprom_ready = true;
	}

	goto exit;

ret_deinit_crc:
	crc_unit_deinit();

exit:
	return eeprom_ready;
}

int settings_eeprom_save_calibration(float sens, float offset, bool active)
{
	int res;

	struct eeprom_calibration_settings sett;

	sett.active = (active ? 0xAABBCCDD : 0);
	sett.offset = offset;
	sett.sens_dev = sens;

	crc_unit_reset();
	crc_unit_input_array((const uint32_t *)&sett, (sizeof(sett) / 4)-1);
	sett.crc = crc_unit_get_crc();

	res = spi_eeprom_write(EEPROM_CALIBRATION_BASE_ADDR, (uint8_t *)&sett, sizeof(sett));

	if (res)
		return -2;
	else
		return 0;
}

int settings_eeprom_load_calibration(float *sens, float *offset, bool *active)
{
	struct eeprom_calibration_settings sett;
	int res;

	res = spi_eeprom_read(EEPROM_CALIBRATION_BASE_ADDR, (uint8_t *)&sett, sizeof(sett));
	if (res)
		return -2;

	crc_unit_reset();
	crc_unit_input_array((const uint32_t *)&sett, sizeof(sett) / 4 - 1);
	if (crc_unit_get_crc() == sett.crc) {
		if (sens)
			*sens = sett.sens_dev;
		if (offset)
			*offset = sett.offset;
		if (active)
			*active = (sett.active ? true : false);
		return 0;
	} else {
		return -1;
	}
}

int settings_eeprom_save_overtemp_limit(float overtemp_limit, bool active)
{
	int res;
	struct eeprom_over_temp_config over_temp_conf_entry;
	const uint8_t zero_vals[sizeof(over_temp_conf_entry)] = {0};

	if (!active) {
		res = spi_eeprom_write(EEPROM_OVER_TEMP_CONFIG_BASE_ADDR, (const uint8_t *)zero_vals,
				 sizeof(zero_vals));
		return res ? -1 : 0;
	}

	over_temp_conf_entry.over_temperature = overtemp_limit;
	crc_unit_reset();
	crc_unit_input_array((uint32_t *)&over_temp_conf_entry, 1);
	over_temp_conf_entry.over_temp_crc = crc_unit_get_crc();
	res = spi_eeprom_write(EEPROM_OVER_TEMP_CONFIG_BASE_ADDR, (const uint8_t *)&over_temp_conf_entry,
			 sizeof(over_temp_conf_entry));

	return res ? -1 : 0;
}

int settings_eeprom_load_overtemp_limit(float *overtemp_limit)
{
	int res;
	struct eeprom_over_temp_config over_temp_conf_entry;

	if (!overtemp_limit)
		return -1001;
	res = spi_eeprom_read(EEPROM_OVER_TEMP_CONFIG_BASE_ADDR, (uint8_t *)&over_temp_conf_entry,
			sizeof(struct eeprom_over_temp_config));
	if (res)
		return -2;

	crc_unit_reset();
	crc_unit_input_array((uint32_t *)&over_temp_conf_entry, 1);
	if (crc_unit_get_crc() != over_temp_conf_entry.over_temp_crc)
		return -1;

	*overtemp_limit = over_temp_conf_entry.over_temperature;
	return 0;
}
