/**
 * signature.c
 *
 * Reading / writing for the user signature region.
 * 
 * Copyright (c) 2022 Bigscreen, Inc.
 */

#include <asf.h>
#include <stdbool.h>
#include <string.h>
#include "signature.h"
#include "flash_mutex.h"

uint32_t sig_buffer[USER_SIG_SIZE_WORDS]; // temporary storage for the signature flash region contents
User_Signature_State sig_state = SIG_UNINITIALIZED; 

SemaphoreHandle_t sig_mutex = NULL; // Only allow one access to the raw memory at a time

// Reads the 512 byte user signature region in flash memory and copies it to a local ram buffer
static uint8_t read_user_signature(void) {
	uint32_t read_retval;
	
	// Make sure the semaphore exists
	if(sig_mutex == NULL) {
		sig_mutex = xSemaphoreCreateMutex();
	}
	Flash_Mutex_Lock();
	xSemaphoreTake(sig_mutex, portMAX_DELAY);
	// Disable interrupts for the read sequence. Do not want to have a context switch when Flash is unavailable.
	irqflags_t flags = cpu_irq_save();
	read_retval = efc_perform_read_sequence(EFC, EFC_FCMD_STUS, EFC_FCMD_SPUS, sig_buffer, USER_SIG_SIZE_WORDS);
	cpu_irq_restore(flags);
	xSemaphoreGive(sig_mutex);
	Flash_Mutex_Unlock();
	
	if(EFC_RC_OK == read_retval) {
		return pdPASS;
	} else {
		return pdFAIL;
	}
	
}

// Erases (resets to all 0xFF) the user signature region
// No need to use FreeRTOS semaphores as this function disables
// interrupts until it is completed. No context switching will occur.
static uint8_t erase_user_signature(void) {
	uint32_t erase_retval;
	
	Flash_Mutex_Lock();
	erase_retval = efc_perform_command(EFC, EFC_FCMD_EUS, 0);
	Flash_Mutex_Unlock();
	
	return (EFC_RC_OK == erase_retval);
}

// copies the current values in the local ram buffer to flash storage
// No need to use FreeRTOS semaphores as this function disables
// interrupts until it is completed. No context switching will occur.
static uint8_t write_user_signature_to_storage(void) {
	// Process is as follows:
	// (1) write the entire data storage section to the flash buffer
	// ...flash buffer is accessible at any flash page address
	// (2) perform the "write user signature" command
	
	uint32_t* first_flash_page; // can program to any valid page within Flash
	uint8_t flash_error = 0;
	
	Flash_Mutex_Lock();
	uint32_t cur_int_status = __get_PRIMASK();
	if(cur_int_status == 0 ){
		__disable_irq();
	}
	
	first_flash_page = (uint32_t*)(IFLASH_ADDR);

	// Copy bytes to the destination flash page buffer
	// Must be performed in 32 bit writes. 8 or 16 bit writes could result in weird stuff
	// Also, writes must be continuous (contiguous?) from lower to higher address.
	// So we can't use memcpy. Probably does random weird stuff in write sequence.
	
	// Making a (possibly stupid) assumption that data is at least one page (512 bytes) long
	for(uint16_t i = 0; i < (USER_SIG_SIZE_BYTES / sizeof(uint32_t)); i++) {
		first_flash_page[i] = sig_buffer[i];
	}
	
	// Perform program function
	flash_error = efc_perform_command(EFC, EFC_FCMD_WUS, 0);
	
	if(cur_int_status == 0) {
		__enable_irq();
	}
	Flash_Mutex_Unlock();
	
	if (EFC_RC_OK == flash_error) {
		return pdPASS;
	}else{
		return pdFAIL;
	}
}	

// Calculates a CRC8 checksum value over any length of data
uint8_t crc8(uint8_t initcrc, const uint8_t* input_data, uint32_t len) {
	uint32_t i;
	uint8_t j;
	const uint8_t* l_input;
	
	l_input = input_data;
	for(i = 0; i < len; i++) {
		initcrc ^= *l_input;
		l_input++;
		
		for(j = 0; j < 8; j++) {
			if(initcrc & 0x80u) {
				initcrc = (initcrc << 1) ^ CRC8POLY;
				} else {
				initcrc = (initcrc << 1);
			}
		}
	}
	
	return initcrc;
}


// Returns a pointer to the signature buffer. User code cannot modify this buffer, should instead be copied and modified
// Reads from Flash storage before returning a pointer to the local buffer
const User_Signature_T* get_signature(void) {
	if((sig_state == SIG_UNINITIALIZED) || (sig_state == SIG_OVERWRITTEN)) {
		if(pdPASS == read_user_signature()) {
			sig_state = SIG_VALID;
			return (const User_Signature_T*) sig_buffer;
		} else {
			return NULL; // User code should check for null before using the buffer.
		}
	} else {
		// no need to re-read flash, can simply return the signature
		return (const User_Signature_T*) sig_buffer;
	}
}
const uint32_t* get_signature_raw(void) {
	if((sig_state == SIG_UNINITIALIZED) || (sig_state == SIG_OVERWRITTEN)) {
		if(pdPASS == read_user_signature()) {
			sig_state = SIG_VALID;
			return (const uint32_t*) sig_buffer;
		} else {
			return NULL; // User code should check for null before using the buffer.
		}
	} else {
		// no need to re-read flash, can simply return the signature
		return (const uint32_t*) sig_buffer;
	}
}

uint8_t update_signature_raw(uint8_t* buf, uint32_t offset, uint32_t len) {
	uint8_t* sig_buffer_as_bytes = (uint8_t*) sig_buffer;
	if((buf != NULL) && (offset < USER_SIG_SIZE_BYTES) && ((offset + len) <= USER_SIG_SIZE_BYTES)) {
		memcpy(&(sig_buffer_as_bytes[offset]), buf, len);
		sig_state = SIG_OVERWRITTEN;
		return pdPASS;
	}
	
	return pdFAIL;
}


// Saves the user signature back to flash
uint8_t save_signature(void) {
	uint8_t retval = pdFAIL;
	
	// Point at the buffer as a signature struct
	User_Signature_T* sig = (User_Signature_T*) sig_buffer;
	// Only allow saving flash signature region if we have a ram buffer and flash regions that are
	// different from each other, and the ram buffer has been recently overwritten
	if(sig_state == SIG_OVERWRITTEN) {
		// Sanity checks: magic number and crc
		if(check_signature(sig)) {
			// all good, we can do the flash write
			retval = erase_user_signature();
			if(pdPASS == retval) {
				retval = write_user_signature_to_storage();
			}
		}
	}
	
	return retval;
}

uint8_t save_signature_raw(void) {
	uint8_t retval = pdFAIL;

	// Only allow saving flash signature region if we have a ram buffer and flash regions that are
	// different from each other, and the ram buffer has been recently overwritten
	if(sig_state == SIG_OVERWRITTEN) {
		retval = erase_user_signature();
		if(pdPASS == retval) {
			retval = write_user_signature_to_storage();
		}
	}
	return retval;		
}

bool check_signature(const User_Signature_T* sig) {
	// Confirms that the signature block is valid:
	// 1. Pointer is not null.
	// 2. Magic number is correct.
	// 3. CRC-8 is valid.
	if(NULL == sig) {
		return false;
	}
	if(SIG_MAGIC != (sig->magic_num)) {
		return false;
	}
	if(crc8(0xFFu, (const uint8_t*)sig, USER_SIG_STRUCT_SIZE - 1) != sig->crc8){
		return false;
	}
	
	return true;
}

static uint16_t sigv2_find_tag_loc(uint8_t tag) {
	uint8_t* sig_bytes = (uint8_t*) sig_buffer;
	uint16_t sig_ptr = 0;
	uint8_t data_length;
	uint8_t entry_length;
	while(sig_ptr < USER_SIG_SIZE_BYTES){
		if(SigTag_Invalid == sig_bytes[sig_ptr]) {return 0xFFFFu;} // Reached the end of programmed signature, bust out
		data_length = sig_bytes[sig_ptr+1];
		entry_length = data_length + 3; // 3 more bytes: tag, length, and CRC
		if(sig_bytes[sig_ptr] == tag) {
			// Check the CRC on this entry
			if(crc8(0xFFu, &(sig_bytes[sig_ptr]), entry_length - 1) == sig_bytes[sig_ptr+entry_length-1]) {
				// it's correct! we can return our location
				return sig_ptr;
			}
		}
		
		sig_ptr += entry_length;
	}
	
	// Wasn't found. Return a false value (max 16 bit int)
	return 0xFFFFu;
}

bool sigv2_find_tag(uint8_t tag, uint8_t* retval) {
	// Returns true if tag was found, false if it was not.
	// If the tag was found, saves the data to retval.
	// Make sure the array that retval is pointing to
	// is big enough to hold the data.
	// Does not change anything in retval if the tag wasn't found.
	uint8_t* sig_bytes = (uint8_t*)sig_buffer;
	uint16_t tag_ptr = sigv2_find_tag_loc(tag);
	if(tag_ptr >= USER_SIG_SIZE_BYTES) {
		return false;
	}
	
	// tag_ptr+1 is length, and data starts at tag_ptr+2
	memcpy(&(retval[0]), &(sig_bytes[tag_ptr+2]), sig_bytes[tag_ptr+1]);
	return true;
}


bool sigv2_tag_exists(uint8_t tag) {
	uint16_t tag_ptr = sigv2_find_tag_loc(tag);
	if(tag_ptr >= USER_SIG_SIZE_BYTES) {
		// not found result is 65535, so this block would run
		return false;
	}
	return true;
}

uint8_t sigv2_tag_length(uint8_t tag) {
	uint8_t* sig_bytes = (uint8_t*)sig_buffer;
	uint16_t tag_ptr = sigv2_find_tag_loc(tag);
	if(tag_ptr >= USER_SIG_SIZE_BYTES) {
		return 0; // zero length isn't a real entry, so this can be used as an "exists" check too
	}
	return sig_bytes[tag_ptr+1];
	
}

uint16_t sigv2_build_signature(uint16_t sig_ptr, uint8_t tag, uint8_t length, uint8_t* data) {
	// Called repeatedly to build the signature buffer.
	// Once all the tags have been added, save the buffer into Flash using "save_signature_raw"
	// Usage - 
	// On the first call, set "sig_ptr" to zero. This means the first location in the signature flash
	// will be used. This function's return value is the new pointer location (first byte just
	// after the tag that was just added). On the next and subsequent calls of this function with the 
	// remaining tags, use the previous call return value as "sig_ptr". 
	// Return value 0xFFFF (65535) is used for a failure
	
	// First check that the tag we're trying to save isn't invalid
	if(SigTag_Invalid == tag) {
		return 0xFFFFu;
	}
	
	// Also check that we can fit this tag in the buffer.
	uint8_t tag_length = length + 3; // tag, length, data bytes, and CRC

	if((sig_ptr + tag_length) > USER_SIG_SIZE_BYTES) {
		return 0xFFFFu;
	}
	
	// Since it can fit, we add it
	sig_state = SIG_OVERWRITTEN;
	uint8_t* sig_bytes = (uint8_t*) sig_buffer;
	sig_bytes[sig_ptr] = tag;
	sig_bytes[sig_ptr+1] = length;
	memcpy(&(sig_bytes[sig_ptr+2]), data, length);
	sig_bytes[sig_ptr+length+2] = crc8(0xFFu, &(sig_bytes[sig_ptr]), tag_length - 1);
	
	return sig_ptr + length + 3;
}

bool sigv2_add_new_tag(uint8_t tag, uint8_t length, uint8_t* data) {
	uint16_t sig_ptr;
	
	// First ensure that the current buffer is actually what's in Flash
	if(sig_state != SIG_VALID) {
		if(pdPASS != read_user_signature()) {
			return false; // failed since we couldn't read the signature region
		}
	}
	
	// Get the first blank spot and stick this tag on the end
	sig_ptr = sigv2_find_first_blank();
	if(sig_ptr >= USER_SIG_SIZE_BYTES) {
		return false; // no space available
	}
	
	sig_ptr = sigv2_build_signature(sig_ptr, tag, length, data);
	save_signature_raw();
	return (sig_ptr != 0xFFFFu); 
}

uint16_t sigv2_find_first_blank(void) {
	// Returns the location of the first blank location. This is represented by a tag with 0xFF.
	// Note that we can't just search for the byte value 0xFF, because it could be a valid
	// length, data, or CRC. Only 0xFF in a tag location is considered empty space.
	// Also returns a tag location if the CRC check is bad. This could be the case if random 
	// data is in the signature for whatever reason.
	
	uint8_t* sig_bytes = (uint8_t*) sig_buffer;
	uint16_t sig_ptr = 0;
	uint8_t data_length;
	
	while(sig_ptr < USER_SIG_SIZE_BYTES) {
		if(SigTag_Invalid == sig_bytes[sig_ptr]) {
			return sig_ptr;
		}
		data_length = sig_bytes[sig_ptr+1];
		if(data_length + sig_ptr + 3u >= USER_SIG_SIZE_BYTES) {
			// Data was too long, so this entry doesn't fit in the signature
			// counts as invalid entry. Return it as a blank.
			return sig_ptr;
		}
		if(sig_bytes[sig_ptr + data_length + 2] != crc8(0xFFu, &(sig_bytes[sig_ptr]), data_length + 2)) {
			return sig_ptr; // invalid CRC, this is also considered a blank
		}
		sig_ptr += data_length + 3; // tag, length, crc bytes and the length of data bytes
	}
	// got here if the entire signature is full
	return 0xFFFFu;
}

void sigv2_upgrade_from_sigv1(void) {
	// Reads the signature, checks if it's a V1 signature, and if so
	// reloads it as a V2 signature and saves it.
	
	const User_Signature_T* sigv1 = get_signature();

	if(check_signature(sigv1)) {
		// start copying into the new signature format
		
		// We need a scratch space for this, as we begin writing the new signature it will get clobbered.
		// This is a fairly large stack usage, should probably run this function in main() before FreeRTOS
		// scheduler is started. That way we have a much larger stack limit to work with.
		User_Signature_T sigv1_copy;
		memcpy(&sigv1_copy, sigv1, sizeof(User_Signature_T));
		
		// Completely clear out the signature region
		memset(sig_buffer, 0xFFu, USER_SIG_SIZE_BYTES);
		
		uint16_t sig_ptr = 0;
		sig_ptr = sigv2_build_signature(sig_ptr, SigTag_Serial, 16, (uint8_t*)&(sigv1_copy.serial_number));
		if(sig_ptr != 0xFFFFu) {
			sig_ptr = sigv2_build_signature(sig_ptr, SigTag_RGB_Color, 3, (uint8_t*)&(sigv1_copy.default_color));
		}
		if(sig_ptr != 0xFFFFu) {
			sig_ptr = sigv2_build_signature(sig_ptr, SigTag_Fan_Speed, 1, (uint8_t*)&(sigv1_copy.default_fan_speed));
		}
		if(sig_ptr != 0xFFFFu) {
			sig_ptr = sigv2_build_signature(sig_ptr, SigTag_Prox_Disable, 1, (uint8_t*)&(sigv1_copy.prox_disabled));
		}
		if(sig_ptr != 0xFFFFu) {
			sig_ptr = sigv2_build_signature(sig_ptr, SigTag_Linkbox_v1, 1, (uint8_t*)&(sigv1_copy.linkbox_is_v1));
		}
		if(sig_ptr != 0xFFFFu) {
			// Got all the variables added, now we can save
			save_signature_raw();
		}
	}
	
}