#include <DHCP/dhcp.h>
#include <DNS/dns.h>
#include <W5500/w5500.h>
#include <socket.h>
#include <stdio.h>

#include "ch32v003fun.h"
#include "uart.h"
#include "w5500.h"

#define DHCP_SOCKET 0
#define DNS_SOCKET 1
#define HTTP_SOCKET 2

volatile uint32_t count = 0;

void init_system(void) {
  SystemInit();  // from ch32v003fun.h

  RCC->APB2PCENR |=
      RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB;  // Enable GPIO clocks

  setup_uart(UART_BRR_APB1);  // 115200 baud
}

void init_gpio(void) {
  // GPIO PB3 pp
  GPIOB->CFGLR &= ~(0xf << (4 * 3));
  GPIOB->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 3);

  // GPIO PB4 pp
  GPIOB->CFGLR &= ~(0xf << (4 * 4));
  GPIOB->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 4);
}

void init_spi(void) {
  // Enable SPI1
  RCC->APB2PCENR |= RCC_APB2Periph_SPI1;

  // CS on PA4, 10MHz Output, open-drain
  GPIOA->CFGLR &= ~(0xf << (4 * 4));
  GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD) << (4 * 4);

  // SCK on PA5, 10MHz Output, alt func, push-pull
  GPIOA->CFGLR &= ~(0xf << (4 * 5));
  GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 5);

  // MOSI on PA7, 10MHz input, floating
  GPIOA->CFGLR &= ~(0xf << (4 * 6));
  GPIOA->CFGLR |= (GPIO_CNF_IN_FLOATING) << (4 * 6);

  // MISO on PA6, 10MHz Output, alt func, push-pull
  GPIOA->CFGLR &= ~(0xf << (4 * 7));
  GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 7);

  // SPI1->CTLR1 = (0 << 15) |  // BIDIMODE
  //               (0 << 14) |  // BIDIOE
  //               (0 << 13) |  // CRCEN
  //               (0 << 12) |  // CRCNEXT
  //               (0 << 11) |  // DFF
  //               (0 << 10) |  // RXONLY
  //               (1 << 9) |   // SSM
  //               (0 << 8) |   // SSI
  //               (0 << 7) |   // LSBFIRST
  //               (0 << 6) |   // SPE (Enable SPI)
  //               (1 << 5) |   // BR[2] (Set baud rate)
  //               (0 << 4) |   // BR[1]
  //               (0 << 3) |   // BR[0]
  //               (1 << 2) |   // MSTR (Master mode)
  //               (0 << 1) |   // CPOL
  //               (0);         // CPHA

  // reset control register
  SPI1->CTLR1 = 0;

  // set prescaler
  // 001: FPCLK/4;
  SPI1->CTLR1 = (SPI1->CTLR1 & ~SPI_CTLR1_BR) | (0x1 << 3);

  // set clock polarity and phase
  SPI1->CTLR1 |= (SPI_CPOL_Low | SPI_CPHA_1Edge);

  // configure NSS pin, master mode
  SPI1->CTLR1 |= SPI_NSS_Soft;  // SSM NSS software control mode

  // CH32V203 is master
  SPI1->CTLR1 |= SPI_Mode_Master;

  // set data direction and configure data pins
  SPI1->CTLR1 |= SPI_Direction_2Lines_FullDuplex;

  // disable I2S mode
  SPI1->I2SCFGR &= SPI_Mode_Select;
  SPI1->CTLR1 &= ~(SPI_CTLR1_DFF);  // DFF 16bit data-length enable, writable
                                    // only when SPE is 0
  SPI1->CTLR1 |= SPI_CTLR1_SPE;
}

volatile int ip_assigned = 0;

void Callback_IPAssigned(void) {
  printf("Callback: IP assigned! Leased time: %ld sec\r\n", getDHCPLeasetime());
  ip_assigned = 1;
}

void Callback_IPConflict(void) { printf("Callback: IP conflict!\r\n"); }

// 1K should be enough, see https://forum.wiznet.io/t/topic/1612/2
uint8_t dhcp_buffer[1024];
// 1K seems to be enough for this buffer as well
uint8_t dns_buffer[1024];

int main(void) {
  init_system();
  init_gpio();
  init_spi();

  printf("SystemClk: %u\r\n", (unsigned)FUNCONF_SYSTEM_CORE_CLOCK);

  printf("Registering W5500 callbacks...\r\n");
  reg_wizchip_cs_cbfunc(W5500_Select, W5500_Unselect);
  reg_wizchip_spi_cbfunc(W5500_ReadByte, W5500_WriteByte);
  // reg_wizchip_spiburst_cbfunc(W5500_ReadBuff, W5500_WriteBuff);

  printf("Calling wizchip_init()...\r\n");
  uint8_t rx_tx_buff_sizes[] = {2, 2, 2, 2, 2, 2, 2, 2};
  wizchip_init(rx_tx_buff_sizes, rx_tx_buff_sizes);

  printf("Calling DHCP_init()...\r\n");
  wiz_NetInfo net_info = {.mac = {0xEA, 0x11, 0x22, 0x33, 0x44, 0xEA},
                          .dhcp = NETINFO_DHCP};
  // set MAC address before using DHCP
  setSHAR(net_info.mac);
  DHCP_init(DHCP_SOCKET, dhcp_buffer);

  uint8_t version = getVERSIONR();

  printf("W5500 VERSIONR: 0x%02X\n", version);

  printf("Registering DHCP callbacks...\r\n");
  reg_dhcp_cbfunc(Callback_IPAssigned, Callback_IPAssigned,
                  Callback_IPConflict);

  printf("Calling DHCP_run()...\r\n");
  // actually should be called in a loop, e.g. by timer
  uint32_t ctr = 1e5;
  while ((!ip_assigned) && (ctr > 0)) {
    DHCP_run();
    ctr--;
  }
  if (!ip_assigned) {
    printf("\r\nIP was not assigned :(\r\n");
    return 1;
  }

  getIPfromDHCP(net_info.ip);
  getGWfromDHCP(net_info.gw);
  getSNfromDHCP(net_info.sn);

  uint8_t dns[4];
  getDNSfromDHCP(dns);

  printf(
      "IP:  %d.%d.%d.%d\r\nGW:  %d.%d.%d.%d\r\nNet: %d.%d.%d.%d\r\nDNS: "
      "%d.%d.%d.%d\r\n",
      net_info.ip[0], net_info.ip[1], net_info.ip[2], net_info.ip[3],
      net_info.gw[0], net_info.gw[1], net_info.gw[2], net_info.gw[3],
      net_info.sn[0], net_info.sn[1], net_info.sn[2], net_info.sn[3], dns[0],
      dns[1], dns[2], dns[3]);

  printf("Calling wizchip_setnetinfo()...\r\n");
  wizchip_setnetinfo(&net_info);

  printf("Calling DNS_init()...\r\n");
  DNS_init(DNS_SOCKET, dns_buffer);

  uint8_t addr[4];
  {
    char domain_name[] = "hye.su";
    printf("Resolving domain name \"%s\"...\r\n", domain_name);
    int8_t res = DNS_run(dns, (uint8_t*)&domain_name, addr);
    if (res != 1) {
      printf("DNS_run() failed, res = %d", res);
      return 1;
    }
    printf("Result: %d.%d.%d.%d\r\n", addr[0], addr[1], addr[2], addr[3]);
  }

  while (1) {
    GPIOB->BSHR = (1 << 3) | (1 << 4);
    printf("+%lu\n", count++);
    Delay_Ms(1000);  // from ch32v003fun.h

    GPIOB->BCR = (1 << 3) | (1 << 4);
    printf("-%lu\n", count++);
    Delay_Ms(1000);
  }
}