#include <stdbool.h>
#include <stdio.h>

#include "stm32f407xx_esp8266.h"
#include "wifi_passthrough.h"

#define WIFI_BRIDGE_CHUNK_SIZE  64U

static HAL_StatusTypeDef WiFiPassthrough_Connect(void) {
	HAL_StatusTypeDef status;

	printf("ESP8266: Checking AT interface\r\n");
	status = ESP8266_Test(10U);
	if (status != HAL_OK) {
		return status;
	}

	printf("ESP8266: Selecting station mode\r\n");
	status = ESP8266_SetStationMode();
	if (status != HAL_OK) {
		return status;
	}

	printf("ESP8266: Joining access point '%s'\r\n",
	WIFI_PASSTHROUGH_SSID);
	status = ESP8266_JoinAccessPoint(WIFI_PASSTHROUGH_SSID,
	WIFI_PASSTHROUGH_PASSWORD);
	if (status != HAL_OK) {
		return status;
	}

	status = ESP8266_SetSingleConnection();
	if (status != HAL_OK) {
		return status;
	}

	printf("ESP8266: Connecting to %s:%u\r\n",
	WIFI_PASSTHROUGH_SERVER_IP, (unsigned int) WIFI_PASSTHROUGH_SERVER_PORT);
	status = ESP8266_ConnectTcp(WIFI_PASSTHROUGH_SERVER_IP,
	WIFI_PASSTHROUGH_SERVER_PORT);
	if (status != HAL_OK) {
		return status;
	}

	status = ESP8266_EnterTransparentMode();
	if (status == HAL_OK) {
		printf("ESP8266: Transparent TCP mode ready\r\n");
	}

	return status;
}

static bool WiFiPassthrough_UpdateClosedState(uint8_t data) {
	static const char closed_token[] = "CLOSED";
	static size_t token_index;

	if (data == (uint8_t) closed_token[token_index]) {
		token_index++;
	} else {
		token_index = (data == (uint8_t) closed_token[0]) ? 1U : 0U;
	}

	if (token_index == (sizeof(closed_token) - 1U)) {
		token_index = 0U;
		return true;
	}

	return false;
}

static HAL_StatusTypeDef WiFiPassthrough_ForwardConsole(
		ringBuffer_t *console_rx_buffer) {
	uint8_t data[WIFI_BRIDGE_CHUNK_SIZE];
	uint16_t length = 0U;

	if (console_rx_buffer == NULL) {
		return HAL_OK;
	}

	while ((length < sizeof(data))
			&& (RingBuff_Read(console_rx_buffer, &data[length]) == 0U)) {
		length++;
	}

	if (length == 0U) {
		return HAL_OK;
	}

	return ESP8266_Transmit(data, length, 1000U);
}

static bool WiFiPassthrough_ForwardNetwork(UART_HandleTypeDef *console_uart) {
	uint8_t data[WIFI_BRIDGE_CHUNK_SIZE];
	uint16_t length = 0U;
	bool connection_closed = false;

	while ((length < sizeof(data)) && (ESP8266_ReadByte(&data[length]) == 0U)) {
		if (WiFiPassthrough_UpdateClosedState(data[length])) {
			connection_closed = true;
		}
		length++;
	}

	if ((console_uart != NULL) && (length != 0U)) {
		(void) HAL_UART_Transmit(console_uart, data, length, 1000U);
	}

	return connection_closed;
}

#if WIFI_PASSTHROUGH_ENABLE_TEST_PAYLOAD
static HAL_StatusTypeDef WiFiPassthrough_SendTestPayload(void) {
	static const uint8_t test_line[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n";
	uint32_t line;

	for (line = 0U; line < WIFI_PASSTHROUGH_TEST_LINE_REPEAT; line++) {
		HAL_StatusTypeDef status = ESP8266_Transmit(test_line,
				(uint16_t) (sizeof(test_line) - 1U), 1000U);
		if (status != HAL_OK) {
			return status;
		}
	}

	return HAL_OK;
}
#endif

void WiFiPassthrough_Run(UART_HandleTypeDef *console_uart,
		ringBuffer_t *console_rx_buffer) {
	uint32_t next_test_tick = 0U;
	bool connection_closed = false;

	while (WiFiPassthrough_Connect() != HAL_OK) {
		printf("ESP8266: Connection failed; resetting and retrying\r\n");
		ESP8266_Reset();
		HAL_Delay(1000U);
	}

	next_test_tick = HAL_GetTick();

	for (;;) {
		if (WiFiPassthrough_ForwardConsole(console_rx_buffer) != HAL_OK) {
			connection_closed = true;
		}

		if (WiFiPassthrough_ForwardNetwork(console_uart)) {
			connection_closed = true;
		}

#if WIFI_PASSTHROUGH_ENABLE_TEST_PAYLOAD
		if ((int32_t) (HAL_GetTick() - next_test_tick) >= 0) {
			if (WiFiPassthrough_SendTestPayload() != HAL_OK) {
				connection_closed = true;
			}
			next_test_tick = HAL_GetTick() +
			WIFI_PASSTHROUGH_TEST_INTERVAL_MS;
		}
#endif

		if (connection_closed) {
			printf("\r\nESP8266: TCP connection closed; reconnecting\r\n");
			/* A reset is deterministic whether the module is still in transparent
			 * mode or has already returned to command mode after CLOSED. */
			ESP8266_Reset();

			while (WiFiPassthrough_Connect() != HAL_OK) {
				ESP8266_Reset();
				HAL_Delay(1000U);
			}

			connection_closed = false;
			next_test_tick = HAL_GetTick();
		}

		HAL_Delay(1U);
	}
}
