#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: 10 minutes)
#define NTP_QUERY_INTERVAL		(10ull*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 NTP_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.

//#define ROOM_SERVER	"192.168.0.80"

typedef struct
{
	char	req_id[2];
	char	flags[2];
	char	PicVer[2];
	char	reserve[2];
	char	req_callsign[8];
	char	zone[8];
	char	area[8];
	unsigned long	ip_address;
	int	ssn;
	int	port;
} call_table;

typedef struct{		
	unsigned char	id[4];
	unsigned char	flags[2];
	unsigned char	reserve[2];
	unsigned char	type_id;
	unsigned char	dest_repeater_id;
	unsigned char	send_repeater_id;
	unsigned char	send_terminal_id;
	unsigned char	seq_high;
	unsigned char	seq_low;
	unsigned char	control;
	unsigned char	body[41];
} packet;

typedef struct{		
	unsigned char	id[4];
	unsigned char	flags[2];
	unsigned char	reserve[2];
	unsigned char	type_id;
	unsigned char	dest_repeater_id;
	unsigned char	send_repeater_id;
	unsigned char	send_terminal_id;
	unsigned char	seq_high;
	unsigned char	seq_low;
	unsigned char	control;
	unsigned char	voice[12];
} voice_pkt;

void	DisplayIPValue(DWORD dwServerIP, BYTE LCDText[]);
BOOL	CheckForNewAttach ( void );
void	usb_send_header (BYTE pkt[]);
void	usb_send_voice (BYTE pkt[]);
void	RF_CallsignPrint ( void );
void	send_dv_header(packet pkt);
void	send_dv_packet(packet pkt);
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	0x00


/*****************************************************************************
  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)
{
	call_table			RoomEntry_PACKET;
	packet				pkt, q_pkt;
	int			 		i;
   	int		addrlen = sizeof(struct sockaddr_in);
	BYTE	*pDesc;

		
	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;
			pDesc  = USBHostGetDeviceDescriptor(1);
			if (pDesc)
			{
	    		pDesc += 12;
				RoomEntry_PACKET.PicVer[1] = *pDesc++;
				RoomEntry_PACKET.PicVer[0] = *pDesc;
			} else {
				RoomEntry_PACKET.PicVer[1] = 0x00;
				RoomEntry_PACKET.PicVer[0] = 0x00;
			}

			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 > (NTP_FAST_QUERY_INTERVAL/65536ull))
				RoomClientState = SM_HOME;	
			break;

		case SM_WAIT:
			if(TickGetDiv64K() - dwTimer > (NTP_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;

	}
}

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

}

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



void	send_dv_callEntryUpdt()
{
	static call_table pkt;
	BYTE	*pDesc;

		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, "RI", 2);
		pkt.flags[0] = VerH;
		pkt.flags[1] = VerL;
		pkt.ssn = SSN;
		pkt.port = AppConfig.DefaultInPort;
		pDesc  = USBHostGetDeviceDescriptor(1);
    	pDesc += 12;
		pkt.PicVer[1] = *pDesc++;
		pkt.PicVer[0] = *pDesc;

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

}

