// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_sys_io/sys_io.h"

#include <cinttypes>

#include "pw_preprocessor/concat.h"
#include "pw_status/status.h"
#include "pw_sys_io_stm32cube_private/config.h"
#include "stm32cube/stm32cube.h"

// These macros remap config options to the various STM32Cube HAL macro names.

// USART_INSTANCE defined to USARTn, where n is the USART peripheral index.
#define USART_INSTANCE \
  PW_CONCAT(PW_SYS_IO_STM32CUBE_USART_PREFIX, PW_SYS_IO_STM32CUBE_USART_NUM)

// USART_GPIO_ALTERNATE_FUNC defined to GPIO_AFm_USARTn, where m is the
// alternate function index and n is the USART peripheral index.
#define USART_GPIO_ALTERNATE_FUNC             \
  PW_CONCAT(GPIO_AF,                          \
            PW_SYS_IO_STM32CUBE_GPIO_AF,      \
            _,                                \
            PW_SYS_IO_STM32CUBE_USART_PREFIX, \
            PW_SYS_IO_STM32CUBE_USART_NUM)

// USART_GPIO_REMAP defined to __HAL_AFIO_REMAP_USARTn_val,
// where n is the USART peripheral index and val is ENABLE or DISABLE.
// It only applies to the stm32f1xx family.
#define USART_GPIO_REMAP                      \
  PW_CONCAT(__HAL_AFIO_REMAP_,                \
            PW_SYS_IO_STM32CUBE_USART_PREFIX, \
            PW_SYS_IO_STM32CUBE_USART_NUM,    \
            _,                                \
            PW_SYS_IO_STM32CUBE_GPIO_REMAP)

// USART_GPIO_PORT defined to GPIOx, where x is the GPIO port letter that the
// TX/RX pins are on.
#define USART_GPIO_TX_PORT PW_CONCAT(GPIO, PW_SYS_IO_STM32CUBE_GPIO_TX_PORT)
#define USART_GPIO_RX_PORT PW_CONCAT(GPIO, PW_SYS_IO_STM32CUBE_GPIO_RX_PORT)
#define USART_GPIO_TX_PIN PW_CONCAT(GPIO_PIN_, PW_SYS_IO_STM32CUBE_GPIO_TX_PIN)
#define USART_GPIO_RX_PIN PW_CONCAT(GPIO_PIN_, PW_SYS_IO_STM32CUBE_GPIO_RX_PIN)

// USART_GPIO_PORT_ENABLE defined to __HAL_RCC_GPIOx_CLK_ENABLE, where x is the
// GPIO port letter that the TX/RX pins are on.
#define USART_GPIO_TX_PORT_ENABLE \
  PW_CONCAT(__HAL_RCC_GPIO, PW_SYS_IO_STM32CUBE_GPIO_TX_PORT, _CLK_ENABLE)

#define USART_GPIO_RX_PORT_ENABLE \
  PW_CONCAT(__HAL_RCC_GPIO, PW_SYS_IO_STM32CUBE_GPIO_RX_PORT, _CLK_ENABLE)

// USART_ENABLE defined to __HAL_RCC_USARTn_CLK_ENABLE, where n is the USART
// peripheral index.
#define USART_ENABLE                          \
  PW_CONCAT(__HAL_RCC_,                       \
            PW_SYS_IO_STM32CUBE_USART_PREFIX, \
            PW_SYS_IO_STM32CUBE_USART_NUM,    \
            _CLK_ENABLE)

static UART_HandleTypeDef uart;

extern "C" void pw_sys_io_Init() {
  GPIO_InitTypeDef GPIO_InitStruct = {};

  USART_ENABLE();
  USART_GPIO_TX_PORT_ENABLE();
  USART_GPIO_RX_PORT_ENABLE();
#if defined(STM32F1)
  __HAL_RCC_AFIO_CLK_ENABLE();
#endif  // defined(STM32F1)

  GPIO_InitStruct.Pin = USART_GPIO_TX_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
#if defined(STM32F0) || defined(STM32F1) || defined(STM32F3)
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
#else
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
#endif
#if !defined(STM32F1)
  GPIO_InitStruct.Alternate = USART_GPIO_ALTERNATE_FUNC;
#endif  // !defined(STM32F1)
  HAL_GPIO_Init(USART_GPIO_TX_PORT, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = USART_GPIO_RX_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
#if defined(STM32F0) || defined(STM32F1) || defined(STM32F3)
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
#else
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
#endif
#if !defined(STM32F1)
  GPIO_InitStruct.Alternate = USART_GPIO_ALTERNATE_FUNC;
#endif  // !defined(STM32F1)
  HAL_GPIO_Init(USART_GPIO_RX_PORT, &GPIO_InitStruct);

#if defined(STM32F1)
  USART_GPIO_REMAP();
#endif  // defined(STM32F1)

  uart.Instance = USART_INSTANCE;
  uart.Init.BaudRate = 115200;
  uart.Init.WordLength = UART_WORDLENGTH_8B;
  uart.Init.StopBits = UART_STOPBITS_1;
  uart.Init.Parity = UART_PARITY_NONE;
  uart.Init.Mode = UART_MODE_TX_RX;
  uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  uart.Init.OverSampling = UART_OVERSAMPLING_16;
  HAL_UART_Init(&uart);
}

// This whole implementation is very inefficient because it uses the synchronous
// polling UART API and only reads / writes 1 byte at a time.
namespace pw::sys_io {
Status ReadByte(std::byte* dest) {
  if (HAL_UART_Receive(
          &uart, reinterpret_cast<uint8_t*>(dest), 1, HAL_MAX_DELAY) !=
      HAL_OK) {
    return Status::ResourceExhausted();
  }
  return OkStatus();
}

Status TryReadByte(std::byte* dest) { return Status::Unimplemented(); }

Status WriteByte(std::byte b) {
  if (HAL_UART_Transmit(
          &uart, reinterpret_cast<uint8_t*>(&b), 1, HAL_MAX_DELAY) != HAL_OK) {
    return Status::ResourceExhausted();
  }
  return OkStatus();
}

// Writes a string using pw::sys_io, and add newline characters at the end.
StatusWithSize WriteLine(std::string_view s) {
  size_t chars_written = 0;
  StatusWithSize result = WriteBytes(as_bytes(span(s)));
  if (!result.ok()) {
    return result;
  }
  chars_written += result.size();

  // Write trailing newline.
  result = WriteBytes(as_bytes(span("\r\n", 2)));
  chars_written += result.size();

  return StatusWithSize(OkStatus(), chars_written);
}

}  // namespace pw::sys_io
