//
// 
//	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
// 
//	This program is free software; you can redistribute it 
//	and/or modify it under the terms of the GLOBALBASE 
//	Library General Public License (G-LGPL) as published by 
//
//	http://www.globalbase.org/
// 
//	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.
//
//

extern "C" {

#include <LOpenTptTCPEndpoint.h>
#include <LSimpleThread.h>

#include	"CServerMaster.h"
#include	"task.h"
#include	"net_msg.h"
#include	"memory_debug.h"
#include	"pri_level.h"
#include	"utils.h"




#define MAX_CONNECTION (UEnvironment::IsRunningOSX()?1:10)

//#define SERVER_SELF_TEST

//-----------------------------------------------------------------------------
//   CServerMaster
//-----------------------------------------------------------------------------


struct ServerMasterList {
	CServerMaster *			serverMaster;
	struct ServerMasterList *	next;
};

static ServerMasterList * server_master_list = nil;

CServerMaster::CServerMaster(int * open_port,int base_port,int limit)
{
int p = base_port;
	
	server_master_lock = new_lock(0);

retry:

	_ExStop();
	try {
		mEndpoint = UNetworkFactory::CreateTCPEndpoint();
	} catch(LException exc) {
		SInt32 err = exc.GetErrorCode();
		fprintf(stderr, "***Error -  TCP/IP may not be installed. (ID : %d)\n", err);
		_ExThrow();
	} catch(...) {
		_ExThrow();
	}

	SInt32 err = 1;
	while ( 1 ) {
		try {
			NET_MSG(" ***trying opening port %i\n", p);
			LInternetAddress address(0, p);
			mEndpoint->Bind(address, MAX_CONNECTION);
			break;
		} catch(LException exc) {
			p++;
			err = exc.GetErrorCode();
			if ( (err != kOTAddressBusyErr && err != kEADDRINUSEErr)
									 || p == base_port + limit ) {
				_ExThrow();
			}
		} catch(...) {
			_ExThrow();
		}
	}
	_ExRevert();
	NET_MSG("***port %i opened\n", p);

	mThread = new LSimpleThread(&Server_Task, this);
	mThread->SetPriority(PRI_XL_FRONT);
	mThread->Resume();
	mThread->Yield(mThread);
	mEndpoint->AddListener(this);

#ifdef SERVER_SELF_TEST
	signature = 'TEST';
	printf("server test ...\n");
	LTCPEndpoint *ep = UNetworkFactory::CreateTCPEndpoint();
	_ExStop();
	try {
		LInternetAddress localAddress(0,0);
		ep->Bind(localAddress);
		LInternetAddress remoteAddress;
		remoteAddress.SetIPAddress(0x7F000001);
		remoteAddress.SetHostPort(p);
		ep->Connect(remoteAddress, 3);
		ep->Send((void*)"TEST",4);
		char test_buffer[32];
		UInt32 test_length = 32;
		Boolean flag = false;
		ep->ReceiveData(test_buffer, test_length, flag, 3);
		ep->Disconnect();
		ep->Unbind();
	} catch (...) {
	}
	_ExRevert();
	delete ep;

	if ( signature != 'CSvM' ) {  // TEST failure
		printf("server test failure\n");
		mThread->DeleteThread();
		_ExStop();
		try {
			mEndpoint->Unbind();
		} catch(...) {}
		delete mEndpoint;
		_ExRevert();
		p++;
		if ( p == base_port + limit )
			_ExThrow();
		goto retry;
	}
	printf("server test OK\n");
#else
	signature = 'CSvM';
#endif

	*open_port = p;
	mEndpointList = NULL;
	mEndpointListLast = NULL;
	mThreadList = NULL;
	mCounter = 0;

	ServerMasterList *new_sml =  (ServerMasterList *)d_alloc(sizeof(ServerMasterList));
 	new_sml->serverMaster = this;
 	new_sml->next = nil;
	if ( ! server_master_list ) {
		server_master_list = new_sml;
	} else {
		new_sml->next = server_master_list;
		server_master_list = new_sml;
	}
	
}

CServerMaster::~CServerMaster()
{
	signature = 0;

	ThreadList *o = mThreadList, *p;
	while (o) {
		p = o->next;
		delete o;
		o = p;
	}

	EndpointList *t = mEndpointList, *q;
	while (t) {
		q = t->next;
		delete t;
		t = q;
	}

	mThread->DeleteThread();

	_ExStop();
	try {
		NET_MSG("***Shuting down the server...\n",0);
		mEndpoint->Unbind();
	} catch(...) {}
	_ExRevert();
	delete mEndpoint;
	mEndpoint = 0;
	NET_MSG("***Server is down.\n",0);
 
	ServerMasterList *prev = nil, *next, *sml = server_master_list;
	for ( ;  sml;  prev = sml, sml = next ) {
		next = sml->next;
		if ( sml->serverMaster == this) {
			if ( prev ) {
				prev->next = sml->next;
			} else {
				server_master_list = sml->next;
			}
			d_f_ree(sml);
			break;
		}
	}
	if ( !sml )
		er_panic("ServerMasterList is invalid");
}

void
CServerMaster::ListenToMessage(MessageT inMessage, void* /* ioParam */)
{
	try {
		switch(inMessage) {
		  case T_LISTEN :
			NET_MSG("***Connection incoming!\n",0);
		  	mEndpoint->Listen();
			if ( mCounter >= MAX_CONNECTION  )
				mEndpoint->RejectIncoming();
			else {
				while( mThread->GetState() != LThread::threadState_Suspended )
					LThread::Yield(mThread);
				mThread->Resume();
				LThread::Yield(mThread);
			}
			break;
		}
	}
	catch(...) {
		printf("*** Incoming Connection Accept Failure\n");
	}
}

LTCPEndpoint*
CServerMaster::AcceptConnection()
{
	LTCPEndpoint * ret;
	if ( signature != 'CSvM' ) {
		throw "No Server Master";
	}

    retry:
	lock_task(server_master_lock);
	if ( mEndpointList ) {
		// Dequeue endpoint
		EndpointList *epl = mEndpointList;
		mEndpointList = epl->next;
		if ( !epl->next ) {
			mEndpointListLast = NULL;
		}
		ret = epl->endpoint;
		delete epl;
		mCounter--;
/*
epl = mEndpointList;
printf("'-' %x",epl);
while (epl) {
printf("->");
printf("%x",epl=epl->next);
}
printf(" : %x\n",mEndpointListLast);
*/
		NET_MSG("***Accepted connection <%d>\n", mCounter);
		unlock_task(server_master_lock, "CServerMaster::AcceptConnection(1)");
		return ret;
	}
	// No connected endpoint : push this thread and wait for incoming connection & being poped
	NET_MSG("***Waiting incoming connection <%d>\n", mCounter);
	THREAD t = (THREAD)LThread::GetCurrentThread();
	ThreadList *tl = new ThreadList;
	tl->thread = t;
	tl->next = mThreadList;
	mThreadList = tl;
	unlock_task(server_master_lock, "CServerMaster::AcceptConnection(2)");
	t->Suspend();
	
	// now dequeue
	goto retry;
}

void
CServerMaster::Server_Task(LThread &, void *inParam)
{
CServerMaster *sm = (CServerMaster *)inParam;

	while(1) {
		sm->mThread->Suspend();
		// woken up on connection incoming
		if ( sm->signature != 'CSvM' && sm->signature != 'TEST' ) {
			break;
		}
		lock_task(sm->server_master_lock);
		EndpointList * o = sm->mEndpointList;
		LTCPEndpoint * ep = NULL;

		_ExStop();
	  retry:
		try {
			ep = UNetworkFactory::CreateTCPEndpoint();
			sm->mEndpoint->AcceptIncoming(ep);
			//NET_MSG("# Incoming connection accepted.\n",0);
		} catch(LException exc) {
			SInt32 err = exc.GetErrorCode();
			fprintf(stderr, "# Error - Couldn't accept connection.(ID : %d)\n", err);
			if (ep)
				delete ep;
			ep = NULL;
			_ExRevert();
			unlock_task(sm->server_master_lock, "CServerMaster::Server_Task(2)");
			continue;
		} catch(...) {
			_ExThrow();
		}
		_ExRevert();

#ifdef SERVER_SELF_TEST
		if ( sm->signature == 'TEST' ) {
			sm->signature = 'CSvM';
			printf("server accept test...\n");
			try {
				char test_buffer[32];
				UInt32 test_length = 32;
				Boolean flag = false;
				ep->ReceiveData(test_buffer, test_length, flag, 5);
				ep->Send((void*)"TEST",4);
			} catch(...) {}
			try {
				ep->SendDisconnect();
				ep->Unbind();
			} catch(...) {}
			delete ep;
			ep = NULL;
			unlock_task(sm->server_master_lock, "CServerMaster::Server_Task(3)");
			continue;
		}
#endif
	 	// Enqueue new connected endpoint
		EndpointList* epl = new EndpointList;
		epl->endpoint = ep;
		epl->next  = NULL;
		if ( sm->mEndpointList ) {
			sm->mEndpointListLast->next = epl;
			sm->mEndpointListLast = epl;
		} else {
			// no list
			sm->mEndpointList = sm->mEndpointListLast = epl;
		}
		sm->mCounter++;
/*
epl = sm->mEndpointList;
printf("'+' %x",epl);
while (epl) {
printf("->");
printf("%x", epl=epl->next);
}
printf(" : %x\n",sm->mEndpointListLast);
*/		
		 while ( sm->mThreadList ) {
			// Pop waiting thread
			ThreadList *tl = sm->mThreadList;
			tl->thread->Resume();
			sm->mThreadList = tl->next;
			delete tl;
			NET_MSG("***Connection %d Accepted by waiting responder\n", sm->mCounter);
		}
		unlock_task(sm->server_master_lock, "CServerMaster::Server_Task(4)");
	}
}

void
DeleteAllServerMaster()
{
	while(server_master_list) 
		delete server_master_list->serverMaster;
}



#pragma mark -
//-----------------------------------------------------------------------------
//   Exception Utility
//-----------------------------------------------------------------------------

short _ExceptionDepth = 0;

void
_ExStop(void)
{
	SetDebugThrow_(debugAction_Nothing);	
	_ExceptionDepth++;
}

void
_ExRevert(void)
{
	_ExceptionDepth--;
	if ( _ExceptionDepth == 0 )
		SetDebugThrow_(debugAction_Alert);
}

void
_ExThrow(void)
{
	_ExceptionDepth--;
	SetDebugThrow_(debugAction_Alert);
fprintf(stderr, "\n<<< ExThrow is called >>>\n\n");
	throw;
}


} // extern "C"
