#include "TCPIPConfig.h"

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

#include <stdlib.h>
#include "GenericTypeDefs.h"
#include "HardwareProfile.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	(14ul * TICK_SECOND)

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

#define CLIENT_DOWN_CHECK_INTERVAL	(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.

void	DisplayIPValue(DWORD dwServerIP, char LCDText[]);
void	vTaskDelay( portTickType xTicksToDelay );

void	ConnectTableCheck(void);
void	send_header_packet (DWORD ip_addr);
void	send_dv_packet (DWORD ip_addr);
void	add_connected_table (DWORD ip_addr);
void	update_connected_table (DWORD ip_addr);
void	delete_from_table (DWORD ip_addr);
void	alive_in_table (DWORD ip_addr);
void    delete_all_from_table (void);
void    reply_server_status(DWORD ip_addr);


struct	node_packet table;
struct	connected_table *first_pnt, *last_pnt;
unsigned char	NodeCallSave[8];
unsigned char	YourCallSave[8];
DWORD	from_ip;
DWORD	cur_ip;
int		sendSW;
int		out_port_save;

static	DWORD		Timer;
static	UDP_SOCKET	SendSocket = INVALID_UDP_SOCKET;
static	UDP_SOCKET	RcvSocket = INVALID_UDP_SOCKET;
static	UDP_SOCKET	LogSocket = INVALID_UDP_SOCKET;

static	char	lastframe[6] ={0x55,0x55,0x55,0x55,0xc8,0x7a};
static	char	LastFrameCheck[6];

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

#define	VerH	0xF0
#define	VerL	0x20

enum
	{
		DS_HOME = 0,
		DS_UDP_IS_OPENED,
		DS_LOOP
	} RoomServerState = DS_HOME;

void DstarRoomServerUDP(void)
{
	static	WORD		 		len;
	static	WORD				l;

	switch(RoomServerState)
	{
		case DS_LOOP:
			while (UDPIsGetReady(RcvSocket))
			{
				len = UDPGetArray((BYTE*)&table, 56);
				from_ip = UDPSocketInfo[RcvSocket].remote.remoteNode.IPAddr.Val;
 
				if ((len == 44) || (len == 40))
				{
					if (!strncmp(table.node_table.req_id, "RS", 2) || 
						 !strncmp(table.node_table.req_id, "SI", 2))
					{
						if (len == 44) out_port_save = table.node_table.port;
						else  	     out_port_save = ROOM_SERVER_OUT_PORT;
						update_connected_table (from_ip);
						if (!strncmp(table.node_table.req_id, "SI", 2))
						{
							reply_server_status(from_ip);
//							if (ReplySW) CONNECTsend (from_ip);
						}
					}
				} else if (len == 16) {
					if (!strncmp(table.node_table.req_id, "DD", 2))
					{
						table.node_table.ip_address = 0;
						delete_from_table (from_ip);
					}
					else if (!strncmp(table.node_table.req_id, "AL", 2))
					{
						alive_in_table (from_ip);
					}
	
				} else {
				    if (!sendSW || (cur_ip == from_ip))
			    	{
						if (len == 56)
      					{
							if (table.node_dv_pkt.b_bone.id == header_type)
							{
								if ((TickGet() - Timer) > CLIENT_DOWN_CHECK_INTERVAL)
								{
									ConnectTableCheck();
								}
								cur_ip = from_ip;
								sendSW = TRUE;
								send_header_packet (from_ip);
							}
						} else if (len == 27) {
							if (table.node_dv_pkt.b_bone.id == dv_type)
							{
								cur_ip = from_ip;
								sendSW = TRUE;
		
								for (l = 0; l < 12 ; l++)
								{
									LastFrameCheck[0] = LastFrameCheck[1]; 
									LastFrameCheck[1] = LastFrameCheck[2]; 
									LastFrameCheck[2] = LastFrameCheck[3]; 
									LastFrameCheck[3] = LastFrameCheck[4]; 
									LastFrameCheck[4] = LastFrameCheck[5];
									LastFrameCheck[5] = table.node_dv_pkt.v_data.voice_segment[l];
 									if (!strncmp(LastFrameCheck,lastframe,6))
									{
										sendSW = FALSE;
										cur_ip = 0x00;
									}
								}	
								send_dv_packet (from_ip);
							}
						}
					}
				}
			}
			break;

		case DS_HOME:
			if(LogSocket == INVALID_UDP_SOCKET)
				LogSocket = UDPOpenEx((DWORD)AppConfig.LogServerName,UDP_OPEN_ROM_HOST,0,AppConfig.DefaultLogPort);

			if(RcvSocket == INVALID_UDP_SOCKET)
				RcvSocket = UDPOpenEx((DWORD)AppConfig.MyIPAddr.Val,UDP_OPEN_SERVER,AppConfig.DefaultInPort,0);

			RoomServerState = DS_UDP_IS_OPENED;

			break;
			
		case DS_UDP_IS_OPENED:
			if(UDPIsOpened(RcvSocket) == TRUE)
			{
				RoomServerState = DS_LOOP;
				first_pnt = pvPortMalloc (sizeof (struct connected_table));
				first_pnt->ip_address = 0;
				first_pnt->port = 0;
				first_pnt->f_chain = NULL;
				first_pnt->flag = 0x00;
				first_pnt->SendSocket = INVALID_UDP_SOCKET;
				last_pnt = first_pnt;
				Timer = TickGet();
			} else {
				UDPClose(RcvSocket);
				RcvSocket = INVALID_UDP_SOCKET;
				UDPClose(LogSocket);
				LogSocket = INVALID_UDP_SOCKET;
				RoomServerState = DS_HOME;
			}
			break;
	}
}

/* Connect table update */
void	ConnectTableCheck( void )
{
	struct	connected_table *pnt;

loop:
	pnt = first_pnt->f_chain;
	while (pnt)
	{
		if (pnt->flag == 0xff)
		{
			delete_from_table (pnt->ip_address);
			goto loop;
		}
		pnt = pnt->f_chain;
	}
}

void	send_header_packet (DWORD ip_addr)
{
	struct	connected_table *pnt;
	struct	node_log log;
	int	sw;	

	sw = 0;
	strncpy (YourCallSave,table.node_dv_pkt.rf_header.MyCall,8);
	strncpy (log.acc_log.MyCall,table.node_dv_pkt.rf_header.MyCall, 8);
	strncpy (log.id, "ACC ", 4);


	pnt = first_pnt->f_chain;
	while (pnt)
	{
	   Timer = TickGet();

		if (pnt->ip_address != ip_addr)
		{
			if  (UDPIsPutReady(pnt->SendSocket))
			{
				UDPPutArray((BYTE*)&table, 56);
				UDPFlush();
			}	
			sw = 1;
			pnt->flag = 0xff;
		}
		if (pnt->ip_address == ip_addr)
	   	{
			strncpy (log.acc_log.NodeCall,pnt->callsign, 8);	
			strncpy (log.acc_log.RoomName,pnt->roomname, 8);
			strncpy (NodeCallSave,pnt->callsign, 8);
			strncpy (pnt->LastCallsign, table.node_dv_pkt.rf_header.MyCall, 8);
			pnt->flag = 0x00;
	   	}
	   	pnt = pnt->f_chain;
	}
//	if (ReplySW) AckNaksend (ip_addr, sw);
	if (LogSocket != INVALID_UDP_SOCKET) 
	{
		if  (UDPIsPutReady(LogSocket))
		{
			UDPPutArray((BYTE*)&log, sizeof (struct node_log));
			UDPFlush();
		}	
	}
}

void	send_dv_packet (DWORD ip_addr)
{
	struct	connected_table *pnt;

	pnt = first_pnt->f_chain;
	while (pnt)
	{
		if (pnt->ip_address != ip_addr)
	 	{ 
			if  (UDPIsPutReady(pnt->SendSocket))
			{
				UDPPutArray((BYTE*)&table, 27);
				UDPFlush();
			}
		}
		pnt = pnt->f_chain;
	}
}

void	add_connected_table (DWORD ip_addr)
{
	struct	connected_table *pnt;
	struct	node_log log;	
	static	UDP_SOCKET NewSocket = INVALID_UDP_SOCKET;

	pnt = pvPortMalloc (sizeof (struct connected_table));
	pnt->f_chain = NULL;
	pnt->ip_address = ip_addr;
	pnt->port = out_port_save;
	pnt->flag = 0x00;
	pnt->SendSocket = INVALID_UDP_SOCKET;
	strncpy (pnt->callsign, table.node_table.callsign, 8);
	strncpy (pnt->roomname, table.node_table.zone, 8);
	strncpy (pnt->LastCallsign, "NONE    ", 8);
	last_pnt->f_chain = pnt;
	last_pnt = pnt;

	NewSocket = UDPOpenEx(ip_addr,UDP_OPEN_IP_ADDRESS,0,out_port_save);
	if (NewSocket != INVALID_UDP_SOCKET) pnt->SendSocket = NewSocket;

	strncpy (log.con_log.NodeCall,table.node_table.callsign, 8);	
	strncpy (log.con_log.RoomName,table.node_table.zone, 8);
	log.con_log.ip_address = ip_addr;	
	strncpy (log.id, "ADD ", 4);
	if (LogSocket != INVALID_UDP_SOCKET)
	{
		if  (UDPIsPutReady(LogSocket))
		{
			UDPPutArray((BYTE*)&log, sizeof (struct node_log));
			UDPFlush();
		}
	}
	return;
}

void	update_connected_table (DWORD ip_addr)
{
	struct	connected_table *pnt;
	struct	node_log log;	
	static	UDP_SOCKET NewSocket = INVALID_UDP_SOCKET;
	
	pnt = first_pnt->f_chain;
	while (pnt)
	{
		if (!strncmp (pnt->callsign, table.node_table.callsign, 8))
		{
			if (pnt->ip_address != ip_addr)
			{
				UDPClose(pnt->SendSocket);
				pnt->ip_address = ip_addr;
				pnt->port = out_port_save;
				strncpy (pnt->roomname, table.node_table.zone, 8);
				NewSocket = UDPOpenEx(ip_addr,UDP_OPEN_IP_ADDRESS,0,out_port_save);
				if (NewSocket != INVALID_UDP_SOCKET) pnt->SendSocket = NewSocket;
			}
			return;
		}
		pnt = pnt->f_chain;
	}
	add_connected_table (ip_addr);
	return;
}

void	delete_from_table (DWORD ip_addr)
{
	int	k, i;
	struct	connected_table *pnt, *pnt_wrk;
	struct	node_log log;	

	pnt_wrk = first_pnt;
	pnt = first_pnt->f_chain;
	while (pnt)
	{
		if (pnt->ip_address == ip_addr)
		{
			UDPClose(pnt->SendSocket);
			pnt_wrk->f_chain = pnt->f_chain;
			vPortFree (pnt);
			last_pnt = first_pnt;
			pnt = first_pnt->f_chain;
			while (pnt)
			{
				last_pnt = pnt;
				pnt = pnt->f_chain;
			}
			return;
		}
		pnt_wrk = pnt;
		pnt = pnt->f_chain;
	}
	return;
}

/* client status check */
void	alive_in_table (DWORD ip_addr)
{
	int	k, i;
	struct	connected_table *pnt;

	pnt = first_pnt->f_chain;
	while (pnt)
	{
		if (pnt->ip_address == ip_addr)
		{
			pnt->flag = 0x00;
		}
		pnt = pnt->f_chain;
	}
}


void    delete_all_from_table (void)
{
    int     k, i;
    struct  connected_table  *pnt, *pnt_wrk;
	struct	node_log log;

	pnt = first_pnt->f_chain;
	while (pnt)
	{
			strncpy (log.con_log.NodeCall,pnt->callsign, 8);	
			strncpy (log.con_log.RoomName,pnt->roomname, 8);
			log.con_log.ip_address = pnt->ip_address;	
			strncpy (log.id, "DEL ", 4);
			if (LogSocket != INVALID_UDP_SOCKET)
			{
				if  (UDPIsPutReady(LogSocket))
				{
					UDPPutArray((BYTE*)&log, sizeof (struct node_log));
					UDPFlush();
				}
			}

		pnt_wrk = pnt->f_chain;
		free (pnt);
		pnt = pnt_wrk;
	}
	last_pnt = first_pnt;
	return;
}

/* server status check */
void    reply_server_status(DWORD ip_addr)
{
    struct  connected_table  *pnt;

	pnt = first_pnt->f_chain;
	while (pnt)
	{
		if (pnt->ip_address == ip_addr)
	   	{
			if  (UDPIsPutReady(pnt->SendSocket))
			{
	        	strncpy (table.node_table.req_id, "SA", 2);
				UDPPutArray((BYTE*)&table, 16);
				UDPFlush();
			}
       		return;
		}
		pnt = pnt->f_chain;
	}
}
