#include "system.h"
#include "../common/usart.h"
static Usart * pInstance  = nullptr;

extern "C" {
  [[gnu::interrupt]] void USART1_IRQHandler (void);
};
void USART1_IRQHandler (void) {
  if (pInstance) pInstance->irq();
};

Usart::Usart(const uint32_t _baud) noexcept : BaseLayer (), tx_ring () {
  pInstance = this;
  // 1. Clock Enable
  RCC.APB2PCENR.modify([](RCC_Type::APB2PCENR_DEF & r) -> auto {
    r.B.USART1EN = SET;
    r.B.IOPAEN   = SET;
  //r.B.AFIOEN   = SET;   // není nutné
    return r.R;
  });
  // 2. GPIO Alternate Config - default TX/PA9, RX/PA10
  GPIOA.CFGHR.modify([](GPIOA_Type::CFGHR_DEF & r) -> auto {
    r.B.MODE9  = 1u; 
    r.B.CNF9   = 2u;  // or 3u for open drain
    r.B.MODE10 = 0u;
    r.B.CNF10  = 1u;  // floating input
    return r.R;
  });
  RCC.APB2PRSTR.B.USART1RST = SET;
  RCC.APB2PRSTR.B.USART1RST = RESET;
  // 5. USART registry 8.bit bez parity
  USART1.CTLR1.modify([] (USART1_Type::CTLR1_DEF & r) -> auto {
    r.B.RE      = SET;
    r.B.TE      = SET;
    r.B.RXNEIE  = SET;
    return r.R;
  });
  USART1.CTLR2.modify ([](USART1_Type::CTLR2_DEF & r) -> auto {
    r.B.STOP = 0u;
    return r.R;
  });
  SetBaud(_baud);
  // NVIC
  NVIC.EnableIRQ (USART1_IRQn);
  USART1.CTLR1.B.UE = SET;        // nakonec povolit globálně
}
void Usart::irq () {
  volatile USART1_Type::STATR_DEF status (USART1.STATR);   // načti status přerušení
  char rdata, tdata;
  
  if (status.B.TXE) {                         // od vysílače
    if (tx_ring.Read (tdata)) {               // pokud máme data
      USART1.DATAR.B.DR = (uint8_t) tdata;    // zapíšeme do výstupu
    } else {                                  // pokud ne
      USART1.CTLR1.B.TXEIE = RESET;
    }
  }
  if (status.B.RXNE) {                        // od přijímače
    rdata = (USART1.DATAR.B.DR);              // načteme data
    Up (&rdata, 1u);                          // a pošleme dál
  }
}
uint32_t Usart::Down(const char * data, const uint32_t len) {
  unsigned n = 0u;
  for (n=0u; n<len; n++) {
    if (!tx_ring.Write(data[n])) break;
  }
  USART1.CTLR1.B.TXEIE = SET; // po povolení přerušení okamžitě přeruší
  return n;
}
void Usart::SetBaud (const uint32_t _baud) const {
  if (_baud == 0u) return;                // ! zero divide
  const ONE_BIT b = USART1.CTLR1.B.UE;
  if (b == SET) USART1.CTLR1.B.UE = RESET;
  const uint32_t HCLK = SystemCoreClock;  // hodiny pro USART zde nejsou děleny
  const uint32_t tmp  = HCLK / _baud;     // lze je tedy zapsat přímo
  USART1.BRR.R = tmp;
  if (b == SET) USART1.CTLR1.B.UE = SET;
}
bool Usart::IOCtrl (const CTRL_TYPES_DEF type, const void * data, const uint32_t len) {
  switch (type) {
    case USB_USART_SET_PARAM : {
      const USB_CDC_LineCoding * lc = reinterpret_cast<const USB_CDC_LineCoding *>(data);
      SetBaud(lc->baud); // Pouze změníme rychlost, ostatní parametry ingorujme
    } return true;
    // Ostatní IOCtrl zatím nebudeme obsluhovat, je to celkem zbytečné
    default : break;
  };
  return false;
}