/**
 ******************************************************************************
 * @file commands.c
 * @brief commands handler body
 * @author S. DI MERCURIO (dimercur@insa-toulouse.fr)
 * @date December 2023
 *
 ******************************************************************************
 * @copyright Copyright 2023 INSA-GEI, Toulouse, France. All rights reserved.
 * @copyright This project is released under the Lesser GNU Public License (LGPL-3.0-only).
 *
 * @copyright This file is part of "Dumber" project
 *
 * @copyright This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * @copyright This program 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
 * Lesser General Public License for more details.

 * @copyright You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 ******************************************************************************
 */

#include "commands.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/** @addtogroup Application_Software
 * @{
 */

/** @addtogroup COMMANDS
 * Commands handler is in charge of decoding received commands and building answer frames.
 * @{
 */

/** @addtogroup COMMANDS_Private Private
 * @{
 */

/** @name Commands definition
 *  List of accepted command identifiers
 */
///@{
#define PingCMD                 'p'
#define ResetCMD                'r'
#define SetMotorCMD             'm'
#define StartWWatchDogCMD       'W'
#define ResetWatchdogCMD        'w'
#define GetBatteryVoltageCMD    'v'
#define GetVersionCMD           'V'
#define StartWithoutWatchCMD    'u'
#define MoveCMD                 'M'
#define TurnCMD                 'T'
#define BusyStateCMD            'b'
#define TestCMD                 't'
#define DebugCMD                'a'
#define PowerOffCMD             'z'
///@}

/** @name Answers definition
 *  List of available answers
 */
///@{
#define OK_ANS                  "O\r"
#define ERR_ANS                 "E\r"
#define UNKNOW_ANS              "C\r"
#define BAT_OK                  "2\r"
#define BAT_LOW                 "1\r"
#define BAT_EMPTY               "0\r"
///@}

char* cmdAddChecksum(const char* str);
char cmdVerifyChecksum(char* str);

/**
 * @brief Add checksum to a string
 *
 *	Make a copy of a string and add checksum at its end (xor based).
 *
 * @warning This function use memory allocation for creating a copy of the original string. Be sure to free memory after use.
 *
 * @remark Error related to memory allocation (allocation failed) is managed directly into malloc -> generate a Panic
 *
 * @param[in] str string without checksum
 * @return 	string with checksum added
 *
 */
char* cmdAddChecksum(const char* str) {
	uint16_t j;
	unsigned char checksum=0;

	char *outstr = (char*) malloc(strlen(str)+2); // +1 for checksum to add, +1 for zero ending

	for (j = 0; str[j] != '\r'; j++) {
		outstr[j] = str[j];
		checksum ^= str[j];
	}

	if (checksum == '\r')
		checksum++;
	outstr[j] = checksum;
	outstr[j + 1] = '\r';
	outstr[j + 2] = 0;

	return outstr;
}

/**
 * @brief Verify checksum of given string
 *
 * Verify if checksum of given string is correct or not. In case of success,
 * function will suppress checksum from the string, otherwise string is not modified.
 *
 * @param[in] str string to verify checksum
 * @return
 * - 0 if checksum is not correct
 * - 1 if successful
 */
char cmdVerifyChecksum(char* str) {
	uint16_t j;
	uint16_t length;
	unsigned char checksum=0;
	char status=0;

	length = strlen(str);
	/* Warning: str should be without ending CR (0x0D) character, so, a ping command should be
	 * received as "pp", 2 chars long
	 *
	 * in the loop after, ending length is length of the caommand string whithout last char, the checksum
	 * so, we have j<length-1
	 *
	 * In case string as length 0 (empty string, return 0
	 */
	if (length!=0) {
		for (j = 0; j<length-1; j++) {
			checksum ^= str[j];
		}

		if (checksum == '\r')
			checksum++;

		if (str[j] == checksum) {
			/*str[length - 2] = 13;
			  str[length - 1] = 0;
		      str[length] = 0;
		     */

			str[length - 1] = 0; /* we remove checksum char */
			str[length] = 0;

			status = 1;
		}
	}

	return status;
}

/**
 * @brief Decode received command
 *
 * Command string is processed and a command structure is filled with information found in string.
 *
 * @warning This function use memory allocation for returning structure. Be sure to release memory after use.
 *
 * @param[in] cmd string with command received
 * @param[in] length length of command string
 * @return
 * - NULL if memory allocation has failed
 * - CMD_Move if string contains MOVE command
 * - CMD_Turn if string contains TURN command
 * - CMD_Generic for each other command
 *
 * Generic structure returned may contains CMD_NONE if command is unknown or CMD_INVALID_CHECKSUM if checksum is not valid
 *
 * @remark Error related to memory allocation (allocation failed) is managed directly into malloc -> generate a Panic
 *
 * @remark Returned value is always cast on a CMD_Generic type, but may contains CMD_Move or CMD_Turn structure. It is
 * user responsibility to check type field inside structure and cast accordingly returned value to correct structure.
 */
CMD_Generic* cmdDecode(char* cmd, uint8_t length) {
	CMD_Generic* decodedCmd;
	char cmd_type = cmd[0];
	char *p;

	/* First, verify checksum */
	if (cmdVerifyChecksum(cmd)) {
		switch (cmd_type)
		{
		case MoveCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Move));
			decodedCmd->type = CMD_MOVE;

			/* verify that command start with "M=" */
			if ((cmd[0]=='M')&&(cmd[1]=='=')) {
				cmd = cmd+2; //cmd+2 for removing "M=" at start of the string
				//((CMD_Move*)decodedCmd)->distance=strtoul(cmd , &p, 10);
				((CMD_Move*)decodedCmd)->distance=(int16_t)strtol(cmd , &p, 10);
				if (p==cmd)
					decodedCmd->type = CMD_NONE; /* missing number value xxxxx in "M=xxxxx" */
			} else
				decodedCmd->type = CMD_NONE; /* misformed command (should start with "M=" */
			break;

		case TurnCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Turn));
			decodedCmd->type = CMD_TURN;

			/* verify that command start with "T=" */
			if ((cmd[0]=='T')&&(cmd[1]=='=')) {
				cmd = cmd+2; //cmd+2 for removing "T=" at start of the string
				//((CMD_Turn*)decodedCmd)->turns=strtoul(cmd , &p, 10);
				((CMD_Turn*)decodedCmd)->turns=(int16_t)strtol(cmd , &p, 10);
				if (p==cmd)
					decodedCmd->type = CMD_NONE; /* missing number value xxxxx in "T=xxxxx" */
			} else
				decodedCmd->type = CMD_NONE; /* misformed command (should start with "T=" */
			break;

		case PingCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_PING;
			break;
		case ResetCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_RESET;
			break;
		case StartWWatchDogCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_START_WITH_WATCHDOG;
			break;
		case StartWithoutWatchCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_START_WITHOUT_WATCHDOG;
			break;
		case ResetWatchdogCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_RESET_WATCHDOG;
			break;
		case GetBatteryVoltageCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_GET_BATTERY;
			break;
		case GetVersionCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_GET_VERSION;
			break;
		case BusyStateCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_GET_BUSY_STATE;
			break;
		case TestCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_TEST;
			break;
		case DebugCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_DEBUG;
			break;
		case PowerOffCMD:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_POWER_OFF;
			break;

		default:
			decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
			decodedCmd->type = CMD_NONE;
		}
	} else { /* Checksum is wrong*/
		decodedCmd = (CMD_Generic*)malloc(sizeof(CMD_Generic));
		decodedCmd->type = CMD_INVALID_CHECKSUM;
	}

	return decodedCmd;
}

/**
 * @brief Add correct checksum to provided answer and send it to XBEE mailbox
 *
 * @warning This function use cmdAddChecksum, so indirectly make use of memory allocation for sending message to XBEE mailbox.
 *          Be sure to release memory in XBEE driver after retrieving message from mailbox.
 *
 * @todo Maybe duplication between this function and cmdSendString: see if only one function with a macro/wrapper could be used
 *
 * @param[in] ans string containing answer to send, without checksum
 * @return None
 */
void cmdSendAnswer(uint8_t ans) {
	char* answer;

	switch (ans) {
	case ANS_OK:
		answer = cmdAddChecksum(OK_ANS);
		break;
	case ANS_ERR:
		answer = cmdAddChecksum(ERR_ANS);
		break;
	default:
		answer = cmdAddChecksum(UNKNOW_ANS);
		break;
	}

	MESSAGE_SendMailbox(XBEE_Mailbox, MSG_ID_XBEE_ANS, APPLICATION_Mailbox, (char*) answer);
}

/**
 * @brief Send arbitrary answer to XBEE driver and add checksum before
 *
 * @warning This function use cmdAddChecksum, so indirectly make use of memory allocation for sending message to XBEE mailbox.
 *          Be sure to release memory in XBEE driver after retrieving message from mailbox.
 *
 * @todo Maybe duplication between this function and cmdSendAnswer: see if only one function with a macro/wrapper could be used
 *
 * @param[in] str string containing answer to send without checksum
 * @return None
 */
void cmdSendString(char *str) {
	char* answer;
	int strlength = strlen(str);
	char localstr[strlength+2];

	strncpy(localstr, str, strlength+2);
	if (localstr[strlength-1]!='\r') { // \r n'est pas present
		localstr[strlength]='\r';
		localstr[strlength+1]=0;
	}

	answer = cmdAddChecksum(localstr);
	MESSAGE_SendMailbox(XBEE_Mailbox, MSG_ID_XBEE_ANS, APPLICATION_Mailbox, (char*) answer);
}

/**
 * @brief Send battery level answer with correct checksum to XBEE driver
 *
 * @warning This function use cmdAddChecksum, so indirectly make use of memory allocation for sending message to XBEE mailbox.
 *          Be sure to release memory in XBEE driver after retrieving message from mailbox.
 *
 * @todo Check batterystate type used here: seems a bit overkill (16bit for only 6 states) -> enum should be better
 *
 * @param[in] batteryState current battery state
 * @return None
 */
void cmdSendBatteryLevel(uint16_t batteryState) {
	char* answer;

	switch (batteryState) {
	case MSG_ID_BAT_CHARGE_COMPLETE:
	case MSG_ID_BAT_CHARGE_HIGH:
	case MSG_ID_BAT_HIGH:
		answer = cmdAddChecksum("2\r");
		break;
	case MSG_ID_BAT_CHARGE_MED:
	case MSG_ID_BAT_MED:
		answer = cmdAddChecksum("1\r");
		break;
	default:
		answer = cmdAddChecksum("0\r");
		break;
	}

	MESSAGE_SendMailbox(XBEE_Mailbox, MSG_ID_XBEE_ANS, APPLICATION_Mailbox, (char*) answer);
}

/**
 * @brief Send version number answer with correct checksum to XBEE driver
 *
 * @warning This function use cmdAddChecksum, so indirectly make use of memory allocation for sending message to XBEE mailbox.
 *          Be sure to release memory in XBEE driver after retrieving message from mailbox.
 *
 * @param None
 * @return None
 */
void cmdSendVersion(void) {
	int versionlength = strlen(SYSTEM_VERSION_STR);
	char versionstr[versionlength+2];
	char* answer;

	strncpy(versionstr,SYSTEM_VERSION_STR,versionlength+2);
	versionstr[versionlength] = '\r';
	versionstr[versionlength+1]=0; // zero ending string

	answer = cmdAddChecksum(versionstr);

	MESSAGE_SendMailbox(XBEE_Mailbox, MSG_ID_XBEE_ANS, APPLICATION_Mailbox, (char*) answer);
}

/**
 * @brief Send if robot is busy (moving) or not XBEE driver
 *
 * @warning This function use cmdAddChecksum, so indirectly make use of memory allocation for sending message to XBEE mailbox.
 *          Be sure to release memory in XBEE driver after retrieving message from mailbox.
 *
 * @param[in] state current robot state
 * @return None
 */
void cmdSendBusyState(uint8_t state) {
	char* answer;

	if (state)
		answer = cmdAddChecksum("1\r");
	else
		answer = cmdAddChecksum("0\r");

	MESSAGE_SendMailbox(XBEE_Mailbox, MSG_ID_XBEE_ANS, APPLICATION_Mailbox, (char*) answer);
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
