#include <DHCP/dhcp.h>

#include "ch32v003fun.h"
#include "debug.h"
#include "dhcp.h"
#include "gpio.h"
#include "socket.h"
#include "spi_dma.h"
#include "systick.h"
#include "timer.h"
#include "uart.h"
#include "w5500.h"

#define MQTT_RECONNECT_INTERVAL 5000
#define MQTT_YIELD_INTERVAL 100
#define W5500_INIT_DELAY_MS 55
#define DHCP_TIMEOUT_MS 15000

typedef struct {
  Network network;
  MQTTClient client;
  ch32_mqtt_options_t opts;
  uint32_t last_reconnect;
  uint32_t last_yield;
  uint8_t is_connected;
} mqtt_state_t;

// init MQTT state
void mqtt_init(mqtt_state_t* state, char* client_id) {
  // TODO
  state->opts.clientid = client_id;
  state->opts.username = "";
  state->opts.password = "";
  state->opts.qos = QOS0;
  state->last_reconnect = 0;
  state->last_yield = 0;
  state->is_connected = 0;
}

// cb fn for when a message is received
void message_arrived(MessageData* md) {
  if (!md || !md->message) {
    DEBUG_PRINT("Error: MessageData is NULL.\n");
    return;
  }

  MQTTMessage* message = md->message;
  DEBUG_PRINT("MQTT Message Arrived:\n");
  DEBUG_PRINT("QoS: %d\n", message->qos);
  DEBUG_PRINT("Retained: %d\n", message->retained);
  DEBUG_PRINT("Duplicate: %d\n", message->dup);
  DEBUG_PRINT("Message ID: %u\n", message->id);
  DEBUG_PRINT("Payload Length: %u\n", (unsigned int)message->payloadlen);

  if (message->payload != NULL && message->payloadlen > 0) {
    DEBUG_PRINT("Payload: ");
    for (unsigned int i = 0; i < (unsigned int)message->payloadlen; ++i) {
      putchar(((unsigned char*)message->payload)[i]);
    }
    DEBUG_PRINT("\n");
  } else {
    DEBUG_PRINT("Payload: <No data>\n");
  }
}

// mqtt reconnect
// TODO: setup will, publish discovery messages, setup subs
void mqtt_process(mqtt_state_t* state) {
  uint32_t now = millis();
  int rc;

  if (!state->is_connected) {
    if (now - state->last_reconnect >= MQTT_RECONNECT_INTERVAL) {
      DEBUG_PRINT("Attempting MQTT connection...\n");

      rc = setup_mqtt_client(&state->network, &state->opts, &state->client);
      if (rc == 0) {
        DEBUG_PRINT("MQTT connected\n");
        state->is_connected = 1;

        rc = subscribe_to_topic(&state->client, SUB_TOPIC, QOS0,
                                message_arrived);
        if (rc != 0) {
          DEBUG_PRINT("Subscribe failed: %d\n", rc);
          state->is_connected = 0;
        } else {
          publish_message(&state->client, "Device connected", PUB_TOPIC);
        }
      }
      state->last_reconnect = now;
    }
  } else if (now - state->last_yield >= MQTT_YIELD_INTERVAL) {
    rc = MQTTYield(&state->client, 100);
    if (rc != 0) {
      DEBUG_PRINT("Yield failed: %d\n", rc);
      state->is_connected = 0;
    }
    state->last_yield = now;
  }
}

void init_system(void) {
  SystemInit();

  init_gpio();
  init_systick();
  tim2_init();

  init_uart(UART_BRR_APB1);
  init_spidma();
}

// Wait for DHCP lease
static int wait_for_dhcp(void) {
  uint32_t start = millis();
  while (dhcp_get_state() != DHCP_STATE_LEASED) {
    if (millis() - start >= DHCP_TIMEOUT_MS) {
      DEBUG_PRINT("DHCP timeout\n");
      return 0;
    }
    dhcp_process();
  }
  return 1;
}

int main(void) {
  init_system();
  Delay_Ms(W5500_INIT_DELAY_MS);

  // Configure network and DHCP
  configure_network();
  dhcp_init();

  if (!wait_for_dhcp()) {
    while (1) {
      // todo: handle err
    }
  }

  resolve_domain_name("example.com");

  // mqtt
  mqtt_state_t mqtt;
  mqtt_init(&mqtt, CLIENT_ID);

  while (1) {
    dhcp_process();
    mqtt_process(&mqtt);
  }

  return 0;
}