#include "lwip/opt.h"
#include "lwip/tcpip.h"
#include "netif/etharp.h"
#include "mbed_interface.h"
#include "ethernet_api.h"
#include "ethernetext_api.h"

#define RECV_TASK_PRI           (osPriorityNormal)
#define PHY_TASK_PRI            (osPriorityNormal)
#define PHY_TASK_WAIT           (200)

/* memory */
static sys_sem_t recv_ready_sem;    /* receive ready semaphore */

/* function */
static void rza1_recv_task(void *arg);
static void rza1_phy_task(void *arg);
static err_t rza1_etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr);
static err_t rza1_low_level_output(struct netif *netif, struct pbuf *p);
static void rza1_recv_callback(void);

static void rza1_recv_task(void *arg) {
    struct netif   *netif = (struct netif*)arg;
    struct eth_hdr *ethhdr;
    u16_t          recv_size;
    struct pbuf    *p;
    int            cnt;

    while (1) {
        sys_arch_sem_wait(&recv_ready_sem, 0);
        for (cnt = 0; cnt < 16; cnt++) {
            recv_size = ethernet_receive();
            if (recv_size != 0) {
                p = pbuf_alloc(PBUF_RAW, recv_size, PBUF_RAM);
                if (p != NULL) {
                    (void)ethernet_read((char *)p->payload, p->len);
                    ethhdr = p->payload;
                    switch (htons(ethhdr->type)) {
                        case ETHTYPE_IP:
                        case ETHTYPE_ARP:
#if PPPOE_SUPPORT
                        case ETHTYPE_PPPOEDISC:
                        case ETHTYPE_PPPOE:
#endif /* PPPOE_SUPPORT */
                            /* full packet send to tcpip_thread to process */
                            if (netif->input(p, netif) != ERR_OK) {
                                /* Free buffer */
                                pbuf_free(p);
                            }
                            break;
                        default:
                            /* Return buffer */
                            pbuf_free(p);
                            break;
                    }
                }
            } else {
                break;
            }
        }
    }
}

static void rza1_phy_task(void *arg) {
    struct netif *netif = (struct netif*)arg;
    s32_t        connect_sts = 0;   /* 0: disconnect, 1:connect */
    s32_t        link_sts;
    s32_t        link_mode_new = NEGO_FAIL;
    s32_t        link_mode_old = NEGO_FAIL;

    while (1) {
        link_sts = ethernet_link();
        if (link_sts == 1) {
            link_mode_new = ethernetext_chk_link_mode();
            if (link_mode_new != link_mode_old) {
                if (connect_sts == 1) {
                    tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_down, (void*) netif, 1);
                }
                if (link_mode_new != NEGO_FAIL) {
                    ethernetext_set_link_mode(link_mode_new);
                    tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_up, (void*) netif, 1);
                    connect_sts = 1;
                }
            }
        } else {
            if (connect_sts != 0) {
                tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_down, (void*) netif, 1);
                link_mode_new = NEGO_FAIL;
                connect_sts   = 0;
            }
        }
        link_mode_old = link_mode_new;
        osDelay(PHY_TASK_WAIT);
    }
}

static err_t rza1_etharp_output(struct netif *netif, struct pbuf *q, ip_addr_t *ipaddr) {
    /* Only send packet is link is up */
    if (netif->flags & NETIF_FLAG_LINK_UP) {
        return etharp_output(netif, q, ipaddr);
    }

    return ERR_CONN;
}

static err_t rza1_low_level_output(struct netif *netif, struct pbuf *p) {
    struct pbuf *q;
    s32_t       cnt;
    err_t       err        = ERR_MEM;
    s32_t       write_size = 0;

    if ((p->payload != NULL) && (p->len != 0)) {
        /* If the first data can't be written, transmit descriptor is full. */
        for (cnt = 0; cnt < 100; cnt++) {
            write_size = ethernet_write((char *)p->payload, p->len);
            if (write_size != 0) {
                break;
            }
            osDelay(1);
        }
        if (write_size != 0) {
            for (q = p->next; q != NULL; q = q->next) {
                (void)ethernet_write((char *)q->payload, q->len);
            }
            if (ethernet_send() == 1) {
                err = ERR_OK;
            }
        }
    }

    return err;
}

static void rza1_recv_callback(void) {
    sys_sem_signal(&recv_ready_sem);
}

err_t eth_arch_enetif_init(struct netif *netif)
{
    ethernet_cfg_t ethcfg;

    /* set MAC hardware address */
#if (MBED_MAC_ADDRESS_SUM != MBED_MAC_ADDR_INTERFACE)
    netif->hwaddr[0] = MBED_MAC_ADDR_0;
    netif->hwaddr[1] = MBED_MAC_ADDR_1;
    netif->hwaddr[2] = MBED_MAC_ADDR_2;
    netif->hwaddr[3] = MBED_MAC_ADDR_3;
    netif->hwaddr[4] = MBED_MAC_ADDR_4;
    netif->hwaddr[5] = MBED_MAC_ADDR_5;
#else
    mbed_mac_address((char *)netif->hwaddr);
#endif
    netif->hwaddr_len = ETHARP_HWADDR_LEN;

    /* maximum transfer unit */
    netif->mtu = 1500;

    /* device capabilities */
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP;

#if LWIP_NETIF_HOSTNAME
    /* Initialize interface hostname */
    netif->hostname = "lwiprza1";
#endif /* LWIP_NETIF_HOSTNAME */

    netif->name[0] = 'e';
    netif->name[1] = 'n';

    netif->output     = rza1_etharp_output;
    netif->linkoutput = rza1_low_level_output;

    /* Initialize the hardware */
    ethcfg.int_priority = 6;
    ethcfg.recv_cb      = &rza1_recv_callback;
    ethcfg.ether_mac    = (char *)netif->hwaddr;
    ethernetext_init(&ethcfg);

    /* semaphore */
    sys_sem_new(&recv_ready_sem, 0);

    /* task */
    sys_thread_new("rza1_recv_task", rza1_recv_task, netif, DEFAULT_THREAD_STACKSIZE, RECV_TASK_PRI);
    sys_thread_new("rza1_phy_task", rza1_phy_task, netif, DEFAULT_THREAD_STACKSIZE, PHY_TASK_PRI);

    return ERR_OK;
}

void eth_arch_enable_interrupts(void) {
    ethernetext_start_stop(1);
}

void eth_arch_disable_interrupts(void) {
    ethernetext_start_stop(0);
}
