//
// Created by nano on 4/26/25.
//

#include "communication.h"

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


//TODO: ADD DOCUMENTATION

struct sp_port * port;
struct sp_event_set *event_data_ready;
struct sp_event_set *event_ready_for_tx;

/**
	 * Initializes the serial communication for the usb -> uart board
	 */
void comm_init_communication() {
	struct sp_port **port_list;
	comm_get_port_list(&port_list);
	int portnr=0;
	comm_list_serial_ports(port_list);
	printf("Chose a port: ");
	scanf("%d",&portnr);
	sp_copy_port(port_list[portnr], &port);
	sp_free_port_list(port_list);
	comm_open_port();
}

void comm_get_port_list(struct sp_port ***port_list) {
	enum sp_return result = sp_list_ports(port_list);
	if (result != SP_OK) {
		printf("Error listing serial ports\n");
		exit(-1);
	}
}

void comm_list_serial_ports(struct sp_port **port_list) {
	printf("Port list:\n");
	for (int i = 0; port_list[i] != NULL; i++) {
		char *port_name = sp_get_port_name(port_list[i]);
		printf("Port %d: %s\n", i, port_name);
	}
}

void comm_open_port() {
	/*
	 * Opens communication to the selected serial port and sets the correct parameters for the zigbee carrier board.
	 * Also add event to see when data arrives.
	 */
	check(sp_open(port, SP_MODE_READ_WRITE));
	check(sp_set_baudrate(port, 9600));
	check(sp_set_bits(port, 8));
	check(sp_set_parity(port, SP_PARITY_NONE));
	check(sp_set_stopbits(port, 1));

	check(sp_new_event_set(&event_data_ready));
	check(sp_new_event_set(&event_ready_for_tx));

	sp_add_port_events(event_data_ready, port, SP_EVENT_RX_READY);
	sp_add_port_events(event_ready_for_tx, port, SP_EVENT_TX_READY);
}

/**
	 * Ends the serial communication for the usb -> uart board
	 */
void comm_end_communication() {
	check(sp_close(port));
	sp_free_port(port);
}

int comm_await_data_ready(int timeout) {
	// Await data from the robot
	return (sp_wait(event_data_ready, timeout) == SP_OK);
}

int comm_await_ready_for_tx(int timeout) {
	return (sp_wait(event_ready_for_tx, timeout) == SP_OK);
}

comm_result comm_nonblocking_read(byte *data, int amount_of_bytes) {
	//read available data
	//TODO: TEST ACTUAL READ LOGIC
	int recv_bytes = sp_nonblocking_read(port, data, amount_of_bytes);
	if (recv_bytes == amount_of_bytes) {
		return ALL_BYTES;
	}
	if (recv_bytes >= 0) {
		return NOT_ENOUGH_BYTES;
	}
	return ERROR;
}

comm_result comm_blocking_read(byte *data, int amount_of_bytes, int timeout) {
	//read available data
	//TODO: TEST ACTUAL READ LOGIC
	int recv_bytes = sp_blocking_read(port, data, amount_of_bytes, timeout);
	if (recv_bytes == amount_of_bytes) {
		return ALL_BYTES;
	}
	if (recv_bytes >= 0) {
		return NOT_ENOUGH_BYTES;
	}
	return ERROR;
}

comm_result comm_blocking_write(byte *data, int amount_of_bytes, int timeout) {
	// we are writing 8 bit bytes to the fpga anyways and pc -> fpga only needs to tell what do to next, can be fit in 8 bits
	//TODO: TEST ACTUAL WRITE LOGIC
	int sent_bytes = sp_blocking_write(port, data, amount_of_bytes, timeout);
	if (sent_bytes == amount_of_bytes) {
		return ALL_BYTES;
	}
	if (sent_bytes >= 0) {
		return NOT_ENOUGH_BYTES;
	}
	return ERROR;
}

comm_result comm_nonblocking_write(byte *data, int amount_of_bytes) {
	// we are writing 8 bit bytes to the fpga anyways and pc -> fpga only needs to tell what do to next, can be fit in 8 bits
	//TODO: TEST ACTUAL WRITE LOGIC
	int sent_bytes = sp_nonblocking_write(port, data, amount_of_bytes);
	if (sent_bytes == amount_of_bytes) {
		return ALL_BYTES;
	}
	if (sent_bytes >=0) {
		return NOT_ENOUGH_BYTES;
	}
	return ERROR;
}

int comm_output_waiting() {
	return sp_output_waiting(port);
}

/** Helper function for error handling. Taken from libserialport examples */
int check(enum sp_return result)
{
	/* For this example we'll just exit on any error by calling abort(). */
	char *error_message;

	switch (result) {
		case SP_ERR_ARG:
			printf("Error: Invalid argument.\n");
		abort();
		case SP_ERR_FAIL:
			error_message = sp_last_error_message();
		printf("Error: Failed: %s\n", error_message);
		sp_free_error_message(error_message);
		abort();
		case SP_ERR_SUPP:
			printf("Error: Not supported.\n");
		abort();
		case SP_ERR_MEM:
			printf("Error: Couldn't allocate memory.\n");
		abort();
		case SP_OK:
			default:
					return result;
	}
}
