#include "server.h"
#include "..\DynamicLink\serverblock.h"
#include "..\ClientChannel\channel.h"
#include "utilities.h"
#include "global.h"

static char *Buffer = NULL;
static HANDLE ClientProcessHandle = NULL;

#define INIT_SERVER				0
#define INIT_CHANNEL			1
#define PROCESS_REQUESTS		2
#define SET_GLOBAL_SERVER_READY	3

static State = INIT_SERVER;

/*
**
*/
#define TEXT	0
#define DATA	1

static int *CodeBuffer = NULL;
static int *DataBuffer = NULL;
static int CodeStart   = 0;
static int DataStart   = 0;
static int CodeSize		= 0;
static int DataSize		= 0;

CLEAN_BOOL StoreLong(int pvMem, int l)
{
	char *dest;

	dest = (((int) CodeBuffer) + (pvMem - CodeStart));

	*((int *) dest) = l;

    return( CLEAN_TRUE );
}

int mwrites(int kind, int offset, CLEAN_STRING s, int address)
{
	char *dest;
	int i;

	if (kind == TEXT) {
		dest = ((int) CodeBuffer) + offset;
	}
	else {
		dest = ((int) DataBuffer) + offset;
	}

	for( i = 0; i < (s->length); i++)
	{
		dest[i] = (s->characters)[i];
	}


	return (address + (s->length));
}

// ServerInfoBlock
static PSERVER_INFO_BLOCK PServerInfoBlock = NULL;

static void
SetEnvInt (char *name, int value)
{
	char env[sizeof(int) * 3 + 2]; /* buffer size conservatively estimated */

	(void) sprintf(env, "%d", value);
	SetEnvironmentVariableA(name, env);
}

// ---------------------------------------------------------------------------------
void StartProcess(CLEAN_STRING current_directory, CLEAN_STRING file_name, CLEAN_STRING commandline,
				   CLEAN_BOOL *ok, int *client_id)
{
	// ServerInfoBlock
	DWORD dwServerId;
	HANDLE hServerReady;
	HANDLE hClientReady;
	HANDLE hFileMapping;

	PROCESS_INFORMATION pi;

	STARTUPINFO si;
	
	BOOL fSuccess;					// Process information

	// ServerInfoBlock
	AddClient_to_ServerInfoBlock( 
		PServerInfoBlock, 
		&dwServerId, 
		&hServerReady, 
		&hClientReady,
		&hFileMapping);

	/* set these environment variable in the current process, which means
	   that they are inherited by the child */
	SetEnvInt( "dwServerId", dwServerId);
	SetEnvInt( "hServerReady", hServerReady);
	SetEnvInt( "hClientReady", hClientReady);
	SetEnvInt( "hFileMapping", hFileMapping);
	SetEnvInt( "MESSAGE_SIZE", MESSAGE_SIZE);

	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);

	si.lpTitle = file_name->characters;
	si.wShowWindow = SW_SHOWNORMAL;
	si.dwFlags = STARTF_USESHOWWINDOW;

	fSuccess = CreateProcess( 
		NULL,							// Executable
		commandline->characters,		// Commandline
		NULL,							// Standard security (Proces)
		NULL,							// Standard security (Thread)
		TRUE,							// No handle inherited by child
		CREATE_NEW_CONSOLE,				// Plain process
		NULL,							// Environment of parent
		current_directory->characters, /*dir,*/				// Current directory
		&si,							// Startup information
		&pi);							// Process information

	if( !fSuccess ) {
		*ok = 0;
		*client_id = 0;
	} else {
		CloseHandle(pi.hThread);

		UpdateClient_in_ServerInfoBlock( 
			PServerInfoBlock, 
			pi.hProcess, 
			pi.dwProcessId);

		*ok = CLEAN_TRUE;
		*client_id = pi.dwProcessId;
	}
}

void ReceiveReqWithTimeOut(CLEAN_BOOL static_application_as_client, CLEAN_BOOL *timeout,int *client_id, CLEAN_STRING *result)
{
	char *s;
	static CLEAN_STRING cs = NULL;

	int clientId;

	// ServerInfoBlock
	DWORD dwResult;
	int client_i;

	// ServerInfoBlock
	DWORD dwServerId;
	HANDLE hServerReady;
	HANDLE hClientReady;
	HANDLE hFileMapping;

	HANDLE hClient;
	HANDLE hServer;

	BOOL ok;
	int length;
	char *buffer;

	*timeout = CLEAN_FALSE;
	
#ifdef RECEIVEREQ
	msg( "ReceiveReq 1" );

#endif

	if( cs != NULL ) {
		rfree( cs );
		cs = NULL;
	}

#ifdef RECEIVEREQ
	msg( "ReceiveReq 2" );
#endif

	if( State == INIT_SERVER ) {
		State = INIT_CHANNEL;

		// ServerInfoBlock
		PServerInfoBlock = Empty_ServerInfoBlock();
	    AddInitialSyncs_to_ServerInfoBlock( PServerInfoBlock );

		if( static_application_as_client == CLEAN_FALSE ) {
			cs = (CLEAN_STRING) rmalloc (sizeof(int)+12+rstrlen(GetCommandLine()) + 1);

			//                         0123456789 * 0 
			rsprintf( cs->characters, "AddClient\n%s\n\n", GetCommandLine() );
			cs->length = rstrlen(cs->characters);

			*client_id = 0;
			*result = cs;
			return;
		}

	
		/*
		** Static application as client
		*/
	}
	else if( State == INIT_CHANNEL ) {
		State = PROCESS_REQUESTS;
	}
	else if( State == SET_GLOBAL_SERVER_READY ) {
		/*
		** Settings this flag later guarantees round-robin fashion
		** of serving clients. Not doing this, guarantees that 
		** registering clients take priority.
		*/
		SetEvent( PServerInfoBlock->hClientKilledOrReady[GLOBAL_SERVER_READY] );
		State = PROCESS_REQUESTS;
	}

	// ServerInfoBlock, only initial block
	dwResult = WaitForMultipleObjects(
		(PServerInfoBlock->n_Clients * 2) - 1,
		&(PServerInfoBlock->hClientKilledOrReady[LOCAL_CLIENT_READY]),
		FALSE,
		10);//INFINITE );

	if( dwResult == WAIT_TIMEOUT ) {
		*timeout = CLEAN_TRUE;

		cs = (CLEAN_STRING) rmalloc (sizeof(int)+ 12);
		cs->length = 0;

		*result = cs;
		*client_id = 0;
		return;
	}
	
	if( dwResult == WAIT_FAILED) {
		error();
		msg( "ReceiveReqWithTimeOut: WaitForMultipleObjects failed" );
		ExitProcess(-1);
	}

	client_i = dwResult - WAIT_OBJECT_0 + 1;
#ifdef DEBUG
	rsprintf( q, "Client #%d with %d", Client_i / 2, Client_i  % 2);
	msg( q );
#endif DEBUG

	/*
	** Protocol message has been received, the following code handles
	** the messages
	*/

	if( client_i < 2 ) {
		if( client_i != LOCAL_CLIENT_READY ) {
			msg( "local client should be ready" );
			ExitProcess(-1);
		}

		Buffer = PServerInfoBlock->hView[INDEX(client_i)];
		buffer = GetReceiveBuffer (Buffer);

		clientId = *((DWORD *) (buffer+GLOBAL_BUFFER_START));

		cs = (CLEAN_STRING) rmalloc(sizeof(int) + rstrlen( buffer + sizeof(DWORD) + GLOBAL_BUFFER_START) + 1);
		cs->length = rstrlen( buffer + sizeof(DWORD) + GLOBAL_BUFFER_START );
		rscopy(cs->characters, buffer + sizeof(DWORD) + GLOBAL_BUFFER_START);

#ifdef DEBUG
		msg( cs->characters );
#endif 	
		State = SET_GLOBAL_SERVER_READY;

		if( clientId != UNKNOWN_CLIENT_ID ) {
			/*
			** An AddAndInit--protocol. Reply by sending the handles
			** to the required synchronization objects.
			*/
			// ServerInfoBlock
			AddClient_to_ServerInfoBlock( 
				PServerInfoBlock, 
				&dwServerId, 
				&hServerReady, 
				&hClientReady,
				&hFileMapping);

			hClient = OpenProcess(STANDARD_RIGHTS_REQUIRED | PROCESS_ALL_ACCESS, FALSE, clientId);
			if( hClient == NULL ) {
				error();
				msg( "ReceiveReq: could not open handle to statically linked client" );
				ExitProcess(-1);
			}

			UpdateClient_in_ServerInfoBlock( 
				PServerInfoBlock, 
				hClient, 
				clientId);

			/*
			** Open handle to itself
			*/
			hServer = OpenProcess(STANDARD_RIGHTS_REQUIRED | PROCESS_ALL_ACCESS, FALSE, dwServerId);
			if( hServer == NULL ) {
				error();
				msg( "ReceiveReq: could not open handle to itself, the dynamic linker" );
				ExitProcess(-1);
			}
			
			/*
			** Fill buffer
			*/
			*((DWORD *) (buffer+GLOBAL_BUFFER_START)) = dwServerId;
			ok = DuplicateHandle(hServer, hServerReady, hClient,
				((HANDLE *) (buffer+ GLOBAL_BUFFER_START + sizeof(DWORD) )), NULL, FALSE, DUPLICATE_SAME_ACCESS);
			if( !ok ) {
				error();
				msg( "Receivereq: could not dup handle" );
				ExitProcess(-1);
			}

			ok = DuplicateHandle(hServer, hClientReady, hClient,
				((HANDLE *) (buffer+ GLOBAL_BUFFER_START + sizeof(DWORD) + sizeof(HANDLE) )), NULL, FALSE, DUPLICATE_SAME_ACCESS);
			if( !ok ) {
				error();
				msg( "Receivereq: could not dup handle" );
				ExitProcess(-1);
			}

			ok = DuplicateHandle(hServer, hFileMapping, hClient,
				((HANDLE *) (buffer+ GLOBAL_BUFFER_START + sizeof(DWORD) + 2 * sizeof(HANDLE) )), NULL, FALSE, DUPLICATE_SAME_ACCESS);
			if( !ok ) {
				error();
				msg( "Receivereq: could not dup handle" );
				ExitProcess(-1);
			}

			*((DWORD *) (buffer+ GLOBAL_BUFFER_START + sizeof(DWORD) + 3 * sizeof(HANDLE) )) = MESSAGE_SIZE;
			
			// Send to client
			SetEvent( PServerInfoBlock->hServerReady[LOCAL_SERVER_READY] );

			// Wait for confirmation that Client processed buffer
			WaitForSingleObject( PServerInfoBlock->hClientKilledOrReady[LOCAL_CLIENT_READY], INFINITE );

			// Signal Client
			SetEvent( PServerInfoBlock->hServerReady[LOCAL_SERVER_READY] );
		}
		else 
			SetEvent( PServerInfoBlock->hServerReady[LOCAL_SERVER_READY] );

		*client_id = clientId;
		*result = cs;

		ReleaseReceiveBuffer (buffer, Buffer);
		return;
	}

	if( IS_CLIENT_KILLED(client_i) ) {

		*client_id = PServerInfoBlock->ProcessId[ INDEX( client_i ) ];

		RemoveClient_from_ServerInfoBlock( PServerInfoBlock, (INDEX( client_i )) );


		cs = (CLEAN_STRING) rmalloc(sizeof(int) + 7 );
		cs->length = 7;
		//						  012345 6
		rsncopy( cs->characters, "Close\n\n", 7);
		*result = cs;
		return; 
	}

	if( IS_CLIENT_READY(client_i) )
		;

#ifdef DEBUG
	msg( "client ready" );
#endif


	// Prepare communication channel
	Buffer = PServerInfoBlock->hView[INDEX(client_i)];
	buffer = GetReceiveBuffer (Buffer);

	SetHandlesToClient(
		PServerInfoBlock->hServerReady[INDEX(client_i)],
		PServerInfoBlock->hClientKilledOrReady[INDEX_CLIENT_KILLED(client_i)],
		PServerInfoBlock->hClientKilledOrReady[INDEX_CLIENT_READY(client_i)] );


	length = *((DWORD *) (buffer+SIZE_OF_MESSAGE));

	ClientProcessHandle = PServerInfoBlock->hClientKilledOrReady[INDEX_CLIENT_KILLED(client_i)];

	s = buffer+DATA_START+sizeof(DWORD);	

	cs = (CLEAN_STRING) rmalloc (sizeof(int) + /*rstrlen(s) + 1*/ length );
	rsncopy( cs->characters, s, length);
	cs->length = length;  rstrlen(s);

	(*result) = cs;
	*client_id = PServerInfoBlock->ProcessId[ INDEX(client_i) ];

	ReleaseReceiveBuffer (buffer, Buffer);
}

void ReceiveCodeDataAdr(int code_size, int data_size, CLEAN_BOOL *result, int *code_start, int *data_start) {
	
#ifdef DEBUG
	msg( "ReceiveCodeDataAdr" );
#endif

	if( State != PROCESS_REQUESTS ) {
		msg("ReceiveCodeDataAdr: no init");
		ExitProcess(-1);
	}

	// Reserve memory for server
	// Code
	if( code_size != 0 ) {
		CodeBuffer = (int *) VirtualAlloc( NULL,code_size,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE );

		if( !CodeBuffer ) {
			error();
			msg( "**CodeBuffer");
			ExitProcess(-1);
		}
	}

	// Data
	if( data_size != 0) {
		DataBuffer = (int *) VirtualAlloc( NULL,data_size,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE );

		if( (DataBuffer == NULL) && (data_size != 0)) {
			error();
			msg( "DataBuffer");
			ExitProcess(-1);
		}
	}

	// Make client reserve memory for code and data
	Buffer[MESSAGE_TYPE] = ADDRESS_UNKNOWN;

	*((int *) (Buffer+DATA_START)) = code_size;
	*((int *) (Buffer+DATA_START+sizeof(int))) = data_size;

#ifdef DEBUG
	msg( "ReceiveCodeDataAdr 2" );
#endif

	Send();

#ifdef DEBUG
	msg( "ReceiveCodeDataAdr 3" );
#endif

	// Receive message containing startaddresses
	if( (Receive()) == CLIENT_READY ) {
		CodeSize = code_size;
		CodeStart = *((int *) (Buffer+DATA_START));
		*code_start = CodeStart;

		DataSize = data_size;
		DataStart =	*((int *) (Buffer+DATA_START+sizeof(int))); 
		*data_start = DataStart;

		// Operation correctly performed
		*result = CLEAN_TRUE;
	} else { 
		*result = CLEAN_FALSE;

		// Free used server buffers (code,data)
 		if( !VirtualFree(CodeBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReceiveCodeDataAdr: 1" );
			ExitProcess(-1);
		}
		CodeBuffer = NULL;


		if( !VirtualFree(DataBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReceiveCodeDataAdr: 2" );
			ExitProcess(-1);
		}
		DataBuffer = NULL;
	}

#ifdef DEBUG
	msg( "ReceiveCodeDataAdr 4");
#endif

}

void NeedBaseLibraries (CLEAN_STRING clstring, int n_libraries,CLEAN_BOOL *result,CLEAN_STRING *s) {

	char *hLibraryBufferView = NULL;
	BufferInfo server_info;
	BufferInfo client_info;

	if (n_libraries == 0) {
		*result = CLEAN_TRUE;
		return;
	}

	/* Allocate and fill buffer with library names */

	// Work						
	CreateBuffer( "ServerLibraryBuffer", clstring->length, FALSE, &server_info);

	hLibraryBufferView = server_info.hView;
	rsncopy(hLibraryBufferView, clstring->characters,clstring->length);

	/* Send a message demanding the bases of libraries */

	Buffer[MESSAGE_TYPE] = NEED_BASE_OF_LIBRARIES;
	*((int *) (Buffer+DATA_START)) = n_libraries;
	*((int *) (Buffer+DATA_START+sizeof(int))) = clstring->length;

#ifdef DEBUG
	rsprintf( bs, "SERVER: n_libraries: %d, string length: %d", n_libraries, clstring->length );
	msg( bs );
#endif

	Send();

#ifdef DEBUG
	msg( "NeedBaseLibraries 3a" );
#endif

	if( (Receive()) == CLIENT_READY ) {

		CloseBuffer( &server_info );

		/*
		** Answer received. Convert it to a string
		*/
#ifdef DEBUG
		msg( "NeedBaseLibraries 4" );
#endif

		CreateBuffer( "ClientLibraryBuffer",BUFFER_SIZE_UNKNOWN, TRUE, &client_info);
		hLibraryBufferView = client_info.hView;

		*result = CLEAN_TRUE;
		*s = cleanstringn(hLibraryBufferView,sizeof(int) * n_libraries);

		CloseBuffer( &client_info );
	} else {
		*result = CLEAN_FALSE;
		*s = EmptyCleanString;
		msg( "NeedBaseLibraries: client killed" );
	}
}

int FlushBuffers() {
	DWORD lpNumberOfBytesWritten;

	if( (CodeBuffer != NULL) && (CodeSize !=0 ) ) {
		
		if( !WriteProcessMemory(ClientProcessHandle,
							(LPVOID) CodeStart, //code,
							CodeBuffer, // source,
							CodeSize,
							&lpNumberOfBytesWritten) ) {
			error();
			msg( "mwrites");
			ExitProcess(-1);
		}

		if( !VirtualFree(CodeBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReplyReq: if( !VirtualFree(CodeBuffer,0,MEM_RELEASE) ) {" );
			ExitProcess(-1);
		}
		CodeBuffer = NULL;
	}

	if( (DataBuffer != NULL) && (DataSize != 0) ) {
		if( !WriteProcessMemory(ClientProcessHandle,
							(LPVOID) DataStart, //code,
							DataBuffer, // source,
							DataSize,
							NULL) ) {
			error();
			msg( "mwrites");
			ExitProcess(-1);
		}

		if( !VirtualFree(DataBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReplyReq: 1" );
			ExitProcess(-1);
		}
		DataBuffer = NULL;
	}

	return 1;
}

int ReplyReqS(CLEAN_STRING message)
{
	char	*buffer;

#ifdef DEBUG
	msg( "ReplyReqS" );
#endif

	if( State != PROCESS_REQUESTS ) {
		msg("ReplyReqS: no init");
		ExitProcess(-1);
	}

#ifdef DEBUG_MV
	if( (CodeBuffer != NULL) && (CodeSize !=0 ) ) {
		if( !WriteProcessMemory(ClientProcessHandle,
							(LPVOID) CodeStart, //code,
							CodeBuffer, // source,
							CodeSize,
							&lpNumberOfBytesWritten) ) {
			error();
			msg( "mwrites");
			ExitProcess(-1);
		}

		if( !VirtualFree(CodeBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReplyReq: if( !VirtualFree(CodeBuffer,0,MEM_RELEASE) ) {" );
			ExitProcess(-1);
		}
		CodeBuffer = NULL;
	}

	if( (DataBuffer != NULL) && (DataSize != 0) ) {
		if( !WriteProcessMemory(ClientProcessHandle,
							(LPVOID) DataStart, //code,
							DataBuffer, // source,
							DataSize,
							NULL) ) {
			error();
			msg( "mwrites");
			ExitProcess(-1);
		}

		if( !VirtualFree(DataBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReplyReq: 1" );
			ExitProcess(-1);
		}
		DataBuffer = NULL;
	}
#endif

	buffer	= GetSendBuffer (message->length + DATA_START, MESSAGE_SIZE, Buffer, ClientProcessHandle);

	buffer[MESSAGE_TYPE] = ADDRESS_KNOWN;
	*((DWORD *) (buffer+SIZE_OF_MESSAGE)) = message->length;

	rsncopy( buffer + DATA_START,message->characters, message->length);

	Send();

	ReleaseSendBuffer (buffer, Buffer);
#ifdef DEBUG
	msg( "returning from ReplyReqS" );
#endif

	return 1;
}

// -------
int ReplyReq(int num)
{
	DWORD lpNumberOfBytesWritten;

#ifdef DEBUG
	msg( "ReplyReq" );
#endif

	if( State != PROCESS_REQUESTS ) {
		msg("ReplyReq: no init");
		ExitProcess(-1);
	}

	if( (CodeBuffer != NULL) && (CodeSize !=0 ) ) {
		
		if( !WriteProcessMemory(ClientProcessHandle,
							(LPVOID) CodeStart, //code,
							CodeBuffer, // source,
							CodeSize,
							&lpNumberOfBytesWritten) ) {
			error();
			msg( "mwrites");
			ExitProcess(-1);
		}

		if( !VirtualFree(CodeBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReplyReq: if( !VirtualFree(CodeBuffer,0,MEM_RELEASE) ) {" );
			ExitProcess(-1);
		}
		CodeBuffer = NULL;
	}

	if( (DataBuffer != NULL) && (DataSize != 0) ) {
		if( !WriteProcessMemory(ClientProcessHandle,
							(LPVOID) DataStart, //code,
							DataBuffer, // source,
							DataSize,
							NULL) ) {
			error();
			msg( "mwrites");
			ExitProcess(-1);
		}

		if( !VirtualFree(DataBuffer,0,MEM_RELEASE) ) {
			error();
			msg( "ReplyReq: 1" );
			ExitProcess(-1);
		}
		DataBuffer = NULL;
	}

	Buffer[MESSAGE_TYPE] = ADDRESS_KNOWN;
	*((int *) (Buffer+DATA_START)) = num;

	Send();

#ifdef DEBUG
	msg( "returning from ReplyReq" );
#endif

	return 1;
}

CLEAN_BOOL KillClient(int client_id) {

	int i;

	i = (N_OF_RESERVED_ENTRIES_INITIAL_BLOCK / 2);
	
	while( i < ((N_OF_RESERVED_ENTRIES_INITIAL_BLOCK / 2) + (PServerInfoBlock->n_Clients)) )  {

		if( (PServerInfoBlock->ProcessId)[i] == client_id ) {
			
			TerminateProcess( PServerInfoBlock->hClientKilledOrReady[2 * i + CLIENT_KILLED], 0 );

			return( CLEAN_TRUE );
		}
		i++;
	}
	
	msg( "KillClient: internal error; could not kill client process" );
	ExitProcess(-1);
	return( CLEAN_FALSE );
}
