// Filename : controller.cpp
//
// This file was initially generated by SpaceStudio but is meant to be modified
// by the user.
#include "controller.h"
#include "spacecomp_pre_cpp.h"

#define INPUT_STREAM_LENGTH 20

controller::controller(SPACECOMP_CTOR_PARAMS(controller, INDEX))
	: abstract_module(SPACECOMP_MBASE_ARGS(controller, INDEX))
	, SPACECOMP_START_ILIST(controller, INDEX)
	// Other initializers may go here
{
	SPACECOMP_THREAD(controller, INDEX, thread);
	request_stack(16 * 1024);
}

void controller::thread(SPACECOMP_THREAD_PARAMS(controller, INDEX, thread)) {
	spacecomp_thread_initialize();
	int32_t operand1;
	int32_t operand2;
	int32_t result;
	operation_t operation;
	while (1) {
		const bool initializing = spacecomp_thread_loop_start();
		if (initializing) {}
		read_data(&operand1, &operand2, &operation);
		result = delegate_operation(operation, operand1, operand2);
		send_result(result);
	}
}

void controller::send_result(int32_t data) {
	if (is_verbose())
		SpacePrint("Result:%d\n", data);
#ifdef SPACE_ARCHITECTURE_IMPLEMENTATION
	int32_t i;
	uint32_t power;
	uint32_t digit;

	// Negative number
	if (data < 0) {
		send_character('-');
		data *= -1;
	}

	for(i=get_order(data);i>=0;i--) {
		power = pow(10, i);
		digit = data/power;
		data -= digit*power;
		send_character(itoa(digit));
	}

	send_character(0xD);
#endif
}

void controller::send_character(uint8_t character) {
	uint32_t data = character;
	ModuleWrite(OUTPUT_WRITER0_ID, SPACE_BLOCKING, data);
}

uint8_t controller::read_input() {
	uint32_t data;
	ModuleRead(INPUT_READER0_ID, SPACE_BLOCKING, data);
	return (uint8_t)data;
}

void controller::read_data(int32_t* operand1, int32_t* operand2, operation_t* operation) {
	uint8_t raw[INPUT_STREAM_LENGTH];

	read_input_stream(raw);
	*operand1 = get_first_operand(raw);
	*operand2 = get_second_operand(raw);
	*operation = get_operation(raw);
}

int32_t controller::get_first_operand(uint8_t* ptr) {
	uint32_t length = get_operation_position(ptr);
	uint8_t* trimPtr = trim_spaces(ptr, &length);
	int32_t number = character_to_number(trimPtr, length);

	if (is_verbose())
		SpacePrint("First operand:%d\n", number);

	return number;
}

int32_t controller::get_second_operand(uint8_t* ptr) {
	uint32_t length = get_lf_position(ptr) - get_operation_position(ptr) - 1;
	uint8_t* trimPtr = trim_spaces(ptr + get_operation_position(ptr) + 1, &length);
	int32_t number = character_to_number(trimPtr, length);

	if (is_verbose())
		SpacePrint("Second operand:%d\n", number);

	return number;
}

int32_t controller::character_to_number(uint8_t* ptr, uint32_t length) {
	unsigned char i;
	unsigned long number = 0;
	bool is_negative_number = false;

	 for(i=0;i<length;i++) {
		 if (is_negative(ptr[i])) {
			 is_negative_number = true;
			 continue;
		 }
		number += atoi(ptr[i]) * pow(10, length-i-1);
	 }

	 return is_negative_number ? number * -1 : number;
}

bool controller::is_negative(uint8_t character) {
	if (character == '-')
		return true;
	return false;
}

bool controller::is_digit(uint8_t character) {
	if (('0' <= character) && (character <= '9'))
		return true;
	return false;
}

bool controller::is_space(char character) {
	if (character == ' ')
		return true;
	return false;
}

bool controller::is_operation_character(uint8_t character) {
	if ((character == '+') || (character == '-') || (character == '/') || (character == 'x') || (character == 'X') || (character == '*') || (character == 'z'))
		return true;
	return false;
}

uint32_t controller::pow(int32_t base, int32_t exponent) {
	uint32_t result = 1;
	int32_t i=0;
	for(i=0;i<exponent;i++)
		result *= base;
	return result;
}

int32_t controller::get_operation_position(uint8_t* ptr) {
	bool is_first_character = true;
	int32_t i=0;
	for(i=0;i<INPUT_STREAM_LENGTH;i++) {
		if (!is_first_character && is_operation_character(ptr[i]))
			return i;

		if (is_first_character && is_digit(ptr[i]))
			is_first_character = false;
	}

	return -1;
}

void controller::read_input_stream(uint8_t* ptr) {
	int position;
	sanitize(ptr, &position);
	bool is_overflow = false;

	// Validate input stream
    while(!is_valid_command(ptr) || is_division_by_zero(ptr)) {
    	// Prevent to have a string with no operation or with invalid operands
    	if (is_lf_detected(ptr) && (!is_operation_detected(ptr) || !is_operand1_detected(ptr) || !is_operand2_detected(ptr)))
    	{
    		sanitize(ptr, &position);
    		display_message("Invalid input string\n");
    		is_overflow = false;
    	}

    	if (is_division_by_zero(ptr)) {
    		sanitize(ptr, &position);
    		display_message("Division by zero detected\n");
    		is_overflow = false;
    	}

		ptr[position++] = read_input();

		// Loop in array
		if (position == INPUT_STREAM_LENGTH) {
			sanitize(ptr, &position);
			if (!is_overflow)
				display_message("String too long, try again\n");
			is_overflow = true;
		}
    }
}

bool controller::is_valid_command(uint8_t* ptr) {
	return is_lf_detected(ptr) && is_operation_detected(ptr) && is_operand1_detected(ptr) && is_operand2_detected(ptr);
}

bool controller::is_division_by_zero(uint8_t* ptr) {
	return is_valid_command(ptr) && get_second_operand(ptr) == 0 && get_operation(ptr) == DIVIDE;
}

void controller::sanitize(uint8_t* ptr, int* position) {
	*position = 0;
	init(ptr);
}

bool controller::is_operation_detected(uint8_t* ptr) {
	return get_operation_position(ptr) != -1;
}

bool controller::is_operand1_detected(uint8_t* ptr) {
	uint32_t length = get_operation_position(ptr);
	uint8_t* trimPtr = trim_spaces(ptr, &length);
	return is_number(trimPtr, length);
}

bool controller::is_operand2_detected(uint8_t* ptr) {
	uint32_t length = get_lf_position(ptr) - get_operation_position(ptr) - 1;
	uint8_t* trimPtr = trim_spaces(ptr + get_operation_position(ptr) + 1, &length);
	return is_number(trimPtr, length);
}

bool controller::is_number(uint8_t* ptr, uint32_t length) {
	bool is_first_character = true;
	int i;

	for(i=0;i<length;i++) {
		if (is_space(ptr[i]))
			return false;

		if (is_first_character && !is_digit(ptr[i]) && !is_negative(ptr[i]))
			return false;

		if (!is_first_character && !is_digit(ptr[i]))
			return false;

		is_first_character = false;
	}

	return true;
}

bool controller::is_lf_detected(uint8_t* ptr) {
	return get_lf_position(ptr) != -1;
}

int32_t controller::get_lf_position(uint8_t* ptr) {
	int32_t i;
	for(i=0;i<INPUT_STREAM_LENGTH;i++)
		if (is_lf(ptr[i]))
			return i;
	return -1;
}

bool controller::is_lf(uint8_t character) {
	return character == 0xD || character == 0xA;
}

uint8_t* controller::trim_spaces(uint8_t* ptr, uint32_t* length) {
	int i;
	int count = 0;

	for(i=0;i<*length;i++)
		if (is_space(ptr[i]))
			continue;
		else
			break;

	uint8_t* tmpPtr = ptr + i;
	*length -= i;

	// Trim leading spaces
	for(i=*length-1;i>=0;i--) {
		if (is_space(tmpPtr[i]))
			count++;
		else
			break;
	}

	*length -= count;

	return tmpPtr;
}

operation_t controller::get_operation(uint8_t* ptr) {
	uint8_t operation = ptr[get_operation_position(ptr)];

	if (operation == '+') {
		if (is_verbose())
			SpacePrint("Addition detected!\n");
		return ADD;
	}

	if (operation == '-') {
		if (is_verbose())
			SpacePrint("Subtraction detected!\n");
		return SUBTRACTION;
	}

	if ((operation == '*') || (operation == 'x') || (operation == 'X')) {
		if (is_verbose())
			SpacePrint("Multiplication detected!\n");
		return MULTIPLY;
	}

	if (operation == 'z') {
		if (is_verbose())
			SpacePrint("Matrix multiplication detected!\n");
		return MATRIX_MULTIPLICATION;
	}

	if (is_verbose())
		SpacePrint("Division detected!\n");
	return DIVIDE;
}

int32_t controller::delegate_operation(operation_t operation, int32_t operand1, int32_t operand2) {
	int32_t result=0;
    switch (operation) {
        case ADD: {
        	send_addition_operand(operand1);
        	send_addition_operand(operand2);
        	result = read_addition_result();
			#ifdef SPACE_SIMULATION
				SpacePrint("%d + %d = %d\n", operand1, operand2, result);
			#endif
        	break;
        }
        case SUBTRACTION: {
        	send_subtraction_operand(operand1);
        	send_subtraction_operand(operand2);
        	result = read_subtraction_result();
			#ifdef SPACE_SIMULATION
				SpacePrint("%d - %d = %d\n", operand1, operand2, result);
			#endif
        	break;
        }
        case MULTIPLY: {
        	send_multiplication_operand(operand1);
        	send_multiplication_operand(operand2);
        	result = read_multiplication_result();
			#ifdef SPACE_SIMULATION
				SpacePrint("%d * %d = %d\n", operand1, operand2, result);
			#endif
        	break;
        }
        case DIVIDE: {
        	send_division_operand(operand1);
        	send_division_operand(operand2);
        	result = read_division_result();
			#ifdef SPACE_SIMULATION
				SpacePrint("%d / %d = %d\n", operand1, operand2, result);
			#endif
        	break;
        }
        case MATRIX_MULTIPLICATION: {
        	send_matrix(operand1);	/* operand1 refers to an index */
        	send_matrix(operand2); /* operand2 refers to an index */
        	read_matrix_result();
			#ifdef SPACE_SIMULATION
        		SpacePrint("Matrix mult (%d, %d) finished\n", operand1, operand2);
			#endif
        	break;
        }
    }

    return result;
}

void controller::send_addition_operand(int32_t data) {
	ModuleWrite(ADDER0_ID, SPACE_BLOCKING, data);
}

void controller::send_subtraction_operand(int32_t data) {
	ModuleWrite(SUBTRACTOR0_ID, SPACE_BLOCKING, data);
}

void controller::send_multiplication_operand(int32_t data) {
	ModuleWrite(MULTIPLIER0_ID, SPACE_BLOCKING, data);
}

void controller::send_division_operand(int32_t data) {
	ModuleWrite(DIVIDER0_ID, SPACE_BLOCKING, data);
}

void controller::send_matrix(uint32_t index) {
#ifdef SPACE_SIMULATION
	if (is_verbose()) {
		SpacePrint("Controller Sending \n");
		for (unsigned i = 0; i < MATRIX_ROWS; ++i){
			SpacePrint("\t");
			for (unsigned j = 0; j < MATRIX_COLUMNS; ++j) {
				SpacePrint("%d ",matrix_data[index][i * MATRIX_ROWS + j]);
			}
			SpacePrint("\n");
		}
		SpacePrint("\n");
	}
#endif
#if EXPLICIT_DMA
	for(int i=0;i<MATRIX_COUNT;i++) {
		if (index == i)
			Memory2Stream(MATRIX_RAM_ID, MATRIX_BYTES*index, MATRIX_MULT0_ID, SPACE_BLOCKING, MATRIX_BYTES);
	}
#else
	ModuleWrite(MATRIX_MULT0_ID, SPACE_BLOCKING, matrix_data[index], MATRIX_ELEMENTS);
#endif
}

int32_t controller::read_addition_result() {
	int32_t data;
	ModuleRead(ADDER0_ID, SPACE_BLOCKING, data);
    return data;
}

int32_t controller::read_subtraction_result() {
	int32_t data;
	ModuleRead(SUBTRACTOR0_ID, SPACE_BLOCKING, data);
    return data;
}

int32_t controller::read_multiplication_result() {
	int32_t data;
	ModuleRead(MULTIPLIER0_ID, SPACE_BLOCKING, data);
    return data;
}

int32_t controller::read_division_result() {
	int32_t data;
	ModuleRead(DIVIDER0_ID, SPACE_BLOCKING, data);
    return data;
}

void controller::read_matrix_result() {
#if EXPLICIT_DMA
	Stream2Memory(MATRIX_MULT0_ID, MATRIX_RAM_ID, MATRIX_BYTES*10, SPACE_BLOCKING, MATRIX_BYTES);
#else
	ModuleRead(MATRIX_MULT0_ID, SPACE_BLOCKING, m_matrix_result, MATRIX_ELEMENTS);
#endif
#ifdef SPACE_SIMULATION
	if (is_verbose()) {
	SpacePrint("Controller Result \n");
		for (unsigned i = 0; i < MATRIX_ROWS; ++i){
			SpacePrint("\t");
			for (unsigned j = 0; j < MATRIX_COLUMNS; ++j) {
				SpacePrint("%d ",m_matrix_result[i * MATRIX_ROWS + j]);
			}
			SpacePrint("\n");
		}
		SpacePrint("\n");
	}
#endif
}

void controller::init(uint8_t* ptr) {
	int i=0;
	for(i=0;i<INPUT_STREAM_LENGTH;i++)
		ptr[i] = 0;
}

int32_t controller::atoi(uint8_t c) {
	return c - 0x30;
}

uint8_t controller::itoa(uint32_t x) {
	return x + 0x30;
}

uint32_t controller::get_order(uint32_t data) {
	if (data == 0)
		return 0;

	uint32_t order = 0;

	while (data >= pow(10, order)) {
		order++;
	}

	return --order;
}

void controller::display_message(const char* const message) {
	int i=0;
	do {
		send_character(message[i]);
	} while(!is_lf(message[i++]));

	send_character(0xD);
}

#include "spacecomp_post_cpp.h"
