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

#include "main.h"
#include "cmsis_os.h"

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

// System clock
uint32_t HCLK_freq;
uint32_t PCLK1_freq;
uint32_t PCLK2_freq;
void SystemClock_Config(void);

// LED: GPIO_PIN_13
static void LED_GPIO_Init(void);

UART_HandleTypeDef huart1;
static void USART1_UART_Init(void);

// Override for printf function
int __io_putchar(int ch) {
	HAL_UART_Transmit(&huart1, (uint8_t*) &ch, 1,
	HAL_MAX_DELAY);
	return ch;
}

extern ringBuffer_t bufferU1;
extern ringBuffer_t bufferU3;

static uint8_t uart1_rx_byte;
static uint8_t esp8266_rx_byte;

int main(void) {

	HAL_Init();
	SystemClock_Config();
	LED_GPIO_Init();

	USART1_UART_Init();

	if (ESP8266_Init() != HAL_OK) {
		Error_Handler();
	}

	HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
	HAL_NVIC_EnableIRQ(USART1_IRQn);

	HAL_NVIC_SetPriority(USART3_IRQn, 5, 0);
	HAL_NVIC_EnableIRQ(USART3_IRQn);

	if (HAL_UART_Receive_IT(&huart1, &uart1_rx_byte, 1) != HAL_OK) {
		Error_Handler();
	}
	if (HAL_UART_Receive_IT(&hesp8266_uart, &esp8266_rx_byte, 1) != HAL_OK) {
		Error_Handler();
	}

	printf("\r\nSTM32F407 ESP8266 HAL Transparent TCP client\r\n");
	WiFiPassthrough_Run(&huart1, &bufferU1);

	while (1) {
	}

}

static void LED_GPIO_Init(void) {

	GPIO_InitTypeDef GPIO_InitStruct = { 0 };

	__HAL_RCC_GPIOE_CLK_ENABLE();

	// Configure LED0_Pin
	GPIO_InitStruct.Pin = GPIO_PIN_13;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
}

static void USART1_UART_Init(void) {

	GPIO_InitTypeDef GPIO_InitStruct = { 0 };

	// Enable UART pins clock access
	__HAL_RCC_GPIOA_CLK_ENABLE();

	// Enable UART module clock access
	__HAL_RCC_USART1_CLK_ENABLE();

	// Configure pins to act as alternate function pins (UART)
	GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	huart1.Instance = USART1;
	huart1.Init.BaudRate = 115200;
	huart1.Init.WordLength = UART_WORDLENGTH_8B;
	huart1.Init.StopBits = UART_STOPBITS_1;
	huart1.Init.Parity = UART_PARITY_NONE;
	huart1.Init.Mode = UART_MODE_TX_RX;
	huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	huart1.Init.OverSampling = UART_OVERSAMPLING_16;
	if (HAL_UART_Init(&huart1) != HAL_OK) {
		Error_Handler();
	}

}

void USART1_IRQHandler(void) {
	HAL_UART_IRQHandler(&huart1);
}

void USART3_IRQHandler(void) {
	HAL_UART_IRQHandler(&hesp8266_uart);
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
	if (huart->Instance == USART1) {
		// A byte arrived from the PC terminal
		(void) RingBuff_Write(&bufferU1, uart1_rx_byte);
		// Arm the next one-byte PC reception
		(void) HAL_UART_Receive_IT(&huart1, &uart1_rx_byte, 1);
	} else if (huart->Instance == USART3) {
		// A byte arrived from the ESP8266
		(void) RingBuff_Write(&bufferU3, esp8266_rx_byte);
		// Arm the next one-byte ESP8266 reception
		(void) HAL_UART_Receive_IT(&hesp8266_uart, &esp8266_rx_byte, 1);
	}
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
	if (huart->Instance == USART1) {
		// Restart PC-side reception after overrun, noise, or framing error
		(void) HAL_UART_Receive_IT(&huart1, &uart1_rx_byte, 1);
	} else if (huart->Instance == USART3) {
		// Restart ESP8266-side reception after an error
		(void) HAL_UART_Receive_IT(&hesp8266_uart, &esp8266_rx_byte, 1);
	}
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void) {

	RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
	RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };

	// Configure the main internal regulator output voltage
	__HAL_RCC_PWR_CLK_ENABLE();
	__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

	// Initializes the RCC Oscillators according to the specified parameters
	// in the RCC_OscInitTypeDef structure.
	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
	RCC_OscInitStruct.HSEState = RCC_HSE_ON;
	RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
	RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
	RCC_OscInitStruct.PLL.PLLM = 25;
	RCC_OscInitStruct.PLL.PLLN = 336;
	RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
	RCC_OscInitStruct.PLL.PLLQ = 7; // 25 MHz / 25 * 336 / 7 = 48 MHz for USB clock
	if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
		Error_Handler();
	}

	// Initializes the CPU, AHB and APB buses clocks
	RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
			| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
	RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
	RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
	RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
	RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

	if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
		Error_Handler();
	}

	// Test HCLK clock
	HCLK_freq = HAL_RCC_GetHCLKFreq();
	if (HCLK_freq != (uint32_t) 168000000) {
		Error_Handler();
	}

	// Test PCLK1 clock
	PCLK1_freq = HAL_RCC_GetPCLK1Freq();
	if (PCLK1_freq != (uint32_t) 42000000) {
		Error_Handler();
	}

	// Test PCLK2 clock
	PCLK2_freq = HAL_RCC_GetPCLK2Freq();
	if (PCLK2_freq != (uint32_t) 84000000) {
		Error_Handler();
	}

}

/**
 * @brief  Period elapsed callback in non blocking mode
 * @note   This function is called  when TIM7 interrupt took place, inside
 * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
 * a global variable "uwTick" used as application time base.
 * @param  htim : TIM handle
 * @retval None
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
	if (htim->Instance == TIM7) {
		HAL_IncTick();
	}
}

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void) {
	__disable_irq();
	while (1) {
	}
}

#ifdef USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
