#include "eth.h"

#include "system.h"
// net interface
struct netif *netif, server_netif;
// udp protocol control block
struct udp_pcb *pcb;

struct raw_pcb *udpRawPcb, *tcpRawPcb;
// parameters var
ip_addr_t localIpAddr, netmask, gw;
unsigned char mac_ethernet_address[] = {0x00, 0x0a, 0x35, 0x00, 0x01, 0x02}; // MAC
// suppose in LAN 192.168.1.1/24
// for single udp socket, port 8090<-->8090

uint32_t local_ip = 66;
uint32_t remote_ip = 106;
unsigned local_port = 8090;
unsigned remote_port = 8090;

ip_addr_t tcpServerIp,tcpClientIp;

/// @brief UDP init
/// @return 0 for success.
int UdpInit()
{
	netif = &server_netif;
	// local configuration
	IP4_ADDR(&localIpAddr, 192, 168, 1, local_ip);
	IP4_ADDR(&netmask, 255, 255, 255, 0);
	IP4_ADDR(&gw, 192, 168, 1, 1);

	xil_printf(INFO_MSG("Local : 192.168.1.%3d : %d"), local_ip, local_port);
	xil_printf(INFO_MSG("Remote: 192.168.1.%3d : %d"), remote_ip, remote_port);

	lwip_init();
	if (!xemac_add(netif, &localIpAddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR))
	{
		xil_printf("Error adding N/W interface\r\n");
		return -1;
	} // add port
	netif_set_default(netif);
	netif_set_up(netif); // set up network

	err_t err;

	// udp control
	pcb = udp_new();
	if (!pcb)
	{
		xil_printf("Error Creating PCB.\r\n");
		return -1;
	}
	// local port
	err = udp_bind(pcb, IP_ADDR_ANY, local_port);
	if (err != ERR_OK)
	{
		xil_printf("Unable to bind to port %d\r\n", local_port);
		return -1;
	}

	// set callback function
	udp_recv(pcb, &UdpRecvCallback, NULL);
	// #if LWIP_RAW == 1
	// 	xil_printf("RAW ON\r\n");
	// #else
	// 	xil_printf("RAW OFF\r\n");
	// #endif
	return 0;
}

/// @brief UDP send data
/// @param send_buff
/// @param data_num is the length in Byte of the send_buff, also is the protocol payload length
void UdpSendData(uint8_t *send_buff, int data_num)
{
	err_t err;

	// pbuf
	struct pbuf *pbuf_to_be_sent = NULL;
	pbuf_to_be_sent = pbuf_alloc(PBUF_TRANSPORT, data_num, PBUF_POOL);
	memcpy(pbuf_to_be_sent->payload, (u8 *)send_buff, data_num);

	// send
	ip_addr_t addr_to_be_sent;
	IP4_ADDR(&addr_to_be_sent, 192, 168, 1, remote_ip);
	err = udp_sendto(pcb, pbuf_to_be_sent, &addr_to_be_sent, remote_port);
	if (err != ERR_OK)
	{
		xil_printf("Error on udp send : %d\r\n", err);
		pbuf_free(pbuf_to_be_sent);
		return;
	}
	pbuf_free(pbuf_to_be_sent); // free pbuf
}

/// @brief receive the udp packet from ethernet chip
/// @param p contains the udp data packet
/// @param addr source ip
/// @param port source port
void UdpRecvCallback(void *arg, struct udp_pcb *tpcb, struct pbuf *p, ip_addr_t *addr, u16_t port)
{
	// NOTE: payload is the pure udp data
	// 		 len is the length of the udp data
	//		 not including udp header.
	if (p->len > 1472)
	{
		LOG_ERROR("UDP packet size too large");
	}
	LOG_INFO("Recv eth packet");
	return;
}

int RawInit()
{
	netif = &server_netif;
	// local configuration
	IP4_ADDR(&localIpAddr, 192, 168, 1, local_ip);
	IP4_ADDR(&netmask, 255, 255, 255, 0);
	IP4_ADDR(&gw, 192, 168, 1, 1);

	PRINT_IP(localIpAddr, "Local IP:");
	PRINT_IP(netmask, "Netmask :");
	PRINT_IP(gw, "Gateway :");

	lwip_init();
	if (!xemac_add(netif, &localIpAddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR))
	{
		xil_printf("Error adding N/W interface\r\n");
		return -1;
	} // add port
	netif_set_default(netif);
	netif_set_up(netif); // set up network

	tcpRawPcb = raw_new(IP_PROTO_TCP);
	tcpRawPcb->flags = RAW_FLAGS_HDRINCL;
	if (!tcpRawPcb)
	{
		xil_printf("Error Creating TCP PCB.\r\n");
		return -1;
	}

	IP4_ADDR(&tcpServerIp, 192, 168, 1, 105);
	PRINT_IP(tcpServerIp,"TCP Server IP:");
	IP4_ADDR(&tcpClientIp, 192, 168, 1, 101);
	PRINT_IP(tcpClientIp,"TCP Client IP:");

	udpRawPcb = raw_new(IP_PROTO_UDP);
	udpRawPcb->flags = RAW_FLAGS_HDRINCL;
	raw_bind(udpRawPcb, IP_ADDR_ANY);
	if (!udpRawPcb)
	{
		xil_printf("Error Creating UDP PCB.\r\n");
		return -1;
	}

	raw_recv(tcpRawPcb, RawTcpRecvCallback, NULL);
	//	raw_recv(udpRawPcb, RawUdpRecvCallback, NULL);
}

uint8_t RawUdpSend(uint8_t *send_buff, int data_num)
{
	int status;
	err_t err;
	ip_addr_t addr_to_be_sent;
	IP4_ADDR(&addr_to_be_sent, 192, 168, 1, remote_ip);

	// pbuf
	struct pbuf *pbuf_to_be_sent = NULL;
	pbuf_to_be_sent = pbuf_alloc(PBUF_IP, data_num, PBUF_POOL);
	memcpy(pbuf_to_be_sent->payload, (u8 *)send_buff, data_num);

	err = raw_sendto_if_src(udpRawPcb, pbuf_to_be_sent, &addr_to_be_sent,
							netif, &localIpAddr);
	if (err != ERR_OK)
	{
		xil_printf("Error on udp send : %d\r\n", err);
		pbuf_free(pbuf_to_be_sent);
		return;
	}
	pbuf_free(pbuf_to_be_sent); // free pbuf
}

uint8_t RawTcpSend(uint8_t *send_buff, int data_num)
{
	int status;
	err_t err;
	ip_addr_t addr_to_be_sent;
	IP4_ADDR(&addr_to_be_sent, 192, 168, 1, remote_ip);
	// pbuf
	struct pbuf *pbuf_to_be_sent = pbuf_alloc(PBUF_IP, data_num, PBUF_POOL);
	memcpy(pbuf_to_be_sent->payload, (u8 *)send_buff, data_num);


	err = raw_sendto_if_src(tcpRawPcb, pbuf_to_be_sent, &addr_to_be_sent,
							netif, &localIpAddr);

//	ethernet_output(netif, pbuf_to_be_sent, (struct eth_addr *)(netif->hwaddr), (const struct eth_addr *)&ethbroadcast, ETHTYPE_IP);

	if (err != ERR_OK)
	{
		xil_printf("Error on udp send : %d\r\n", err);
		pbuf_free(pbuf_to_be_sent);
		return -1;
	}
	pbuf_free(pbuf_to_be_sent); // free pbuf
	return 0;
}

u8_t RawUdpRecvCallback(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr)
{
	struct ip_hdr *ipHeader = (struct ip_hdr *)p->payload;
	struct udp_hdr *udpHeader = (struct udp_hdr *)((uint8_t *)ipHeader + IP_HLEN);
	PRINT_IP(ipHeader->src, "Src IP");
	PRINT_IP(ipHeader->dest, "Dst IP");
	xil_printf(INFO_MSG("port: %d --> %d"), lwip_htons(udpHeader->src), lwip_htons(udpHeader->dest));
	LOG_BLANK_LINE;
	//	if(udpHeader->dest != 777) return -17;
	//	RawUdpSend(udpPacket, p->len-20);
	pbuf_free(p);
	return 0;
}


u8_t RawTcpRecvCallback(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr)
{
	err_t err;
	struct ip_hdr *ipHeader = (struct ip_hdr *)p->payload;
	struct tcp_hdr *tcpHeader = (struct tcp_hdr *)((uint8_t *)ipHeader + IP_HLEN);
	PRINT_IP(ipHeader->src, "Src IP");
	PRINT_IP(ipHeader->dest, "Dst IP");
//	xil_printf(INFO_MSG("port: %d --> %d"), lwip_htons(tcpHeader->src), lwip_htons(tcpHeader->dest));
//	xil_printf(INFO_MSG("flag is 0x%x, payload len is %d"),(TCPH_FLAGS((tcpHeader))),p->len);

	if(CHECKSUM_CHECK_IP)
	{
		xil_printf("HI");
	}
	// client to server
	if(ipHeader->src.addr == tcpClientIp.addr)
	{
//		LOG_INFO("Recv TCP Client Packet");
		ipHeader->src.addr = localIpAddr.addr;
		ipHeader->dest.addr = tcpServerIp.addr;
		err =  RawTcpSend(p->payload, p->len);
	}

	// server to client
	if(ipHeader->src.addr == tcpServerIp.addr)
	{
//		LOG_INFO("Recv TCP Server Packet");
//		ipHeader->src.addr = localIpAddr.addr;
//		ipHeader->dest.addr = tcpClientIp.addr;
		err = RawTcpSend(p->payload, p->len);
	}


	LOG_BLANK_LINE;
	pbuf_free(p);
	return 0;
}

/// @brief hook from ip4_input() function
/// @param pbuf received struct pbuf passed to ip_input()
/// @param input_netif struct netif on which the packet has been received
/// @return always 1
int IPv4InputHook(struct pbuf *pbuf, struct netif *input_netif)
{
	struct ip_hdr *ipHeader = (struct ip_hdr *)p->payload;
	PRINT_IP(ipHeader->src, "Src IP");
	PRINT_IP(ipHeader->dest, "Dst IP");
	pbuf_free(pbuf);
	return 1;
}
