/* 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 <stm32/stm32f4xx.h>
#include <cmsis/core_cm4.h>
#include <reflow-controller/shell.h>
#include <stm-periph/uart.h>
#include <string.h>
#include <reflow-controller/adc-meas.h>
#include <reflow-controller/digio.h>
#include <stdlib.h>
#include <malloc.h>
#include <helper-macros/helper-macros.h>
#include <reflow-controller/systick.h>
#include <stm-periph/unique-id.h>
#include <reflow-controller/calibration.h>
#include <reflow-controller/temp-converter.h>
#include <fatfs/ff.h>
#include <reflow-controller/stack-check.h>
#include <reflow-controller/rotary-encoder.h>
#include <reflow-controller/safety-adc.h>

#ifndef GIT_VER
#define GIT_VER "VERSION NOT SET"
#endif

extern struct stm_uart shell_uart;
static shellmatta_instance_t shell;
static char shell_buffer[512];
static char history_buffer[600];

static shellmatta_retCode_t shell_cmd_ver(const shellmatta_handle_t   handle,
					  const char                  *arguments,
					  uint32_t                    length)
{
	(void)arguments;
	(void)length;
	uint32_t low_id;
	uint32_t mid_id;
	uint32_t high_id;

	unique_id_get(&high_id, &mid_id, &low_id);

	shellmatta_printf(handle, "Reflow Oven Controller Firmware " xstr(GIT_VER) "\r\n"
					"Compiled: " __DATE__ " at " __TIME__ "\r\n"
					"Serial: %08X-%08X-%08X", high_id, mid_id, low_id);

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_digio_get(const shellmatta_handle_t   handle,
						const char                  *arguments,
						uint32_t                    length)
{
	(void)arguments;
	(void)length;

	shellmatta_printf(handle,
			  "DIGIO0 DIGIO1 DIGIO2 DIGIO3 LED0 LED1 LS\r\n"
			  "%d      %d      %d      %d      %d    %d    %d\r\n",
			  digio_get(0), digio_get(1), digio_get(2), digio_get(3),
			  led_get(0), led_get(1), loudspeaker_get());

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_digio_set(const shellmatta_handle_t   handle,
						const char                  *arguments,
						uint32_t                    length)
{
	(void)length;
	(void)handle;
	int num = 100;
	int state;
	char buff[64];
	char *curr_token;

	strncpy(buff, arguments, sizeof(buff)-1);
	buff[63] = '\0';

	curr_token = strtok(buff, " ");
	curr_token = strtok(NULL, " ");
	if (!curr_token)
		return SHELLMATTA_ERROR;

	num = atoi(curr_token);

	if (!curr_token)
		return SHELLMATTA_ERROR;

	curr_token = strtok(NULL, " ");
	state = atoi(curr_token);

	if (num < 4 && num >= 0)
		digio_set(num, state);
	else if (num >= 4 && num <= 5)
		led_set(num - 4, state);
	else if (num == 6)
		loudspeaker_set(state);
	else
		return SHELLMATTA_ERROR;

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_pt1000_res(const shellmatta_handle_t   handle,
						 const char                  *arguments,
						 uint32_t length)
{
	(void)arguments;
	(void)length;
	float resistance;
	int pt1000_status;
	enum adc_pt1000_error pt1000_flags;
	char display_status[100];
	float temp;
	int temp_conv_status;
	const char *temp_prefix;

	display_status[0] = 0;

	pt1000_status = adc_pt1000_get_current_resistance(&resistance);

	if (pt1000_status == 2) {
		strcat(display_status, " UNSTABLE ");
	} else if (pt1000_status) {
		pt1000_flags = adc_pt1000_check_error();
		if (pt1000_flags & ADC_PT1000_INACTIVE)
			strcat(display_status, " DEACTIVATED ");
		else if (pt1000_flags & ADC_PT1000_WATCHDOG_ERROR)
			strcat(display_status, " WATCHDOG ");
		else if (pt1000_flags & ADC_PT1000_OVERFLOW)
			strcat(display_status, " OVERFLOW ");
	} else {
		strcpy(display_status, "VALID");
	}

	temp_conv_status = temp_converter_convert_resistance_to_temp(resistance, &temp);
	switch (temp_conv_status) {
	case 1:
		temp_prefix = ">";
		break;
	case -1:
		temp_prefix = "<";
		break;
	case 0:
		/* FALLTHRU */
	default:
		temp_prefix = "";
		break;
	}

	shellmatta_printf(handle, "PT1000 resistance: %.2f Ohm (%s%.1f °C) [%s]\r\n", resistance, temp_prefix,temp, display_status);

	return SHELLMATTA_OK;
}
static shellmatta_retCode_t shell_cmd_clear_error_status(const shellmatta_handle_t handle, const char *arguments,
							 uint32_t length)
{
	(void)arguments;
	(void)length;
	(void)handle;

	adc_pt1000_clear_error();

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_uptime(const shellmatta_handle_t   handle,
					     const char                  *arguments,
					     uint32_t length)
{
	(void)arguments;
	(void)length;

	shellmatta_printf(handle, "Uptime: %llu secs", global_tick_ms/1000);
	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_cal(const shellmatta_handle_t   handle,
					  const char                  *arguments,
					  uint32_t length)
{
	(void)arguments;
	(void)length;

	return calibration_sequence_shell_cmd(handle, arguments, length);
}

static shellmatta_retCode_t shell_meminfo(const shellmatta_handle_t   handle,
					  const char                  *arguments,
					  uint32_t length)
{
	(void)arguments;
	(void)length;
	struct mallinfo mi;

	shellmatta_printf(handle,
			  "Stack pointer: %p\r\n"
			  "Stack usage: 0x%x bytes\r\n"
			  "Lim to heap: 0x%x bytes\r\n",
			  read_stack_pointer(),
			  stack_check_get_usage(),
			  stack_check_get_free());

	mi = mallinfo();
	shellmatta_printf(handle, "\r\nDynamic Memory Management\r\n");
	shellmatta_printf(handle, "Allocated bytes: %d\r\n", mi.arena, mi.arena);
	shellmatta_printf(handle, "Number of free chunks: %d\r\n", mi.ordblks);
	shellmatta_printf(handle, "Top-most, releasable space: %d\r\n", mi.keepcost);
	shellmatta_printf(handle, "Total free space: %d\r\n", mi.fordblks);
	shellmatta_printf(handle, "Total allocated space: %d\r\n", mi.uordblks);

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_rot(const shellmatta_handle_t   handle,
					  const char *arguments,
					  uint32_t length)
{
	(void)arguments;
	(void)length;

	uint32_t rot_val;

	rot_val = rotary_encoder_get_abs_val();
	//delta = rotary_encoder_get_change_val();
	shellmatta_printf(handle, "Rotary encoder value: %u\r\n", rot_val);

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_pt1000_res_loop(const shellmatta_handle_t handle, const char *arguments,
						      uint32_t length)
{
	(void)arguments;
	(void)length;
	static uint64_t timestamp = 0ULL;

	if (systick_ticks_have_passed(timestamp, 150)) {
		shell_cmd_pt1000_res(handle, "", 0UL);
		timestamp = systick_get_global_tick();
	}

	return SHELLMATTA_CONTINUE;
}

static shellmatta_retCode_t shell_cmd_ls(const shellmatta_handle_t handle, const char *arguments,
					 uint32_t length)
{
	(void)length;
	(void)arguments;
	DIR dir;
	FRESULT res;
	FILINFO fno;

	res = f_opendir(&dir, "/");
	if (res != FR_OK) {
		shellmatta_printf(handle, "Filesystem inaccessible. Is an SD Card inserted?\r\n");
		return SHELLMATTA_OK;
	}

	while (f_readdir(&dir, &fno) == FR_OK) {
		if (fno.fname[0] == 0)
			break;
		shellmatta_printf(handle, "%c\t%s\r\n", (fno.fattrib & AM_DIR ? 'd' : 'f'), fno.fname);
	}

	f_closedir(&dir);

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_reset(const shellmatta_handle_t handle, const char *arguments,
					    uint32_t length)
{
	(void)handle;
	(void)length;
	(void)arguments;

	NVIC_SystemReset();

	return SHELLMATTA_BUSY;
}

static shellmatta_retCode_t shell_cmd_cat(const shellmatta_handle_t handle, const char *arguments,
					  uint32_t length)
{
	FIL file;
	char path_buff[256];
	const char *path;
	UINT bytes_read;
	FRESULT res;

	strncpy(path_buff, arguments, MIN(sizeof(path_buff), length));
	path_buff[MIN(length, sizeof(path_buff)-1)] = 0;
	path = strtok(path_buff, " ");
	path = strtok(NULL, " ");

	if (strlen(path) == 0) {
		shellmatta_printf(handle, "Specify path!\r\n");
		return SHELLMATTA_OK;
	}

	res = f_open(&file, path, FA_READ);
	if (res == FR_OK) {
		shellmatta_write(handle, "\r\n", 2U);
		do {
			res = f_read(&file, path_buff, sizeof(path_buff), &bytes_read);
			if (bytes_read > 0)
				shellmatta_write(handle, path_buff, bytes_read);
			else
				break;
		} while (res == FR_OK);

		shellmatta_write(handle, "\r\n", 2U);
	}

	if (res != FR_OK) {
		shellmatta_printf(handle, "Error reading file\r\n");
	}

	f_close(&file);

	return SHELLMATTA_OK;
}

static shellmatta_retCode_t shell_cmd_safety_adc(const shellmatta_handle_t handle, const char *arguments,
					  uint32_t length)
{
	(void)length;
	(void)arguments;

	shellmatta_printf(handle, "VREF:\t%8.2f\tmV\r\n", safety_adc_get_vref());
	shellmatta_printf(handle, "TEMP:\t%8.2f\tdeg. Celsius\r\n", safety_adc_get_temp());

	return SHELLMATTA_OK;
}

//typedef struct shellmatta_cmd
//{
//    char                    *cmd;       /**< command name                           */
//    char                    *cmdAlias;  /**< command alias                          */
//    char                    *helpText;  /**< help text to print in "help" command   */
//    char                    *usageText; /**< usage text to print on parameter error */
//    shellmatta_cmdFct_t     cmdFct;     /**< pointer to the cmd callack function    */
//    struct shellmatta_cmd   *next;      /**< pointer to next command or NULL        */
//} shellmatta_cmd_t;

static shellmatta_cmd_t cmd[14] = {
	{
		.cmd = "version",
		.cmdAlias = "ver",
		.helpText = "Print firmware version",
		.usageText = NULL,
		.cmdFct = shell_cmd_ver,
		.next = &cmd[1],
	},
	{
		.cmd = "pt1000",
		.cmdAlias = "pt",
		.helpText = "Get current filtered and calibrated PT1000 resistance",
		.usageText = NULL,
		.cmdFct = shell_cmd_pt1000_res,
		.next = &cmd[2],
	},
	{
		.cmd = "pt1000-dump",
		.cmdAlias = "ptdump",
		.helpText = "Get current filtered and calibrated PT1000 resistance in a loop",
		.usageText = "pt1000-dump",
		.cmdFct = shell_cmd_pt1000_res_loop,
		.next = &cmd[3],
	},
	{
		.cmd = "pt1000-clear-error",
		.cmdAlias = "pt-clear",
		.helpText = "Clear error status of PT1000 reading",
		.usageText = NULL,
		.cmdFct = shell_cmd_clear_error_status,
		.next = &cmd[4],
	},
	{
		.cmd = "digio-get",
		.cmdAlias = "dg",
		.helpText = "Read all digital input/output ports",
		.usageText = NULL,
		.cmdFct = shell_cmd_digio_get,
		.next = &cmd[5],
	},
	{
		.cmd = "digio-set",
		.cmdAlias = "ds",
		.helpText = "Set DIGIO Port",
		.usageText = "digio-set <num> <state>",
		.cmdFct = shell_cmd_digio_set,
		.next = &cmd[6],
	},
	{
		.cmd = "uptime",
		.cmdAlias = "upt",
		.helpText = "Get uptime in seconds",
		.usageText = "",
		.cmdFct = shell_cmd_uptime,
		.next = &cmd[7],
	},
	{
		.cmd = "calibrate",
		.cmdAlias = "cal",
		.helpText = "Calibrate resistance measurement",
		.usageText = "",
		.cmdFct = shell_cmd_cal,
		.next = &cmd[8],
	},
	{
		.cmd = "meminfo",
		.cmdAlias = NULL,
		.helpText = "Get information about memory usage",
		.usageText = "",
		.cmdFct = shell_meminfo,
		.next = &cmd[9],
	},
	{
		.cmd = "rotary-encoder",
		.cmdAlias = "rot",
		.helpText = "Get current rotary encoder value",
		.usageText = "",
		.cmdFct = shell_cmd_rot,
		.next = &cmd[10],
	},
	{
		.cmd = "ls",
		.cmdAlias = NULL,
		.helpText = "List filesystem contents",
		.usageText = "",
		.cmdFct = shell_cmd_ls,
		.next = &cmd[11],
	},
	{
		.cmd = "reset",
		.cmdAlias = NULL,
		.helpText = "Reset controller",
		.usageText = "reset",
		.cmdFct = shell_cmd_reset,
		.next = &cmd[12],
	},
	{
		.cmd = "cat",
		.cmdAlias = NULL,
		.helpText = "Print file contents",
		.usageText = "cat <path>",
		.cmdFct = shell_cmd_cat,
		.next = &cmd[13],
	},
	{
		.cmd = "safety-adc",
		.cmdAlias = NULL,
		.helpText = "",
		.usageText = "",
		.cmdFct = shell_cmd_safety_adc,
		.next = NULL,
	},

};

shellmatta_handle_t shell_init(shellmatta_write_t write_func)
{
	shellmatta_handle_t handle;
	shellmatta_retCode_t ret;

	ret = shellmatta_doInit(&shell, &handle, shell_buffer, sizeof(shell_buffer), history_buffer, sizeof(history_buffer),
				"\e[1;32mReflow Controller>\e[m ", cmd, write_func);
	if (ret != SHELLMATTA_OK)
		handle = NULL;

	return handle;
}

void shell_print_motd(shellmatta_handle_t shell)
{
	/* Clear display and set cursor to home position */
	shellmatta_printf(shell, "\e[2J\e[H");
	shellmatta_printf(shell, "Shimatta 仕舞った Reflow Controller ready\r\n\r\n");
	shell_cmd_ver(shell, NULL, 0UL);
	shell_handle_input(shell, "\r\n", 2UL);
}

void shell_handle_input(shellmatta_handle_t shell, const char *data, size_t len)
{
	if (!shell)
		return;

	shellmatta_processData(shell, (char *)data, (uint32_t)len);
}

void shell_print_string(shellmatta_handle_t shell, const char *string)
{
	if (!shell)
		return;

	shellmatta_write(shell, (char *)string, strlen(string));
}
