#include "TCPIPConfig.h"

#include "TCPIP Stack/TCPIP.h"
#include "TCPIP Stack/StackTsk.h"
#include "TCPIP Stack/BerkeleyAPI.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "lcd.h"

#include <stdlib.h>
#include "GenericTypeDefs.h"
#include "HardwareProfile.h"
#include "usb_config.h"
#include "USB/usb.h"
#include "USB/usb_host_generic.h"
#include "user.h"
#include "../include/USB/timer.h"
#include "../USB/usb_host_local.h"
#include "node.h"


// Defines how frequently to resynchronize the date/time (default: 5 minutes)
#define SERVER_QUERY_INTERVAL		(5ull*60ull * TICK_SECOND)

// Defines how long to wait to retry an update after a failure.
// Updates may take up to 6 seconds to fail, so this 14 second delay is actually only an 8-second retry.
#define SERVER_FAST_QUERY_INTERVAL	(14ull * TICK_SECOND)

// Reference Epoch to use.  (default: 01-Jan-1970 00:00:00)
//#define NTP_EPOCH 				(86400ul * (365ul * 70ul + 17ul))

// Defines how long to wait before assuming the query has failed
#define SERVER_REPLY_TIMEOUT		(10ul*TICK_SECOND)

// These are normally available network time servers.
// The actual IP returned from the pool will vary every
// minute so as to spread the load around stratum 1 timeservers.
// For best accuracy and network overhead you should locate the 
// pool server closest to your geography, but it will still work
// if you use the global pool.ntp.org address or choose the wrong 
// one or ship your embedded device to another geography.


void	DisplayIPValue(DWORD dwServerIP, BYTE LCDText[]);
BOOL	CheckForNewAttach ( void );
static	void	send_dv_header(packet pkt);
static	void	send_dv_packet(packet pkt);
static	void	send_dv_callEntryUpdt(void);

BYTE        deviceAddress;  // Address of the device on the USB

SOCKET		bsdUdpClientSend, bsdUdpClientRcv;
struct sockaddr_in	udpaddrSend, udpaddrRcv;

static DWORD		dwTimer;

static	BYTE	LCDText[32];

extern BYTE RoomServerDomainName[64];
extern BYTE	RoomName[8];
extern BYTE	NodeName[8];
extern	WORD out_port;
extern	WORD in_port;

extern	WORD	seq;
extern	DWORD	dwServerIP;
extern	WORD	SSN;


#define	VerH	0x0a
#define	VerL	0x05


/*****************************************************************************
  Function:
	void BerkeleyUDPClientDemo(void)

  Summary:
	Periodically checks the current time from a pool of servers.

  Description:
	This function periodically checks a pool of time servers to obtain the
	current date/time.

  Precondition:
	UDP is initialized.

  Parameters:
	None

  Returns:
  	None
  	
  Remarks:
	This function requires once available UDP socket while processing, but
	frees that socket when the SNTP module is idle.
  ***************************************************************************/

	static enum
	{
		SM_HOME = 0,
		SM_NAME_RESOLVE,
		SM_CREATE_SOCKET,
		//SM_BIND,	// Not required since we are sending the first packet
		SM_UDP_SEND,
		SM_UDP_RECV,
		SM_SHORT_WAIT,
		SM_WAIT,
		SM_LOOP
	} RoomClientState = SM_HOME;


extern	xQueueHandle xDstarSndQueue;
extern	xQueueHandle xDstarRcvQueue;

void DstarRoomClientTCP(void)
{
	static	call_table	RoomEntry_PACKET;
	static	packet		pkt, q_pkt;
	static	int			i;
   	static	int			addrlen = sizeof(struct sockaddr_in);
	extern	BYTE	PicVer[2];

		
	switch(RoomClientState)
	{
		case SM_HOME:
			// Obtain ownership of the DNS resolution module
			if(!DNSBeginUsage())
				break;

			// Obtain the IP address associated with the server name
			DNSResolve((BYTE *)AppConfig.ServerName, DNS_TYPE_A);
			dwTimer = TickGet();
			RoomClientState = SM_NAME_RESOLVE;
			break;

		case SM_NAME_RESOLVE:
			// Wait for DNS resolution to complete
			if(!DNSIsResolved((IP_ADDR*)&dwServerIP)) 
			{
				if((TickGet() - dwTimer) > (5 * TICK_SECOND)) 
				{
					DNSEndUsage();
					dwTimer = TickGetDiv64K();
					RoomClientState = SM_SHORT_WAIT;
				}
				break;
			}
			
			// Obtain DNS resolution result
			if(!DNSEndUsage())
			{
				// No valid IP address was returned from the DNS 
				// server.  Quit and fail for a while if host is not valid.
				dwTimer = TickGetDiv64K();
				RoomClientState = SM_SHORT_WAIT;
				break;
			}
			RoomClientState = SM_CREATE_SOCKET;

		case SM_CREATE_SOCKET:
			bsdUdpClientSend = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
			if(bsdUdpClientSend == INVALID_SOCKET)	return;

			memset(&udpaddrSend, 0, addrlen);
            udpaddrSend.sin_port = AppConfig.DefaultOutPort;
            udpaddrSend.sin_addr.S_un.S_addr = dwServerIP;
			udpaddrSend.sin_family = AF_INET;
			bind(bsdUdpClientSend, (struct sockaddr *)&udpaddrSend, addrlen);

			bsdUdpClientRcv = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
			if(bsdUdpClientRcv == INVALID_SOCKET)	return;

			memset(&udpaddrRcv, 0, addrlen);
            udpaddrRcv.sin_port = AppConfig.DefaultInPort;
            udpaddrRcv.sin_addr.S_un.S_addr = INADDR_ANY;
			udpaddrRcv.sin_family = AF_INET;
			bind(bsdUdpClientRcv, (struct sockaddr *)&udpaddrRcv, addrlen);

        	RoomClientState = SM_UDP_SEND;

            // No break needed
            
		case SM_UDP_SEND:
            // Entry packet to server
			strncpy (RoomEntry_PACKET.zone, AppConfig.DefaultRoomName, 8);
			strncpy (RoomEntry_PACKET.area, AppConfig.DefaultNodeName, 8);
			strncpy (RoomEntry_PACKET.req_callsign, AppConfig.DefaultNodeName, 8);
			RoomEntry_PACKET.ip_address = 0;
			strncpy (RoomEntry_PACKET.req_id, "SI", 2);
			RoomEntry_PACKET.flags[0] = VerH;
			RoomEntry_PACKET.flags[1] = VerL;
			RoomEntry_PACKET.ssn = 0;
			RoomEntry_PACKET.port = AppConfig.DefaultInPort;
			RoomEntry_PACKET.PicVer[1] = PicVer[1];
			RoomEntry_PACKET.PicVer[0] = PicVer[0];

			if(sendto(bsdUdpClientSend, (const char*)&RoomEntry_PACKET, 
							sizeof(RoomEntry_PACKET), 0, (struct sockaddr*)&udpaddrSend, addrlen)>0)
            {
				dwTimer = TickGet();
                RoomClientState = SM_UDP_RECV;
            }
			break;

		case SM_UDP_RECV:
			// Look for a response time packet
			i = recvfrom(bsdUdpClientRcv, (char*)&RoomEntry_PACKET, 16, 0, 
										(struct sockaddr*)&udpaddrRcv, &addrlen);
            if(i != 16)
			{
				if((TickGet()) - dwTimer > SERVER_REPLY_TIMEOUT)
				{
					// Abort the request and wait until the next timeout period
					closesocket(bsdUdpClientSend);
					closesocket(bsdUdpClientRcv);
					memcpy (LCDText, "Server Down!    ", 16);
					DisplayIPValue(dwServerIP, LCDText);
					xMessage.pcMessage = LCDText;
			 		xMessage.xMinDisplayTime = 2000 / portTICK_RATE_MS;
					xQueueSend( xLCDQueue, &xMessage, 0 );
					RoomClientState = SM_WAIT;
					break;
				}
				break;
			}
			RoomClientState = SM_LOOP;
			memcpy (LCDText, "Connected to    ", 16);
			DisplayIPValue(dwServerIP, LCDText);
			xMessage.pcMessage = LCDText;
	 		xMessage.xMinDisplayTime = 2000 / portTICK_RATE_MS;
			xQueueSend( xLCDQueue, &xMessage, 0 );
			break;

		case SM_SHORT_WAIT:
			if(TickGetDiv64K() - dwTimer > (SERVER_FAST_QUERY_INTERVAL/65536ull))
				RoomClientState = SM_HOME;	
			break;

		case SM_WAIT:
			if(TickGetDiv64K() - dwTimer > (SERVER_QUERY_INTERVAL/65536ull))
				RoomClientState = SM_HOME;	
			break;

		case SM_LOOP:
			i = recvfrom(bsdUdpClientRcv, (char*)&pkt, 56, 0, 
										(struct sockaddr*)&udpaddrRcv, &addrlen);
			if (i == 56)
			{
				xQueueSendToFront (xDstarRcvQueue, &pkt, 0);
			}
			else if (i == 27)
			{
				xQueueSendToBack (xDstarRcvQueue, &pkt, 0);
			}
			/* From Rig */
			if (xQueueReceive(xDstarSndQueue, &q_pkt, 0) == pdTRUE)
			{
				if (q_pkt.type_id == 0x20)
				{
					send_dv_packet (q_pkt);
				}
				else if (q_pkt.type_id == 0x10)
				{
					send_dv_header(q_pkt);
				}
			}	

			break;

	}
}

static	void	send_dv_header(packet pkt)
{
			send_dv_callEntryUpdt();
			sendto(bsdUdpClientSend, (char*)&pkt, 56, 0, 
							(struct sockaddr*)&udpaddrSend, sizeof(struct sockaddr_in));

}

static	void	send_dv_packet(packet pkt)
{
			sendto(bsdUdpClientSend, (char*)&pkt, 27, 0, 
							(struct sockaddr*)&udpaddrSend, sizeof(struct sockaddr_in));
}



static	void	send_dv_callEntryUpdt()
{
	static call_table pkt;
	extern	BYTE PicVer[2];

		memcpy (pkt.zone, AppConfig.DefaultRoomName, 8);
		memcpy (pkt.area, AppConfig.DefaultNodeName, 8);
		memcpy (pkt.req_callsign, AppConfig.DefaultNodeName, 8);
		pkt.ip_address = 0;
		memcpy (pkt.req_id, "RS", 2);
		pkt.flags[0] = VerH;
		pkt.flags[1] = VerL;
		pkt.ssn = SSN;
		pkt.port = AppConfig.DefaultInPort;
		pkt.PicVer[1] = PicVer[1];
		pkt.PicVer[0] = PicVer[0];;

		sendto (bsdUdpClientSend, (char *)&pkt, sizeof(call_table), 0, 
					(struct sockaddr *)&udpaddrSend, sizeof(struct sockaddr_in)); 
	
	return;

}

