// SPDX-License-Identifier: Apache-2.0
/*
 * Copyright (c) 2021 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
 */


#include <sstream>
#include <string>

#include "configBitstreamParser.hpp"
#include "display.hpp"
#include "ihexParser.hpp"


/* line format
 * :LLAAAATTHH...HHCC
 * LL   : nb octets de data dans la ligne (hexa)
 * AAAA : addresse du debut de la ligne ou mettre les data 
 * TT   : type de la ligne (cf. plus bas)
 * HH   : le champ de data
 * CC   : Checksum (cf. plus bas)
 */
/* type : 00 -> data + addr 16b
 *        01 -> end of file
 *        02 -> extended addr
 *        03 -> start segment addr record
 *        04 -> extended linear addr record
 *        05 -> start linear addr record
 */

#define LEN_BASE  1
#define ADDR_BASE 3
#define TYPE_BASE 7
#define DATA_BASE 9

IhexParser::IhexParser(const std::string &filename, bool reverseOrder, bool verbose):
		ConfigBitstreamParser(filename, ConfigBitstreamParser::ASCII_MODE,
		verbose),
		_base_addr(0), _reverseOrder(reverseOrder)
{}

int IhexParser::parse()
{
	std::string str;
	std::istringstream lineStream(_raw_data);

	uint16_t next_addr = 0;
	bool is_first = true;
	data_line_t cnt;
	cnt.length = 0;

	while (std::getline(lineStream, str, '\n')) {
		char *ptr;
		uint8_t sum = 0;
		uint16_t tmp, byteLen, type, checksum;
		uint32_t addr, loc_addr;

		/* if '\r' is present -> drop */
		if (str.back() == '\r')
			str.pop_back();

		if (str[0] == '#')  // comment
			continue;
		if (str[0] != ':') {
			printError("Error: a line must start with ':'");
			return EXIT_FAILURE;
		}
		/* len */
		sscanf((char *)&str[LEN_BASE], "%2hx", &byteLen);
		/* address */
		sscanf((char *)&str[ADDR_BASE], "%4x", &addr);
		/* type */
		sscanf((char *)&str[TYPE_BASE], "%2hx", &type);
		/* checksum */
		sscanf((char *)&str[DATA_BASE + byteLen * 2], "%2hx", &checksum);

		sum = byteLen + type + (addr & 0xff) + ((addr >> 8) & 0xff);

		switch (type) {
		case 0:
			loc_addr = _base_addr + addr;
			/* if this is the first line
			 * prepare structure with base address
			 * if previous address + line length didn't match new addr
			 * -> break -> store and start a new section
			 */
			if (next_addr != addr || is_first) {
				if (!is_first)
					_array_content.push_back(cnt);
				cnt.addr = loc_addr;
				cnt.length = 0;
				cnt.line_data.clear();
				is_first = false;
			}

			ptr = (char *)&str[DATA_BASE];
			if (_bit_data.size() < loc_addr + byteLen)
				_bit_data.resize(2*(loc_addr + byteLen));
			for (int i = 0; i < byteLen; i++, ptr += 2) {
				sscanf(ptr, "%2hx", &tmp);
				_bit_data[loc_addr + i] = (_reverseOrder)? reverseByte(tmp):tmp;
				sum += tmp;
				cnt.line_data.push_back(_bit_data[loc_addr+i]);
			}
			cnt.length += byteLen;
			next_addr = addr + byteLen;
			_bit_length += (byteLen * 8);
			break;
		case 1:
			if (cnt.length != 0)
				_array_content.push_back(cnt);
			return EXIT_SUCCESS;
			break;
		case 4:  // Extended Linear Address Record
			if (addr != 0 || byteLen != 2) {
				printError("Error: Wrong Extented Linear Address Record instruction");
				return EXIT_FAILURE;
			}
			sscanf((char *)&str[DATA_BASE], "%4hx", &tmp);
			_base_addr = ((uint32_t)tmp << 16);
			sum += ((tmp >> 0) & 0xff) + ((tmp >> 8) & 0xff);
			break;
		case 5:  // Start Linear Address Record.
			if (addr != 0 || byteLen != 4) {
				printError("Error: Wrong Start Linear Address Record instruction");
				return EXIT_FAILURE;
			}
			sscanf((char *)&str[DATA_BASE], "%4x", &addr);
			_base_addr = addr;
			for (uint8_t i = 0; i < 4; i++)
				sum += (addr >> (i * 8)) & 0xff;
			break;
		default:
			char mess[256];
			snprintf(mess, 256, "Error: unknown type %d %d %d\n", type,
				byteLen, addr);
			printError(mess);
			return EXIT_FAILURE;
		}

		if (checksum != (0xff&((~sum)+1))) {
			char mess[256];
			snprintf(mess, 256, "Error: wrong checksum %02x %02x %02x",
				checksum, sum, (0xff & ((~sum) + 1)));
			printError(mess);
			return EXIT_FAILURE;
		}
	}

	return EXIT_SUCCESS;
}
