//       _________ __                 __
//      /   _____//  |_____________ _/  |______     ____  __ __  ______
//      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
//      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
//     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
//             \/                  \/          \//_____/            \/
//  ______________________                           ______________________
//                        T H E   W A R   B E G I N S
//         Stratagus - A free fantasy real time strategy game engine
//
/**@name netdriver.cpp - Session mangement (SDL_net Socket Implementation). */
//
//      (c) Copyright 2005 by Edward Haase and Jimmy Salmon
//
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; only version 2 of the License.
//
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
//      02111-1307, USA.
//

//@{

/*----------------------------------------------------------------------------
--  Includes
----------------------------------------------------------------------------*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#ifndef _MSC_VER
#include <errno.h>
#endif

#include "stratagus.h"
#include "netdriver.h"
#include "net_lowlevel.h"

/*----------------------------------------------------------------------------
--  Defines
----------------------------------------------------------------------------*/

/**
**  LINK
**
**  Adds an item to a linked list.
*/
#define LINK(first, item, last, count) { \
	if (!first)                     \
		first = item;               \
	if (!last) {                    \
		last = item;                \
	} else {                        \
		item->Next = last->Next;    \
		last->Next = item;          \
		item->Prev = last;          \
		last = item;                \
		if (!item->Prev->Next)      \
			item->Prev->Next = item;\
	}                               \
	++count;                        \
}

/**
**  UNLINK
**
**  Removes an item from the linked list.
*/
#define UNLINK(first, item, last, count) { \
	if (item->Prev)                   \
		item->Prev->Next = item->Next;\
	if (item->Next)                   \
		item->Next->Prev = item->Prev;\
	if (item == last)                 \
		last = item->Prev;            \
	if (item == first)                \
		first = item->Next;           \
	--count;                          \
}

/*----------------------------------------------------------------------------
--  Variables
----------------------------------------------------------------------------*/

static Socket MasterSocket;

SessionPool *Pool;
ServerStruct Server;

/*----------------------------------------------------------------------------
--  Functions
----------------------------------------------------------------------------*/

/**
**  Send a message to a session
**
**  @param session  Session to send the message to
**  @param msg      Message to send
*/
void Send(Session *session, const char *msg)
{
	NetSendTCP(session->Sock, msg, strlen(msg));
}

/**
**  Initialize the server
**
**  @param port  Defines the port to which the server will bind.
**
**  @return      0 for success, non-zero for failure
*/
int ServerInit(int port)
{
	Pool = NULL;

	if (NetInit() == -1) {
		return -1;
	}

	if ((MasterSocket = NetOpenTCP(NULL, port)) == (Socket)-1) {
   		fprintf(stderr, "NetOpenTCP failed\n");
   		return -2;
	}

	if (NetSetNonBlocking(MasterSocket) == -1) {
		fprintf(stderr, "NetSetNonBlocking failed\n");
		NetCloseTCP(MasterSocket);
		NetExit();
		return -3;
	}

	if (NetListenTCP(MasterSocket) == -1) {
   		fprintf(stderr, "NetListenTCP failed\n");
		NetCloseTCP(MasterSocket);
		NetExit();
   		return -4;
	}

	if (!(Pool = new SessionPool)) {
		fprintf(stderr, "Out of memory\n");
		NetCloseTCP(MasterSocket);
		NetExit();
		return -5;
	}

	if (!(Pool->Sockets = new SocketSet)) {
		NetCloseTCP(MasterSocket);
		NetExit();
		return -6;
	}

	Pool->First = NULL;
	Pool->Last = NULL;
	Pool->Count = 0;

	return 0;
}

/**
**  ServerQuit: Releases the server socket.
*/
void ServerQuit(void)
{
	NetCloseTCP(MasterSocket);
	// begin clean up of any remaining sockets
	if (Pool) {
		Session *ptr;

		while (Pool->First) {
			ptr = Pool->First;
			UNLINK(Pool->First, Pool->First, Pool->Last, Pool->Count);
			NetCloseTCP(ptr->Sock);
			delete ptr;
		}

		delete Pool->Sockets;
		delete Pool;
	}

	NetExit();
}

/**
**  Returns time (in seconds) that a session has been idle.
**
**  @param session  This is the session we are checking.
*/
static int IdleSeconds(Session *session)
{
	return (int)(time(0) - session->Idle);
}

/**
**  Destroys and cleans up session data.
**
**  @param session  Reference to the session to be killed.
*/
static int KillSession(Session *session)
{
	DebugPrint("Closing connection from '%s'\n" _C_ session->AddrData.IPStr);
	NetCloseTCP(session->Sock);
	Pool->Sockets->DelSocket(session->Sock);
	UNLINK(Pool->First, session, Pool->Last, Pool->Count);
	delete session;
	return 0;
}

/**
**  Accept new connections
*/
static void AcceptConnections()
{
	Session *new_session;
	Socket new_socket;
	unsigned long host;
	int port;
	while ((new_socket = NetAcceptTCP(MasterSocket, &host, &port)) != (Socket)-1) {
		// Check if we're at MaxConnections
		if (Pool->Count == Server.MaxConnections) {
			NetSendTCP(new_socket, "Server Full\n", 12);
			NetCloseTCP(new_socket);
			break;
		}

		new_session = new Session;
		if (!new_session) {
			fprintf(stderr, "ERROR: %s\n", strerror(errno));
			break;
		}

		new_session->Sock = new_socket;
		new_session->Idle = time(0);

		new_session->AddrData.Host = host;
		sprintf(new_session->AddrData.IPStr, "%d.%d.%d.%d", NIPQUAD(ntohl(host)));
		new_session->AddrData.Port = port;
		DebugPrint("New connection from '%s'\n" _C_ new_session->AddrData.IPStr);

		LINK(Pool->First, new_session, Pool->Last, Pool->Count);
		Pool->Sockets->AddSocket(new_socket);
	}
}

/**
**  Kick idlers
*/
static void KickIdlers(void)
{
	Session *session;
	Session *next;

	for (session = Pool->First; session; ) {
		next = session->Next;
		if (IdleSeconds(session) > Server.IdleTimeout) {
			DebugPrint("Kicking idler '%s'\n" _C_ session->AddrData.IPStr);
			KillSession(session);
		}
		session = next;
	}
}

/**
**  Read data
*/
static int ReadData()
{
	int result = Pool->Sockets->Select(0);

	if (result == 0) {
		// No sockets ready
		return 0;
	}
	if (result == -1) {
		// FIXME: print error message
		return -1;
	}

	// ready sockets
	for (Session *session = Pool->First; session; ) {
		Session *next = session->Next;
		if (Pool->Sockets->HasDataToRead(session->Sock)) {
			// socket ready
			session->Idle = time(0);
			int clen = strlen(session->Buffer);
			result = NetRecvTCP(session->Sock, session->Buffer + clen,
				sizeof(session->Buffer) - clen);
			if (result < 0) {
				KillSession(session);
			} else {
				session->Buffer[clen + result] = '\0';
			}
		}
		session = next;
	}

	return 0;
}

/**
**  Accepts new connections, receives data, manages buffers,
*/
int UpdateSessions(void)
{
	AcceptConnections();

	if (!Pool->First) {
		// No connections
		return 0;
	}

	KickIdlers();

	return ReadData();
}

//@}
