#include "TCPIPConfig.h"

#include "TCPIP Stack/TCPIP.h"
#include "TCPIP Stack/StackTsk.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 "../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 SERVER_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 SERVER_FAST_QUERY_INTERVAL	(14ull * TICK_SECOND)

// Defines how long to wait before assuming the query has failed
#define SERVER_REPLY_TIMEOUT		(20ul*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"

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);

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

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;
extern	BYTE	PicVer[2];

#define	VerH	0x0a
#define	VerL	0x00

enum
	{
		DS_HOME = 0,
		DS_UDP_IS_OPENED,
		//SM_NAME_RESOLVE,
		//SM_ARP_START_RESOLVE,
		//SM_ARP_RESOLVE,
		//SM_ARP_START_RESOLVE2,
		//SM_ARP_RESOLVE2,
		//SM_ARP_START_RESOLVE3,
		//SM_ARP_RESOLVE3,
		//SM_ARP_RESOLVE_FAIL,
		DS_UDP_SEND,
		DS_UDP_RECV,
		DS_SHORT_WAIT,
		DS_WAIT,
		DS_LOOP
	} RoomClientState = DS_HOME;

extern	xQueueHandle xDstarSndQueue;
extern	xQueueHandle xDstarRcvQueue;

void DstarRoomClientTCP(void)
{
	static UDP_SOCKET	SendSocket = INVALID_UDP_SOCKET;
	static UDP_SOCKET	RcvSocket = INVALID_UDP_SOCKET;
	static	call_table			RoomEntry_PACKET;
	static	packet				pkt, q_pkt;
	static	WORD		 		len;

	switch(RoomClientState)
	{
		case DS_LOOP:
			if(UDPIsGetReady(RcvSocket))
			{
				len = UDPGetArray((BYTE*)&pkt, 56);
				if (len == 56)
				{
					xQueueSendToFront (xDstarRcvQueue, &pkt, 0);
				}
				else if (len == 27)
				{
					xQueueSendToBack (xDstarRcvQueue, &pkt, 0);
				}
			}
				/* From Rig */
			while (xQueueReceive(xDstarSndQueue, &q_pkt, 0) == pdTRUE)
			{
				if(UDPIsPutReady(SendSocket))
				{
					if (q_pkt.type_id == 0x20)
					{
						UDPPutArray((BYTE*) &q_pkt, 27);	
					}
					else if (q_pkt.type_id == 0x10)
					{
						UDPPutArray((BYTE*) &q_pkt, 56);	
					}
					UDPFlush();	
				}
			}
			break;

		case DS_HOME:
			if(SendSocket == INVALID_UDP_SOCKET)
				SendSocket = UDPOpenEx((DWORD)AppConfig.ServerName,UDP_OPEN_ROM_HOST,0,AppConfig.DefaultOutPort);

			if(RcvSocket == INVALID_UDP_SOCKET)
				RcvSocket = UDPOpenEx((DWORD)AppConfig.ServerName,UDP_OPEN_ROM_HOST,AppConfig.DefaultInPort,0);
			
			RoomClientState = DS_UDP_IS_OPENED;
			break;
			
		case DS_UDP_IS_OPENED:
			if(UDPIsOpened(SendSocket) == TRUE)
			{
				RoomClientState = DS_UDP_SEND;
			}
			break;

		case DS_UDP_SEND:
			// Make certain the socket can be written to
			if(!UDPIsPutReady(SendSocket))
			{
				UDPClose(SendSocket);
				RoomClientState = DS_HOME;
				SendSocket = INVALID_UDP_SOCKET;
				break;
			}

			dwServerIP = UDPSocketInfo[SendSocket].remote.remoteNode.IPAddr.Val;
		    // 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[0] = PicVer[0];
			RoomEntry_PACKET.PicVer[1] = PicVer[1];

			UDPPutArray((BYTE*) &RoomEntry_PACKET, sizeof(RoomEntry_PACKET));	
			UDPFlush();	
			
			dwTimer = TickGet();
			RoomClientState = DS_UDP_RECV;		
			break;

		case DS_UDP_RECV:
			if(UDPIsOpened(RcvSocket) != TRUE) break;
			// Look for a response time packet
			if(!UDPIsGetReady(RcvSocket)) 
			{
				if((TickGet()) - dwTimer > SERVER_REPLY_TIMEOUT)
				{
					// Abort the request and wait until the next timeout period
					UDPClose(SendSocket);
					UDPClose(RcvSocket);
					RoomClientState = DS_HOME;
					SendSocket = INVALID_UDP_SOCKET;
					RcvSocket = INVALID_UDP_SOCKET;
					memcpy (LCDText, "Server Down!    ", 16);
					DisplayIPValue(dwServerIP, LCDText);
					xMessage.pcMessage = LCDText;
			 		xMessage.xMinDisplayTime = 2000 / portTICK_RATE_MS;
					xQueueSend( xLCDQueue, &xMessage, 0 );
				}
				break;
			}
			
			// Get the response from server
			len = UDPGetArray((BYTE*)&RoomEntry_PACKET, 16);
			dwTimer = TickGetDiv64K();
			RoomClientState = DS_WAIT;
			
			// Validate packet size
			if(len == 16u) 
			{
				if (!strncmp (RoomEntry_PACKET.req_id, "SA", 2))
				{
					RoomClientState = DS_LOOP;
					memcpy (LCDText, "Connected to    ", 16);
					DisplayIPValue(dwServerIP, LCDText);
					xMessage.pcMessage = LCDText;
		 			xMessage.xMinDisplayTime = 1000 / portTICK_RATE_MS;
					xQueueSend( xLCDQueue, &xMessage, 0 );
				}
			}
			
			break;

		case DS_SHORT_WAIT:
			// Attempt to requery the NTP server after a specified NTP_FAST_QUERY_INTERVAL time (ex: 8 seconds) has elapsed.
			if(TickGetDiv64K() - dwTimer > (SERVER_FAST_QUERY_INTERVAL/65536ull))
			{
				RoomClientState = DS_HOME;
				SendSocket = INVALID_UDP_SOCKET;
			}
			break;

		case DS_WAIT:
			// Requery the NTP server after a specified NTP_QUERY_INTERVAL time (ex: 10 minutes) has elapsed.
			if(TickGetDiv64K() - dwTimer > (SERVER_QUERY_INTERVAL/65536ull))
			{
				RoomClientState = DS_HOME;
				SendSocket = INVALID_UDP_SOCKET;
			}
			break;
	}
}

