/**
 * @file ethernetif.c
 * @brief Ethernet interface implementation for CH390 + LwIP + FreeRTOS
 */

#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include "lwip/snmp.h"
#include "lwip/etharp.h"
#include "lwip/netifapi.h"
#include "lwip/tcpip.h"
#include "lwip/ip4_addr.h"
#include "netif/ethernet.h"
#include "ethernetif.h"

#include "CH390.h"
#include "CH390_Interface.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include "app_runtime.h"
#include "debug_log.h"
#include "config.h"

#include <string.h>

/* Interface name */
#define IFNAME0 'e'
#define IFNAME1 'n'

/* Global network interface */
struct netif ch390_netif;

/* Mutex for SPI access protection */
static SemaphoreHandle_t spi_mutex = NULL;
static uint8_t s_rx_buffer[CH390_PKT_MAX];
static uint8_t s_tx_buffer[CH390_PKT_MAX];
static uint8_t s_garp_sent = 0u;

/* Forward declarations */
static err_t low_level_init(struct netif *netif);
static err_t low_level_output(struct netif *netif, struct pbuf *p);
static struct pbuf *low_level_input(struct netif *netif);
static void ethernetif_update_link(uint8_t link_status);

/*---------------------------------------------------------------------------
 * Low Level Hardware Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Initialize the CH390 hardware
 * @param netif Network interface structure
 */
static err_t low_level_init(struct netif *netif)
{
    struct ethernetif *ethernetif = netif->state;
    const device_config_t *cfg = config_get();
    
    /* Create SPI mutex */
    if (spi_mutex == NULL)
    {
        spi_mutex = xSemaphoreCreateMutex();
        if (spi_mutex == NULL)
        {
            return ERR_MEM;
        }
    }
    
    /* Initialize CH390 GPIO and SPI */
    ch390_gpio_init();
    ch390_spi_init();
    
    /* Hardware reset CH390 */
    ch390_hardware_reset();
    
    /* Configure CH390 with default settings */
    ch390_default_config();
    debug_log_printf("[ETH] cfg imr=0x%02X intcr=0x%02X rcr=0x%02X\r\n",
                     ch390_read_reg(CH390_IMR),
                     ch390_read_reg(CH390_INTCR),
                     ch390_read_reg(CH390_RCR));
    debug_log_printf("[ETH] bc bcastcr=0x%02X mar7=0x%02X\r\n",
                     ch390_read_reg(CH390_BCASTCR),
                     ch390_read_reg(CH390_MAR + 7));

    /* Apply configured MAC address to CH390 before reading it back into lwIP */
    ch390_set_mac_address((uint8_t *)cfg->net.mac);
    
    /* Set MAC hardware address length */
    netif->hwaddr_len = ETHARP_HWADDR_LEN;
    
    /* Get MAC address from CH390 */
    ch390_get_mac(netif->hwaddr);
    
    /* Maximum transfer unit */
    netif->mtu = 1500;
    
    /* Device capabilities */
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET;
    
    /* Initialize state */
    ethernetif->rx_len = 0;
    ethernetif->rx_status = 0;
    
    /* Enable CH390 interrupt */
    ch390_interrupt_init();

    return ERR_OK;
}

/**
 * @brief Transmit a packet via CH390
 * @param netif Network interface structure
 * @param p Packet buffer to transmit
 * @return ERR_OK on success
 */
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    struct pbuf *q;
    uint16_t offset;
    uint16_t tx_len;
    int tx_rc;
    (void)netif;
    
    /* Take SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }
    
#if ETH_PAD_SIZE
    pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
    
    offset = 0u;

    /* Copy data to contiguous staging buffer */
    for (q = p; q != NULL; q = q->next)
    {
        if ((uint32_t)offset + (uint32_t)q->len > (uint32_t)sizeof(s_tx_buffer))
        {
            if (spi_mutex != NULL)
            {
                xSemaphoreGive(spi_mutex);
            }

#if ETH_PAD_SIZE
            pbuf_add_header(p, ETH_PAD_SIZE);
#endif

            LINK_STATS_INC(link.drop);
            return ERR_BUF;
        }

        MEMCPY(&s_tx_buffer[offset], q->payload, q->len);
        offset = (uint16_t)(offset + q->len);
    }

    tx_len = p->tot_len;

    tx_rc = ch390_runtime_send_packet(s_tx_buffer, tx_len);
    
    /* Release SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }
    
#if ETH_PAD_SIZE
    pbuf_add_header(p, ETH_PAD_SIZE);
#endif

    if (tx_rc != 0)
    {
        LINK_STATS_INC(link.drop);
        LINK_STATS_INC(link.err);
        debug_log_write("[ETH] tx timeout\r\n");
        return ERR_TIMEOUT;
    }
    
    LINK_STATS_INC(link.xmit);
    
    return ERR_OK;
}

/**
 * @brief Receive a packet from CH390
 * @param netif Network interface structure
 * @return Packet buffer containing received data, or NULL if no packet
 */
static struct pbuf *low_level_input(struct netif *netif)
{
    struct ethernetif *ethernetif = netif->state;
    struct pbuf *p = NULL;
    struct pbuf *q;
    uint16_t offset;
    uint16_t len;
    uint32_t rx_len;
    uint8_t rx_status;
    
    /* Take SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }
    
    rx_len = ch390_runtime_receive_packet(s_rx_buffer, &rx_status);
    ethernetif->rx_status = rx_status;
    ethernetif->rx_len = (uint16_t)rx_len;

    if (rx_len == 0u)
    {
        if (spi_mutex != NULL)
        {
            xSemaphoreGive(spi_mutex);
        }
        return NULL;
    }

    len = (uint16_t)rx_len;

#if ETH_PAD_SIZE
    len += ETH_PAD_SIZE;
#endif
    
    /* Allocate pbuf chain */
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    
    if (p != NULL)
    {
        offset = 0u;
#if ETH_PAD_SIZE
        pbuf_remove_header(p, ETH_PAD_SIZE);
#endif
        
        /* Copy packet data into pbuf chain */
        for (q = p; q != NULL; q = q->next)
        {
            MEMCPY(q->payload, &s_rx_buffer[offset], q->len);
            offset = (uint16_t)(offset + q->len);
        }

        if ((ethernetif->rx_status & 0x3Fu) != 0u)
        {
            pbuf_free(p);
            p = NULL;
            LINK_STATS_INC(link.drop);
        }
        else
        {
            LINK_STATS_INC(link.recv);
        }
        
#if ETH_PAD_SIZE
        if (p != NULL)
        {
            pbuf_add_header(p, ETH_PAD_SIZE);
        }
#endif
    }
    else
    {
        /* No memory - drop packet */
        ch390_drop_packet(ethernetif->rx_len);
        LINK_STATS_INC(link.memerr);
        LINK_STATS_INC(link.drop);
    }
    
    /* Release SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }
    
    return p;
}

/*---------------------------------------------------------------------------
 * Public Interface Functions
 *---------------------------------------------------------------------------*/

/**
 * @brief Process received ethernet packets
 * @param netif Network interface structure
 */
void ethernetif_input(struct netif *netif)
{
    struct pbuf *p;
    err_t input_err;
    
    /* Get received packet */
    p = low_level_input(netif);
    
    if (p != NULL)
    {
        /* Pass to LwIP stack */
        input_err = netif->input(p, netif);
        if (input_err != ERR_OK)
        {
            LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
            pbuf_free(p);
        }
    }
}

/**
 * @brief Initialize the ethernet interface
 * @param netif Network interface structure
 * @return ERR_OK on success
 */
err_t ethernetif_init(struct netif *netif)
{
    struct ethernetif *ethernetif;
    
    LWIP_ASSERT("netif != NULL", (netif != NULL));
    
    /* Allocate ethernetif state structure */
    ethernetif = mem_malloc(sizeof(struct ethernetif));
    if (ethernetif == NULL)
    {
        LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
        return ERR_MEM;
    }
    
    memset(ethernetif, 0, sizeof(struct ethernetif));
    
#if LWIP_NETIF_HOSTNAME
    netif->hostname = "tcp2uart";
#endif
    
    /* Initialize SNMP variables */
    MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, 100000000);
    
    netif->state = ethernetif;
    netif->name[0] = IFNAME0;
    netif->name[1] = IFNAME1;
    
    /* Set output functions */
#if LWIP_IPV4
    netif->output = etharp_output;
#endif
    netif->linkoutput = low_level_output;
    
    /* Initialize hardware */
    if (low_level_init(netif) != ERR_OK)
    {
        mem_free(ethernetif);
        netif->state = NULL;
        return ERR_MEM;
    }
    
    return ERR_OK;
}

/**
 * @brief Initialize LwIP network interface
 * @param ipaddr IP address
 * @param netmask Network mask
 * @param gw Gateway address
 */
void lwip_netif_init(const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw)
{
    err_t err;

    g_netif_init_ok = 0;
    g_netif_add_err = 0x7FFFFFFF;
    g_netif_set_default_err = 0x7FFFFFFF;
    g_netif_set_link_down_err = 0x7FFFFFFF;
    g_netif_set_up_err = 0x7FFFFFFF;

    g_netif_phase = 2u;
    /* Add network interface */
    g_netif_phase = 3u;
    err = netifapi_netif_add(&ch390_netif, ipaddr, netmask, gw, NULL,
                             &ethernetif_init, &tcpip_input);
    g_netif_add_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] netif-add fail err=%d\r\n", (int)err);
        return;
    }
    g_netif_phase = 4u;
    
    /* Set as default interface */
    err = netifapi_netif_set_default(&ch390_netif);
    g_netif_set_default_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] set-default fail err=%d\r\n", (int)err);
        return;
    }
    
    /* Set interface down initially */
    err = netifapi_netif_set_link_down(&ch390_netif);
    g_netif_set_link_down_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] set-link-down fail err=%d\r\n", (int)err);
        return;
    }
    err = netifapi_netif_set_up(&ch390_netif);
    g_netif_set_up_err = (int32_t)err;
    if (err != ERR_OK) {
        g_netif_init_ok = -1;
        debug_log_printf("[ETH] set-up fail err=%d\r\n", (int)err);
        return;
    }
    g_netif_phase = 5u;
    g_netif_phase = 6u;
    g_netif_init_ok = 1;
}

void ethernetif_diag_ch390_init(void)
{
    const device_config_t *cfg = config_get();

    if (spi_mutex == NULL)
    {
        spi_mutex = xSemaphoreCreateMutex();
    }

    ch390_gpio_init();
    ch390_spi_init();
    ch390_hardware_reset();
    ch390_default_config();
    ch390_set_mac_address((uint8_t *)cfg->net.mac);
    ch390_interrupt_init();

    g_netif_init_ok = 1;
}

void ethernetif_diag_poll_status(void)
{
    struct ch390_runtime_status runtime_status;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    (void)memset(&runtime_status, 0, sizeof(runtime_status));
    ch390_runtime_poll(&runtime_status);

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }
}

/**
 * @brief Check and handle CH390 link status
 */
static void ethernetif_update_link(uint8_t link_status)
{
    LOCK_TCPIP_CORE();

    if (link_status)
    {
        if (!netif_is_link_up(&ch390_netif))
        {
            netif_set_link_up(&ch390_netif);
            if ((s_garp_sent == 0u) && netif_is_up(&ch390_netif))
            {
                etharp_gratuitous(&ch390_netif);
                s_garp_sent = 1u;
            }
        }
    }
    else
    {
        if (netif_is_link_up(&ch390_netif))
        {
            netif_set_link_down(&ch390_netif);
            s_garp_sent = 0u;
        }
    }

    UNLOCK_TCPIP_CORE();
}

/**
 * @brief Process all pending RX packets
 */
void ethernetif_poll(void)
{
    struct ch390_runtime_status runtime_status;
    struct pbuf *p;
    err_t input_err;
    uint8_t rx_budget = 4u;
    
    /* Take SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }
    
    (void)memset(&runtime_status, 0, sizeof(runtime_status));
    ch390_runtime_poll(&runtime_status);
    
    /* Release SPI mutex */
    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    /* Handle link change */
    if ((runtime_status.int_status & ISR_LNKCHG) != 0u)
    {
        ethernetif_update_link((uint8_t)ch390_runtime_link_up_from_status(&runtime_status));
    }
    
    /* Handle RX overflow */
    if ((runtime_status.int_status & ISR_ROS) != 0u)
    {
        /* RX overflow - packets might be corrupted */
        LINK_STATS_INC(link.err);
    }
    
    /* Always attempt to drain RX FIFO so packet handling does not depend only on ISR_PR timing. */
    while (rx_budget > 0u)
    {
        p = low_level_input(&ch390_netif);
        if (p == NULL)
        {
            break;
        }
        
        input_err = ch390_netif.input(p, &ch390_netif);
        if (input_err != ERR_OK)
        {
            pbuf_free(p);
        }

        --rx_budget;
    }

    if (rx_budget == 0u)
    {
        taskYIELD();
    }
}

void ethernetif_check_link(void)
{
    uint8_t link_status;

    if (spi_mutex != NULL)
    {
        xSemaphoreTake(spi_mutex, portMAX_DELAY);
    }

    link_status = (uint8_t)ch390_get_link_status();

    if (spi_mutex != NULL)
    {
        xSemaphoreGive(spi_mutex);
    }

    ethernetif_update_link(link_status);
}

uint8_t ethernetif_link_is_up(void)
{
    return netif_is_link_up(&ch390_netif) ? 1u : 0u;
}
