#include "at32f403a_407.h"
#include "usb_eth.h"
#include "dhserver.h"
#include "dnserver.h"
#include "usbd_rndis_core.h"
#include "netif/etharp.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/pbuf.h"
#include "lwip/icmp.h"
#include "lwip/udp.h"
#include "lwip/opt.h"
#include "lwip/arch.h"
#include "lwip/api.h"
#include "lwip/inet.h"
#include "lwip/dns.h"
#include "tcpip.h"
#include "usbd_core.h"
#include "usb.h"
#include "common.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdbool.h>
#include <stdlib.h>



static uint8_t hwaddr[6]  = {0x20,0x89,0x84,0x6A,0x96,00};
static uint8_t ipaddr[4]  = {192, 168, 7, 1};
static uint8_t netmask[4] = {255, 255, 255, 0};
static uint8_t gateway[4] = {0, 0, 0, 0};

#define USE_LINK_LED	0

#define NUM_DHCP_ENTRY 3

static dhcp_entry_t entries[NUM_DHCP_ENTRY] =
{
	// mac    ip address        subnet mask        lease time
	{ {0}, {192, 168, 7, 2}, {255, 255, 255, 0}, 24 * 60 * 60 },
	{ {0}, {192, 168, 7, 3}, {255, 255, 255, 0}, 24 * 60 * 60 },
	{ {0}, {192, 168, 7, 4}, {255, 255, 255, 0}, 24 * 60 * 60 }
};

static dhcp_config_t dhcp_config =
{
	{192, 168, 7, 1}, 67, // server address, port
	{192, 168, 7, 1},     // dns server
	"prs",                // dns suffix
	NUM_DHCP_ENTRY,       // num entry
	entries               // entries
};

struct netif netif_data;


static uint8_t received[RNDIS_MTU + 14];
static int recvSize = 0;

SemaphoreHandle_t xSemUsbEth;


void on_packet(const char *data, int size)
{
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;;

	memcpy(received, data, size);
	recvSize = size;
	xSemaphoreGiveFromISR( xSemUsbEth, &xHigherPriorityTaskWoken );

	if( xHigherPriorityTaskWoken != pdFALSE )
	{
        taskYIELD();
	}
}

void usb_polling()
{
	xSemaphoreTake( xSemUsbEth, portMAX_DELAY);

	if (recvSize == 0) 
		return;

	struct pbuf *frame;
	frame = pbuf_alloc(PBUF_RAW, recvSize, PBUF_POOL);

	if (frame == NULL) 
		return;

	memcpy(frame->payload, received, recvSize);
	frame->len = recvSize;
	recvSize = 0;

	tcpip_input(frame, &netif_data);

	pbuf_free(frame);
}

static int outputs = 0;

err_t output_fn(struct netif *netif, struct pbuf *p, ip_addr_t *ipaddr)
{
	return etharp_output(netif, p, ipaddr);
}

err_t linkoutput_fn(struct netif *netif, struct pbuf *p)
{
	int i;
	struct pbuf *q;
	static char data[RNDIS_MTU + 14 + 4];
	int size = 0;
	
	for (i = 0; i < 200; i++)
	{
		if (rndis_can_send()) break;
		vTaskDelay(1);
	}

  	for(q = p; q != NULL; q = q->next)
	{
		if (size + q->len > RNDIS_MTU + 14)
			return ERR_ARG;
		memcpy(data + size, (char *)q->payload, q->len);
		size += q->len;
	}

	if (!rndis_can_send())
		return ERR_USE;

	rndis_send(data, size);
	outputs++;
	return ERR_OK;
}

err_t netif_init_cb(struct netif *netif)
{
	LWIP_ASSERT("netif != NULL", (netif != NULL));
	netif->mtu = RNDIS_MTU;
	netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP;
	netif->state = NULL;
	netif->name[0] = 'E';
	netif->name[1] = 'X';
	netif->linkoutput = linkoutput_fn;
    netif->output = (netif_output_fn)output_fn;
	return ERR_OK;
}

#define PADDR(ptr) ((ip_addr_t *)ptr)

void init_lwip()
{
	struct netif  *netif = &netif_data;

	netif->hwaddr_len = 6;
	memcpy(netif->hwaddr, hwaddr, 6);

	netif = netif_add(netif, PADDR(ipaddr), PADDR(netmask), PADDR(gateway), NULL, netif_init_cb, tcpip_input);
	netif_set_link_up(netif);
	netif_set_up(netif);
}

void usb_hw_init(void)
{
	usb_init();
	rndis_rxproc = on_packet;

	xSemUsbEth = xSemaphoreCreateBinary();
}

bool dns_query_proc(const char *name, ip_addr_t *addr)
{
	if (strcmp(name, "energomera.prs") == 0 || strcmp(name, "www.energomera.prs") == 0)
	{
		addr->addr = *(uint32_t *)ipaddr;
		return true;
	}
	return false;
}

void usb_eth_task(void *arg)
{
    while (1)
    {
        usb_polling();     // usb device polling
    }
}

void usb_eth_init(void)
{
	usb_hw_init();

    tcpip_init(NULL, NULL);
    
	init_lwip();

	while (!netif_is_up(&netif_data)) ;

	while (dhserv_init(&dhcp_config) != ERR_OK) ;

	while (dnserv_init(PADDR(ipaddr), 53, dns_query_proc) != ERR_OK) ;
	
    xTaskCreate(usb_eth_task, ( char * ) "usb_eth_task", 3*configMINIMAL_STACK_SIZE, NULL, 2, NULL);
}
