/**
* A trivial C sockets example
* Uses C99 dialect, so compile with -std=gnu99 or -std=c99
* or via IDE-s Visual Studio 2013 (and higher) / DevC++5.7 (and higher)
*
* Visual C/C++:   Create an empty project, compile and run.
*
* DevC++ (MinGW): Project Options... -> Parameters -> Linker
*                                    -> Add Library or Object:  libws2_32.a
*
* GCC (linux):    gcc -std=gnu99 -pthread sockets.c -o sockets
*/
#include <stdlib.h>    // malloc
#include <stdio.h>     // printf
#include <string.h>    // strcmp
#include <time.h>


/////////////////////////////////////////////////////////////////////////////
/**
* Cross-platform compatibility definitions and helper functions
*
* @note Skip this part -- it's just wrapper code for this demo to
*       run in both Windows and Linux
*/
#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
	#include <Windows.h>
	#include <ws2tcpip.h>               // winsock2 and TCP/IP functions
	#pragma comment (lib, "Ws2_32.lib") // link against winsock libraries
	#define sleep(milliSeconds) Sleep((milliSeconds))
	#if _MSC_VER < 1900 // snprintf only available in VS2014
		#define snprintf(buffer, maxCount, fmt, ...) _snprintf_s(buffer, maxCount, _TRUNCATE, fmt, __VA_ARGS__)
	#endif
#else // UNIX
	#include <unistd.h>             // close()
	#include <pthread.h>            // POSIX threads
	#include <sys/types.h>          // required type definitions
	#include <sys/socket.h>         // LINUX sockets
	#include <netinet/in.h>         // sockaddr_in
	#include <netinet/tcp.h>        // TCP_NODELAY
	#include <errno.h>              // last error number
	#include <sys/ioctl.h>          // ioctl()
	#include <sys/fcntl.h>          // fcntl()
	#include <arpa/inet.h>	        // inet_addr, inet_ntoa
	#define sleep(milliSeconds) usleep((milliSeconds) * 1000)
	// map linux socket calls to winsock calls via macros
	#define closesocket(fd) close(fd)
	#define ioctlsocket(fd, request, arg) ioctl(fd, request, arg)
#endif
#ifndef _countof
	#define _countof(a) (sizeof(a)/sizeof(*(a)))
#endif

// spawns a new thread, no result handle is returned
void spawn_thread(void(*thread_func)(void* arg), void* arg)
{
	#if _WIN32
		CreateThread(0, 4096, (LPTHREAD_START_ROUTINE) thread_func, arg, 0, 0);
	#else // Linux
		pthread_t threadHandle;
		pthread_create(&threadHandle, NULL, (void*(*)(void*))thread_func, arg);
	#endif
}

// measure highest accuracy time in seconds for both Windows and Linux
double timer_Time()
{
	#if _WIN32
		static double timer_freq = 0.0;
		LARGE_INTEGER t;
		if (timer_freq == 0.0) // initialize high perf timer frequency
		{
			QueryPerformanceFrequency(&t);
			timer_freq = (double) t.QuadPart;
		}
		QueryPerformanceCounter(&t);
		return (double) t.QuadPart / timer_freq;
	#else
		struct timespec tm;
		clock_gettime(CLOCK_REALTIME, &tm);
		return tm.tv_sec + (double) tm.tv_nsec / 1000000000.0;
	#endif
}

////////////////////////////////////////////////////////////////////





///////////////////// Tiny portable SOCKETS API ////////////////////

/**
 * Send data to remote socket, return number of bytes sent or -1 if socket closed
 * Automatically closes socket during critical failure
 */
int Socket_Send(int socket, const void* buffer, int numBytes)
{
	int ret = send(socket, (const char*)buffer, numBytes, 0);
	if (ret == -1) // socket error?
		closesocket(socket); // just close the socket
	return ret;
}

/**
 * Recv data from remote socket, return number of bytes received or -1 if socket closed
 * Automatically closes socket during critical failure
 */
int Socket_Recv(int socket, void* buffer, int maxBytes)
{
	int ret = recv(socket, (char*) buffer, maxBytes, 0);
	if (ret == -1) // socket error?
	{
	#if _WIN32
		switch (WSAGetLastError()) {
			case WSAEWOULDBLOCK: return 0; // no data available right now
			case WSAECONNRESET:  closesocket(socket); return -1; // connection reset...
		}
	#else // linux
		switch (errno) {
			case EWOULDBLOCK: return 0; // no data available right now
			case ENETRESET:
			case ECONNABORTED:
			case ECONNRESET: closesocket(socket); return -1; // connection reset...
		}
	#endif
	}
	return ret;
}

/**
 * Configure socket settings: non-blocking I/O, nagle disabled (TCP_NODELAY=TRUE)
 */
void Socket_NoBlockNoDelay(int socket) 
{
	u_long opt_true = 1; // TRUE
	setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char*) &opt_true, sizeof(int));
	ioctlsocket(socket, FIONBIO, &opt_true);
}

/**
 * Creates a new listener type socket used for accepting new connections
 */
int Socket_ListenTo(int port)
{
	// set up local server listener
	struct sockaddr_in addr = { 0 };
	addr.sin_family = AF_INET;
	addr.sin_port   = htons(port);            // host to network short
	addr.sin_addr.s_addr = htonl(INADDR_ANY); // host to network long

	int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // create TCP stream socket
	Socket_NoBlockNoDelay(server);
	bind(server, (struct sockaddr*) &addr, sizeof(addr));   // setup as listening socket
	listen(server, SOMAXCONN);                              // start listening for new clients
	return server;
}

/**
 * Try accepting a new connection from a listening socket. Accepted socket set to noblock nodelay
 */
int Socket_Accept(int listener)
{
	// assume the listener socket is already non-blocking
	int client = accept(listener, NULL, NULL);
	if (client != -1) // do we have a client?
	{
		// set the client socket as non-blocking, since socket options are not inherited
		Socket_NoBlockNoDelay(client);
		return client;
	}
	return client;
}

/**
 * Connects to a remote socket and sets the socket as nonblocking and tcp nodelay
 */
int Socket_ConnectTo(const char* hostname, int port)
{
	struct sockaddr_in addr = { 0 };
	addr.sin_family = AF_INET;
	addr.sin_port   = htons(port); // host to network short
	#if _MSC_VER || __linux // MSVC++ or linux gcc
		inet_pton(AF_INET, hostname, &addr.sin_addr); // text to network address lookup
	#else // __MINGW32__
		addr.sin_addr.s_addr = inet_addr(hostname);
	#endif

	int remote = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	int error  = connect(remote, (struct sockaddr*)&addr, sizeof addr);
	if (error) { // did connect fail?
		printf("Socket_ConnectTo(\"%s\", %d) failed.\n", hostname, port);
		closesocket(remote);
		return -1;
	}
	Socket_NoBlockNoDelay(remote);
	return remote;
}

/////////////////////////////////////////////////////////////////////////////





//////////////////////// BOT CLIENT ///////////////////////////

// a client bot that joins our server via TCP socket
void Client_Bot_Thread(void* botName)
{
	char name[32];
	strncpy(name, botName, sizeof name); // copy the name of the bot

	// Now try to connect to the server on localhost:1337
	int remote = Socket_ConnectTo("127.0.0.1", 1337);
	
	char buffer[4096];
	int len = snprintf(buffer, sizeof buffer, "%s says: Hello!", name);
	if (Socket_Send(remote, buffer, len) < 0)
		return; // something failed. quit

	double start = timer_Time();        // mark the starting time
	srand((unsigned) ((start - (signed long long)start) * 1000000000.0)); // seed with nanosecond timer result
	int waitTime = 500 + rand() % 500;  // time to wait before we send random stuff to Server

	for (;;)
	{
		int elapsedMillis = (int)((timer_Time() - start) * 1000.0);
		if (elapsedMillis > waitTime) // waitTime is over -- spam the server with a random number
		{
			len = snprintf(buffer, sizeof buffer, "%s says: Generated %d", name, rand());
			if (Socket_Send(remote, buffer, len) < 0)
				return; // something failed. quit
			start    = timer_Time();          // reset timer and waitTime
			waitTime = 748 + rand() % 748;
		}

		// meanwhile check if the server sends us anything:
		int received;
		while (received = Socket_Recv(remote, buffer, sizeof buffer))
		{
			if (received < 0)
				return; // remote socket closed, or other error
			printf("Server echoes: %.*s\n", received, buffer); // echo raw text
		}

		sleep(50); // throttle down thread for a few milliseconds until some data is received
	}
}

///////////////////////////////////////////////////////////////







////////////////// SERVER IMPLEMENTATION //////////////////////

int main(int argc, char** argv)
{
	#if _WIN32 // windows sockets require explicit initialization
		WSADATA wsaData;
		WSAStartup(MAKEWORD(2, 2), &wsaData); // target Winsock 2.2
	#endif

	// start listening for new client connections on port 1337
	int server = Socket_ListenTo(1337);

	// spawn virtual clients
	spawn_thread(&Client_Bot_Thread, "spaceman");
	spawn_thread(&Client_Bot_Thread, "mrwalker");

	char buffer[4096];
	int clients[80];
	int num_clients = 0;
	for (;;)
	{
		// accept all pending clients:
		int client;
		while ((client = Socket_Accept(server)) != -1)
			clients[num_clients++] = client; // add client

		// see if we got input from clients:
		for (int i = 0; i < num_clients; ++i)
		{
			int received; // loop while we have received data:
			while (received = Socket_Recv(clients[i], buffer, sizeof buffer))
			{
				if (received < 0) { // error. remote connection closed?
					// remove this client from clients array:
					memmove(&clients[i], &clients[i+1], --num_clients * sizeof(int));
					--i; // user was unshifted, fix index for next loop
					break;
				}
				printf("%.*s\n", received, buffer);

				// echo what we received back to the client:
				Socket_Send(clients[i], buffer, received);
			}
		}

		sleep(1); // throttle the thread because we're using non-blocking IO
	}

	closesocket(server);
	#if _WIN32 // and also explicit cleanup if done using sockets
		WSACleanup();
	#endif
	return 0;
}