// (C) 2020 Philip Endecott.
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENCE.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#ifndef lipstick_uart_hh
#define lipstick_uart_hh

#include <cstdint>
#include <string>
#include <array>
#include <type_traits>
#include <limits>
#include <optional>

#include "arm.hh"
#include "registers.hh"
#include "clocks.hh"


class Uart
{
public:
  // The UART registers are all byte-wide, and are generally on 4-byte 
  // alignment though there are some gaps.
  // Some of the addresses have multiple purposes, i.e. they have one 
  // function when written and another when read, or the function depends 
  // on some other configuration bit.
  // I've not found a better method than #define to deal with the aliases.

#define BYTE_ON_WORD_ALIGNMENT(NAME) uint8_t NAME __attribute__((aligned(4)))

                                 // uart0         uart1
  BYTE_ON_WORD_ALIGNMENT(RBR);   // 0xE000C000    0xE0010000
#define THR RBR
#define DLL RBR
  BYTE_ON_WORD_ALIGNMENT(DLM);   // 0xE000C004    0xE0010004
#define IER DLM
  BYTE_ON_WORD_ALIGNMENT(IIR);   // 0xE000C008    0xE0010008
#define FCR IIR
  BYTE_ON_WORD_ALIGNMENT(LCR);   // 0xE000C00C    0xE001000C
  BYTE_ON_WORD_ALIGNMENT(MCR);   //               0xE0010010
  BYTE_ON_WORD_ALIGNMENT(LSR);   // 0xE000C014    0xE0010014
  BYTE_ON_WORD_ALIGNMENT(MSR);   // 0xE000C018    0xE0010018
  BYTE_ON_WORD_ALIGNMENT(SCR);   // 0xE000C01C    0xE001001C
  BYTE_ON_WORD_ALIGNMENT(ACR);   // 0xE000C020    0xE0010020
  BYTE_ON_WORD_ALIGNMENT(pad3);  // 0xE000C024    0xE0010024
  BYTE_ON_WORD_ALIGNMENT(FDR);   // 0xE000C028    0xE0010028
  BYTE_ON_WORD_ALIGNMENT(pad4);  // 0xE000C02C    0xE001002C
  BYTE_ON_WORD_ALIGNMENT(TER);   // 0xE000C030    0xE0010030

#undef BYTE_ON_WORD_ALIGNMENT

  template <int baudrate, bool interrupts>
  inline void setup() volatile
  {
    // This doesn't set PINSEL; the caller needs to do that.

    // baud = PCLK / (16 * DLM:DLL)
    constexpr int divisor = f_pclk / (baudrate * 16);
    static_assert(baudrate * 16 * divisor == f_pclk);
    // The 16-bit divisor is written into a pair of 8-bit registers.
    // We need to set the DLAB bit in LCR while settings these values.

    uint8_t lcr = 0b00000011;  // 8 bits, 1 stop bit, no parity.

    LCR = lcr | 0b10000000;  // Set DLAB.
    DLM = divisor >> 8;
    DLL = divisor & 0xff;
    LCR = lcr;
  
    FCR = 0b0000001;  // Bit 0 enables.

    // Interrupts:
    IER = interrupts ? 0b0000000101  // Set RBR Interrupt Enable and RX Line Status Interrupt Enable.
                     : 0b0000000000;

    // Disable hardware flow control - for uart1 only.  (Should be the default.)
    MCR = 0b0000000000;
  }

  enum class receive_status_e { idle, data, error };

  inline receive_status_e receive_status() volatile
  {
    // Per-character errors, i.e. parity and framing, seem to be associated 
    // with individual characters and are popogated along the receive FIFO.
    // This would make it possible to retrieve the valid characters preceeding 
    // the error from the FIFO.  But we don't want to do that; we'll report 
    // the error as soon as it occurs so that the caller can reset everything.

    // Beware that reading LSR has side-effects, so we do it only once.
    // Note the error path here has not been tested!

    auto r = LSR;
    if      (r & 0b1000'0010) return receive_status_e::error;    // Test RXFE and OE bits.
    else if (r & 0b0000'0001) return receive_status_e::data;     // Test RDR bit
    else                      return receive_status_e::idle;
  }

  inline char rcv() volatile
  {
    while (receive_status() != receive_status_e::data) {}
    return RBR;
  }

  inline std::optional<char> rcv_nonblock() volatile
  {
    if (receive_status() != receive_status_e::data) return {};
    return RBR;
  }

  inline void receive_reset() volatile
  {
    FCR = 0b0000'0011;  // RX FIFO reset bit ("self-clearing") and FIFO enable bit.
  }

  inline void send(std::string_view s) volatile
  {
    for (char c: s) send(c);
  }

  template < typename CONT,
             std::enable_if_t< std::is_same_v<typename CONT::value_type,uint8_t>, int > = 0
           >
  void send(const CONT& cont) volatile
  {
    for (char c: cont) send(c);
  }

  template < typename I,
             std::enable_if_t< std::is_integral_v<I>, int > = 0
           >
  void send(I i) volatile
  {
    if (i == 0) {
      send('0');
      return;
    }

    if constexpr (std::numeric_limits<I>::is_signed) {
      using U = std::make_unsigned_t<I>;
      if (i < 0) {
        send('-');
        send(static_cast<U>(-i));  // FIXME borked for -maxint
      } else {
        send(static_cast<U>(i));
      }

    } else {
      std::array<char,std::numeric_limits<I>::digits10+1> buf;
      char* p = buf.data() + buf.size();
      int n = 0;
      while (i) {
        ++n;
        --p;
        auto m = i%10;
        i = i/10;
        *p = '0' + m;
      }
      send(std::string_view(p,n));
    }
  }

  template <typename T0, typename T1, typename... Ts>
  void send(T0 arg0, T1 arg1, Ts... args) volatile
  {
    send(arg0);
    send(arg1, args...);
  }

  inline void send(char c) volatile
  {
    while (!(LSR & 0b00100000)) {}  // Test THRE bit
    THR = c;
  }

  void clear_interrupt() volatile
  {
    auto ign [[maybe_unused]] = IIR;
  }

  void wait_until_transmiter_empty() const volatile
  {
    while (!(LSR & 0b01000000)) {}  // Test TEMPT bit
  }

  void rts(bool v) volatile  // Only on UART1
                             // Note that RTS is asserted low.
  {
    MCR = 0 | (v ? 0b10 : 0);  // Assuming we don't need any other bits set in MCR.
  }

#undef THR
#undef DLL
#undef IER
#undef FCR

};


extern volatile Uart uart0;
extern volatile Uart uart1;


#endif

