// (c) 2000 by Zachary Booth Simpson
// This code may be freely used, modified, and distributed without any license
// as long as the original author is noted in the source file and all
// changes are made clear disclaimer using a "MODIFIED VERSION" disclaimer.
// There is NO warranty and the original author may not be held liable for any damages.
// http://www.totempole.net
#include "zocket.h"
#include "msgzocket.h"
#include "hashtable.h"
#include "stdio.h"
#include "string.h"
#include "malloc.h"
#ifdef WIN32
	#include "conio.h"
#else
	#include "sys/time.h"
	#include "sys/types.h"
	#include "termios.h"
	#include "unistd.h"
#endif

// Portable Simple Conio
//======================================================

int isKbHit() {
	#ifdef WIN32
		return kbhit();
	#else
		struct timeval nullTime = {0,0};

		fd_set readSet;
		FD_ZERO( &readSet );
		FD_SET( STDIN_FILENO, &readSet );

		int _select = select( STDIN_FILENO+1, &readSet, NULL, NULL, &nullTime );
		if( _select<0 ) {
			return 0;
		}
		return FD_ISSET(STDIN_FILENO, &readSet);
	#endif
}

int getChar() {
	#ifdef WIN32
		return getch();
	#else
		return getchar();
	#endif
}

void setLineBufferMode( int state ) {
	#ifndef WIN32
		// UNIX must be put into non-canonical mode in order
		// to simulate the simplicity of getch and kbhit under DOS
		struct termios oldts, newts;
		if( tcgetattr(STDIN_FILENO, &oldts) >=0 ) {
			newts = oldts;
			if( state ) {
				newts.c_lflag |= ICANON;
			}
			else {
				newts.c_lflag &= ~ICANON;
				newts.c_cc[VMIN ] = 1;
				newts.c_cc[VTIME] = 0;
			}
			tcsetattr(STDIN_FILENO, TCSAFLUSH, &newts);
		}
	#endif
}

// Test
//======================================================

/*
void serverUDP() {
	Zocket listening( "udp://*:5050" );
	while( 1 ) {
		char buffer[50] = {0,};
		ZAddress from;
		int _read = listening.dgramRead( buffer, 50, from, 1 );
		if( _read > 0 ) {
			printf( "Server read: %s\n", buffer );
		}
		else if( _read < 0 ) {
			printf( "Server error read\n" );
		}
	}
}

void serverTCP() {
	Zocket *serverSide = NULL;
	while( 1 ) {
		Zocket listening( "udp(l)://*:5050" );
		int _accept = listening.accept();
		if( _accept > 0 ) {
			serverSide = new Zocket( &listening );
		}
		else if( _accept < 0 ) {
			printf( "Server accept error\n" );
			break;
		}

		while( 1 ) {
			char buffer[50] = {0,};
			int _read = serverSide->read( buffer, 50, 1 );
			if( _read > 0 ) {
				printf( "Server read: %s\n", buffer );
			}
			else if( _read < 0 ) {
				printf( "Server error read\n" );
				delete serverSide;
				serverSide = NULL;
				break;
			}
		}
	}
}
*/


// State Information
//======================================================

// Adjustable settings
//-----------------------------

char displayMode[40] = {0,};
char displayStatus[40] = {0,};
char displayAddress[40] = {0,};
char displayLastFoundServer[40] = {0,};
char displayClientName[40] = {0,};

// Client Lists, used by server
//-----------------------------

struct Client {
	char name[40];
	SimpleMsgZocket *zocket;
	int loggedIn() { return zocket != NULL; }
} clients[10];

int numClients = 1;
	// As clients join, this is incremented.
	// Their slot remains reserved for them
	// until they return or the server shutsdown.
	// Starts a 1 so that 0 will be reserved

IntTable clientTable;
	// This is a hash table by name which
	// tells us which client number a given
	// name is.

int server = 0;
	// Am I a server

// Broadcast zocket, used by both client and server
//-----------------------------

SimpleMsgZocket *broadcastZocket = NULL;

// Server Zocket, used by client
//-----------------------------

SimpleMsgZocket *serverZocket = NULL;

int client = 0;
	// Am I a client

int clientIsLoggedIn = 0;
	// client is logged in

// Messages
//======================================================

enum Msgs {
	msgLogin = 1,
	msgLoginConfirm,
	msgEcho,
	msgServerQuery,
	msgServerRespond,
};

struct MsgLogin : SimpleMsg {
	char name[30];
	char password[30];
	MsgLogin() { type = msgLogin; len = sizeof(*this); }
};

struct MsgEcho : SimpleMsg {
	int fromServer;
	char string[80];
	MsgEcho() { type = msgEcho; len = sizeof(*this); fromServer = 0; }
};

// Message handlers
//======================================================

void handleTerminate( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	printf( "zocket %p terminated.\n", zocket );
	if( zocket->appData ) {
		// I use the app data to store a pointer to
		// my global zocket handles.  This makes the
		// code so much easier to deal with, without
		// having to do a level of indirection.
		*((SimpleMsgZocket **)zocket->appData) = NULL;
	}
	zocket->kill();
	if( client ) {
		strcpy( displayStatus, "unconnected." );
	}
}

void handleAcceptedConnection( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	if( server ) {
		printf( "connection established: %p\n", zocket );
		zocket->setActivityTimeout( 3 * 60 );
		zocket->setLoginTimeout( 15 );
	}
}

void handleConnected( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	if( client ) {
		printf( "connected to server: %p\n", zocket );
		strcpy( displayStatus, "connected." );
	}
}

void handleLoginConfirm( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	if( client ) {
		printf( "login confirmed.\n" );
		clientIsLoggedIn = 1;
		strcpy( displayStatus, "logged in." );
	}
}

void handleLogin( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	// A login associates a name with a zocket so
	// that the server can send to people by name
	// if it chooses to.
	int sendConfirm = 0;
	MsgLogin *m = (MsgLogin *)msg;
	if( server ) {
		int c = clientTable.getInt( m->name );
		if( c && clients[c].loggedIn() ) {
			printf( "player %s already logged in\n", m->name );
		}
		else if( c ) {
			// client has logged in before and is now returning
			clients[c].zocket = zocket;
			printf( "player %s (#%d) returned.\n", m->name, c );
			sendConfirm++;
		}
		else {
			// brand new client
			strcpy( clients[numClients].name, m->name );
			clients[numClients].zocket = zocket;
			clientTable.set( m->name, numClients );
			zocket->appData = (int)&clients[numClients].zocket;
				// Set the app data to point to the
				// cached handle.  It will be reset by
				// handleTerminate
			printf( "new player %s logged in, client #:%d\n", m->name, numClients );
			numClients++;
			sendConfirm++;
		}

		if( sendConfirm ) {
			SimpleMsg msg;
			msg.type = msgLoginConfirm;
			msg.len = sizeof(msg);
			zocket->write( &msg );
			zocket->setLoginTimeout( 0 );
		}
	}
}

void handleEcho( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	MsgEcho *m = (MsgEcho *)msg;
	if( server && !m->fromServer ) {
		m->fromServer = 1;
			// Prevents recursion on client/server
		printf( "Received msg: \"%s\".  Echoing...\n", m->string );
		for( int i=1; i<10; i++ ) {
			if( clients[i].loggedIn() ) {
				clients[i].zocket->write( m );
			}
		}
	}
	else {
		printf( "Received echo from server: %s\n", m->string );
	}
}

void handleServerQuery( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	if( server ) {
		SimpleMsg _msg;
		_msg.type = msgServerRespond;
		_msg.len = sizeof(_msg);
		zocket->write( &_msg );
	}
}

void handleServerRespond( SimpleMsgZocket *zocket, SimpleMsg *msg ) {
	if( client ) {
		strcpy( displayLastFoundServer, zocket->remoteAddress.getString(1,0,1) );
	}
}

// State
//======================================================

SimpleMsgZocket *listeningZocket = NULL;
	// This is the listening zocket used
	// only by the server.

void startupServer() {
	listeningZocket = new SimpleMsgZocket( "tcp(lb)://*:5050" );
	listeningZocket->appData = (int)&listeningZocket;
	server = 1;
}

void shutdownServer() {
	if( server ) {
		if( listeningZocket ) {
			listeningZocket->kill();
			listeningZocket = NULL;
		}
		for( int i=1; i<10; i++ ) {
			if( clients[i].loggedIn() ) {
				clients[i].zocket->kill();
			}
		}
		memset( clients, 0, sizeof(Client) * 10 );
		server = 0;
	}
}

void startupClient() {
	client = 1;
	clientIsLoggedIn = 0;
}

void shutdownClient() {
	if( client ) {
		if( serverZocket ) {
			serverZocket->kill();
		}
		clientIsLoggedIn = 0;
		client = 0;
	}
}

void toggleMode() {
	// This function cycles through the three modes.
	// Because this is the only function that changes
	// the mode, we can make assumptions about what
	// needs to be shutdown and startedup.

	if( !client && !server ) {
		// initial mode is client only
		startupClient();
		strcpy( displayMode, "client-only" );
		strcpy( displayStatus, "unconnected" );
	}
	else if( client && server ) {
		// Switch from dual to client only
		shutdownServer();
		shutdownClient();
		startupClient();
		strcpy( displayMode, "client-only" );
		strcpy( displayStatus, "unconnected" );
	}
	else if( client && !server ) {
		// Switch from client only to server only
		shutdownClient();
		startupServer();
		strcpy( displayMode, "server-only" );
		strcpy( displayStatus, "n/a" );
	}
	else if( server && !client ) {
		// Switch from server only to dual only
		startupClient();
		strcpy( displayMode, "client/server" );
		strcpy( displayStatus, "unconnected" );
	}
}


// User interface
//======================================================

void printStatus() {
	printf(
		"\nPress '?' to refresh this status.\n" 
		"Mode('m'): %-15s  Status: %-15s\n"
		"Address('a'): %s\n"
		"Located Server('f' to use this): %s\n",
		displayMode,
		displayStatus,
		displayAddress,
		displayLastFoundServer
	);
	if( server ) {
		printf( "Currently logged in: " );
		for( int i=0; i<10; i++ ) {
			if( clients[i].zocket ) {
				printf( "%d.%s ", i, clients[i].name );
			}
		}
		printf( "\n" );
	}
	if( client ) {
		printf( "Client name('n'): %-20s\n", displayClientName );
	}

	printf(
		"Other Commands: 'c'=connect to address. 'l'=send login. 'e'=send echo msg.\n"
		"                'd'=disconnect from server. 'b'=broadcast server query.\n"
	);
}

int checkUI() {
	if( isKbHit() ) {
		int c = getChar();
		switch( c ) {
			case 'q':
				return 0;
			case '?':
				printStatus();
				break;
			case 'm':
				toggleMode();
				printStatus();
				break;
			case 'a':
				printf( "\nEnter New Address: " );
				setLineBufferMode( 1 );
				scanf( "%s", displayAddress );
				setLineBufferMode( 0 );
				printStatus();
				break;
			case 'f':
				strcpy( displayAddress, displayLastFoundServer );
				printStatus();
				break;
			case 'n':
				printf( "\nName to login as: " );
				setLineBufferMode( 1 );
				scanf( "%s", displayClientName );
				setLineBufferMode( 0 );
				printStatus();
				break;
			case 'c':
				if( client && !serverZocket ) {
					serverZocket = new SimpleMsgZocket( displayAddress, NULL, "b" );
						// I explicitly force non-blocking here in
						// case it isn't specified in the address
						// (which can be user-editted)
					serverZocket->appData = (int)&serverZocket;
				}
				break;
			case 'l':
				if( serverZocket ) {
					MsgLogin login;
					strcpy( login.name, displayClientName );
					strcpy( displayStatus, "awaiting login." );
					serverZocket->write( &login );
				}
				break;
			case 'e':
				if( serverZocket ) {
					MsgEcho msgEcho;
					strcpy( msgEcho.string, "This is a message" );
					serverZocket->write( &msgEcho );
				}
				break;
			case 'd':
				if( client && serverZocket ) {
					serverZocket->requestTermination();
				}
				break;
			case 'b': {
				if( client && broadcastZocket ) {
					SimpleMsg msg;
					msg.type = msgServerQuery;
					msg.len = sizeof(msg);
					//ZAddress zz( "udp://255.255.255.255:5050" );
					//broadcastZocket->dgramWrite( &msg, zz );
					broadcastZocket->write( &msg );
				}
				break;
			}
		}
	}

	return 1;
}

// Main Loop
//======================================================

void main( int argc, char **argv ) {
	strcpy( displayAddress, "tcp://127.0.0.1:5050" );
	strcpy( displayClientName, "Zack" );

	HashTable opt;
	for( int i=1; i<argc; i++ ) {
		opt.set( argv[i], "1" );
	}

	dTimeInit();

	SimpleMsgZocket::registerHandler( -1, handleTerminate );
	SimpleMsgZocket::registerHandler( -2, handleAcceptedConnection );
	SimpleMsgZocket::registerHandler( -3, handleConnected );
	SimpleMsgZocket::registerHandler( msgLogin, handleLogin );
	SimpleMsgZocket::registerHandler( msgLoginConfirm, handleLoginConfirm );
	SimpleMsgZocket::registerHandler( msgEcho, handleEcho );
	SimpleMsgZocket::registerHandler( msgServerQuery, handleServerQuery );
	SimpleMsgZocket::registerHandler( msgServerRespond, handleServerRespond );

	broadcastZocket = new SimpleMsgZocket( "udp(b)://255.255.255.255:5050" );
	broadcastZocket->appData = (int)&broadcastZocket;

	toggleMode();

	printStatus();

	LocalTimer timer( 0.5 );

	while( 1 ) {
		dTimeLatchFrame();

		if( !checkUI() ) {
			break;
		}

		SimpleMsgZocket::pollAll();

		if( timer.isDone() ) {
			timer.start( 0.5 );
			printf( "." );
		}
	}
}
