#include <unistd.h>
#include <pcap/pcap.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <assert.h>
#include <marsio.h>

#include <tfe_utils.h>
#include <tfe_proxy.h>
#include <traffic_mirror.h>

static char * mr4_ethdev_send_prepare(struct traffic_mirror_ethdev * ethdev, unsigned int tid)
{
    struct traffic_mirror_ethdev_mr4 * detail_mr4 = ethdev->detail_mr4;
    assert(tid < detail_mr4->nr_tx_threads);

    marsio_buff_t * buff;
    int ret = marsio_buff_malloc_device(detail_mr4->device, &buff, 1, MARSIO_SOCKET_ID_ANY, MARSIO_LCORE_ID_ANY);
    if (unlikely(ret < 0))
    {
        assert(0);
        return NULL;
    }

    char * buff_start = marsio_buff_mtod(buff);
    if (unlikely(!buff_start))
    {
        assert(0);
        return NULL;
    }

    detail_mr4->sendbuf[tid] = buff;
    return buff_start;
}

static int mr4_ethdev_send_finish(struct traffic_mirror_ethdev * ethdev, unsigned int tid, unsigned int pktlen)
{
    struct traffic_mirror_ethdev_mr4 * detail_mr4 = ethdev->detail_mr4;
    assert(detail_mr4 != NULL && detail_mr4->sendbuf[tid] != NULL);

    marsio_buff_t * buff = detail_mr4->sendbuf[tid];
    UNUSED char * append_ptr = marsio_buff_append(buff, pktlen);
    assert(append_ptr != NULL);

    int ret = marsio_send_burst_with_options(detail_mr4->sendpath, tid, &buff, 1, MARSIO_SEND_OPT_REHASH);
    if (unlikely(ret < 0))
    {
        marsio_buff_free(detail_mr4->instance, &buff, 1, MARSIO_SOCKET_ID_ANY, MARSIO_LCORE_ID_ANY);
        return -1;
    }

    detail_mr4->sendbuf[tid] = NULL;
    return 0;
}

static void mr4_ethdev_send_abort(struct traffic_mirror_ethdev * ethdev, unsigned int tid)
{
    struct traffic_mirror_ethdev_mr4 * detail_mr4 = ethdev->detail_mr4;
    assert(detail_mr4 != NULL && detail_mr4->sendbuf[tid] != NULL);

    marsio_buff_t * buff = detail_mr4->sendbuf[tid];
    marsio_buff_free(detail_mr4->instance, &buff, 1, MARSIO_SOCKET_ID_ANY, MARSIO_LCORE_ID_ANY);
    detail_mr4->sendbuf[tid] = NULL;
}

static void mr4_ethdev_destroy(struct traffic_mirror_ethdev * ethdev)
{
    struct traffic_mirror_ethdev_mr4 * detail_mr4 = ethdev->detail_mr4;
    if(detail_mr4 && detail_mr4->sendpath)
    {
        marsio_sendpath_destory(detail_mr4->sendpath);
    }

    if(detail_mr4 && detail_mr4->device)
    {
        marsio_close_device(detail_mr4->device);
    }

    if(detail_mr4 && detail_mr4->instance)
    {
        marsio_destory(detail_mr4->instance);
    }

    if(detail_mr4)
    {
        free(detail_mr4);
    }

    free(ethdev);
}

/* =============================== MODE LIBPCAP ========================================== */
static char * pcap_ethdev_send_prepare(struct traffic_mirror_ethdev *ethdev, unsigned int tid)
{
    struct traffic_mirror_ethdev_pcap * detail_pcap = ethdev->detail_pcap;
    return detail_pcap->sendbuf[tid];
}

static int pcap_ethdev_send_finish(struct traffic_mirror_ethdev * ethdev, unsigned int tid, unsigned int pktlen)
{
    struct traffic_mirror_ethdev_pcap * detail_pcap = ethdev->detail_pcap;
    assert(detail_pcap != NULL && detail_pcap->sendbuf[tid] != NULL);

    char * sendbuf = detail_pcap->sendbuf[tid];
    return pcap_sendpacket(detail_pcap->pcap_device_handle, (const u_char *)sendbuf, pktlen);
}

static void pcap_ethdev_send_abort(struct traffic_mirror_ethdev * ethdev, unsigned int tid)
{
    return;
}

static void pcap_ethdev_destroy(struct traffic_mirror_ethdev * ethdev)
{
    pcap_close(ethdev->detail_pcap->pcap_device_handle);
    return free(ethdev);
}

/* =============================== VIRTUAL INTERFACE START ========================================== */
void traffic_mirror_ethdev_destroy(struct traffic_mirror_ethdev * ethdev)
{
    ethdev->fn_destroy(ethdev);
}

int traffic_mirror_ethdev_finish(struct traffic_mirror_ethdev * ethdev, unsigned int tid, unsigned int pktlen)
{
    return ethdev->fn_send_finish(ethdev, tid, pktlen);
}

char * traffic_mirror_ethdev_send_prepare(struct traffic_mirror_ethdev * ethdev, unsigned int tid)
{
    return ethdev->fn_send_prepare(ethdev, tid);
}

/* =============================== VIRTUAL INTERFACE END =========================================== */

struct traffic_mirror_ethdev * traffic_mirror_ethdev_mr4_create(const char * str_ethdev,
    unsigned int nr_threads, void * logger)
{
    struct traffic_mirror_ethdev * ethdev = ALLOC(struct traffic_mirror_ethdev, 1);
    ethdev->type = TRAFFIC_MIRROR_ETHDEV_MARSIO;

    ethdev->detail_mr4 = ALLOC(struct traffic_mirror_ethdev_mr4, 1);
    strncpy(ethdev->str_device, str_ethdev, sizeof(ethdev->str_device) - 1);

    /* PREPARE MR4 INSTANCES */
    struct traffic_mirror_ethdev_mr4 * detail_mr4 = ethdev->detail_mr4;
    detail_mr4->nr_tx_threads = nr_threads;

    /* create a marsio instance and setup options,
     * the instance need to run in no-thread-affinity mode */
    detail_mr4->instance = marsio_create();
    assert(detail_mr4->instance != NULL);

    if (marsio_init(detail_mr4->instance, "tfe-traffic-mirror") < 0)
    {
        TFE_LOG_ERROR(logger, "failed at init MARSIOv4 instance.");
        goto errout;
    }

    detail_mr4->device = marsio_open_device(detail_mr4->instance, str_ethdev, 0, nr_threads);
    if (!detail_mr4->device)
    {
        TFE_LOG_ERROR(logger, "failed at open device %s, nr_threads = %u", str_ethdev, nr_threads);
        goto errout;
    }

    detail_mr4->sendpath = marsio_sendpath_create_by_vdev(detail_mr4->device);
    if (!detail_mr4->sendpath)
    {
        TFE_LOG_ERROR(logger, "failed at creating sendpath for device %s", str_ethdev);
        goto errout;
    }

    /* TODO: load the mtu and mac address from mrzcpd */
    ethdev->mtu = ETHER_MAX_LEN;
    ethdev->fn_send_prepare = mr4_ethdev_send_prepare;
    ethdev->fn_send_finish = mr4_ethdev_send_finish;
    ethdev->fn_send_abort = mr4_ethdev_send_abort;
    ethdev->fn_destroy = mr4_ethdev_destroy;

    return ethdev;

errout:
    if (ethdev)
    {
        mr4_ethdev_destroy(ethdev);
    }
    return NULL;
}

struct traffic_mirror_ethdev * traffic_mirror_ethdev_pcap_create(const char * str_ethdev, void * logger)
{
    struct traffic_mirror_ethdev * ethdev = ALLOC(struct traffic_mirror_ethdev, 1);
    char pcap_errbuf[PCAP_ERRBUF_SIZE] = {};

    int fd = 0;
    struct ifreq if_req{};

    ethdev->en_offload_ip_cksum = 0;
    ethdev->en_offload_tcp_cksum = 0;
    ethdev->en_offload_vlan = 0;

    ethdev->detail_pcap = ALLOC(struct traffic_mirror_ethdev_pcap, 1);
    ethdev->detail_pcap->pcap_device_handle = pcap_open_live(str_ethdev, 0, 0, 0, pcap_errbuf);
    if (!ethdev->detail_pcap->pcap_device_handle)
    {
        TFE_LOG_ERROR(logger, "failed at pcap_open_live(), device = %s: %s", str_ethdev, pcap_errbuf);
        goto errout;
    }

    /* local ether address */
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0)
    {
        TFE_LOG_ERROR(logger, "failed at create socket: %s", strerror(errno));
        /* after log, reset errno */
		errno = 0;
        goto errout;
    }

    memset(&if_req, 0, sizeof(if_req));
    strncpy(if_req.ifr_ifrn.ifrn_name, str_ethdev, IFNAMSIZ - 1);

    if(ioctl(fd, SIOCGIFHWADDR, &if_req) < 0)
    {
        TFE_LOG_ERROR(logger, "failed at read hwaddr of device %s: %s", str_ethdev, strerror(errno));
        /* after log, reset errno */
		errno = 0;
        goto errout;
    }

    for(int i = 0; i < 6; i++)
    {
        ethdev->local_ether_addr[i] = if_req.ifr_ifru.ifru_hwaddr.sa_data[i];
    }

    /* MTU */
    memset(&if_req, 0, sizeof(if_req));
    strncpy(if_req.ifr_ifrn.ifrn_name, str_ethdev, IFNAMSIZ - 1);

    if (ioctl(fd, SIOCGIFMTU, &if_req) < 0)
    {
        TFE_LOG_ERROR(logger, "failed at read mtu of device %s: %s", str_ethdev, strerror(errno));
        /* after log, reset errno */
		errno = 0;
        goto errout;
    }

    ethdev->mtu = if_req.ifr_ifru.ifru_mtu;
    strncpy(ethdev->str_device, str_ethdev, sizeof(ethdev->str_device) - 1);

    TFE_LOG_INFO(logger, "traffic mirror device %s (pcap mode): %02X:%02X:%02X:%02X:%02X:%02X, mtu=%u",
        ethdev->str_device, ethdev->local_ether_addr[0], ethdev->local_ether_addr[1],
        ethdev->local_ether_addr[2], ethdev->local_ether_addr[3],
        ethdev->local_ether_addr[4], ethdev->local_ether_addr[5], ethdev->mtu);

    ethdev->fn_send_prepare = pcap_ethdev_send_prepare;
    ethdev->fn_send_finish = pcap_ethdev_send_finish;
    ethdev->fn_send_abort = pcap_ethdev_send_abort;
    ethdev->fn_destroy = pcap_ethdev_destroy;

    close(fd);
    return ethdev;

errout:
    if (fd > 0) close(fd);
    if (ethdev->detail_pcap->pcap_device_handle) pcap_close(ethdev->detail_pcap->pcap_device_handle);
    if (ethdev) FREE(&ethdev);
    return nullptr;
}
