/*
 *	Module: 		gwssoup.cpp
 *	Author:			TANAKA Goh <goh@yamato.ibm.co.jp>
 *	Date:			1995/07/07
 *	Description:	DLL to use WinVN under offline environment via read/write
 *					WSSoup data.
 *					This source is extracted from gensock.cpp and modified.
 */

#include <windows.h>
#include <string.h>
#include "wssoup.h"

extern "C" {
#include "gensock.h"
}

#define SOCKET_BUFFER_SIZE	512

/* This is for NT */
#ifdef _WIN32
HANDLE		dll_module_handle;
#define		GENSOCK_EXPORT

/* This is for WIN16 */
#else
HINSTANCE	dll_module_handle;
#define		GENSOCK_EXPORT	_export
#endif

//
// ---------------------------------------------------------------------------
// container for a buffered SOCK_STREAM.

class connection : wssoup {
private:
	char*			in_buffer;
	char*			out_buffer;
	unsigned int	in_index;
	unsigned int	out_index;
	unsigned int	in_buffer_total;
	unsigned int	out_buffer_total;
	
public:
	connection(void);
	~connection(void);
	
	int			get_connected(char* hostname, char* service);
	int			get_buffer(int wait);
	int			close(void);
	int			getchar(int wait, char* ch);
	int			put_data(char* data, unsigned long length);
	int			put_data_buffered(char* data, unsigned long length);
	int			put_data_flush(void);
};

connection::connection(void)
{
	in_buffer = new char[SOCKET_BUFFER_SIZE + 1];
	
	if (in_buffer) {
		memset(in_buffer, 0, SOCKET_BUFFER_SIZE + 1);
	}
	
	out_buffer = new char[SOCKET_BUFFER_SIZE + 1];
	
	if (out_buffer) {
		memset(out_buffer, 0, SOCKET_BUFFER_SIZE + 1);
	}
	
	in_index = 0;
	out_index = 0;
	in_buffer_total = 0;
	out_buffer_total = 0;
}

connection::~connection(void)
{
	delete [] in_buffer;
	delete [] out_buffer;
}

//
// ---------------------------------------------------------------------------
//

int connection::get_connected(char FAR * hostname, char FAR * service)
{
	// if the ctor couldn't get a buffer
	if (!in_buffer || !out_buffer) {
		return ERR_CANT_MALLOC;
	}
	
	// --------------------------------------------------
	// resolve the service name
	//
	
	if (!stricmp(service, "nntp") || !strcmp(service, "119")) {
		put_server_response("200 InterNetNews NNRP\r\n");
	} else if (!stricmp(service, "smtp") || !strcmp(service, "25")) {
		put_server_response("220 Service ready\r\n");
	} else {
		return ERR_CANT_RESOLVE_SERVICE;
	}
	
	return 0;
}

//
//---------------------------------------------------------------------------
//
// The 'wait' parameter, if set, says to return WAIT_A_BIT
// if there's no data waiting to be read.

int connection::get_buffer(int wait)
{
	get_server_response(in_buffer, SOCKET_BUFFER_SIZE);
	
	if (!strcmp(in_buffer, "")) {
		return WAIT_A_BIT;
	}
	
	// reset buffer indices.
	in_buffer_total = strlen(in_buffer);
	in_index = 0;
	
	return 0;
}

//
//---------------------------------------------------------------------------
// get a character from this connection.
//

int connection::getchar(int wait, char FAR * ch)
{
	int	retval;
	
	if (in_index >= in_buffer_total) {
		if ((retval = get_buffer(wait)))
			return retval;
	}
	
	*ch = in_buffer[in_index++];
	return 0;
}

//
//---------------------------------------------------------------------------
// FIXME: should try to handle the fact that send can only take
// an int, not an unsigned long.

int connection::put_data(char * data, unsigned long length)
{
	if (!stricmp(get_server_command(), "DATA") ||
		!stricmp(get_server_command(), "POST")) {
		if (header == TRUE) {
			if (length - 2 && !memcmp(data + length - 2, "\r\n", 2)) {
				*(data + length - 2) = '\n';
				length--;
			}
		}
		
		memcpy(out_buffer, data, (size_t)length);
		*(out_buffer + length) = 0;
		put_server_command(out_buffer);
		memset(out_buffer, 0, sizeof(out_buffer));
		out_buffer_total = 0;
	} else {
		if (!memcmp(data + length - 2, "\r\n", 2)) {
			if (length - 2) {
				memcpy(out_buffer + out_buffer_total, data, (size_t)length - 2);
				*(out_buffer + out_buffer_total + length - 2) = 0;
			}
			
			put_server_command(out_buffer);
			memset(out_buffer, 0, sizeof(out_buffer));
			out_buffer_total = 0;
		} else {
			memcpy(out_buffer + out_buffer_total, data, (size_t)length);
			*(out_buffer + out_buffer_total + length) = 0;
			out_buffer_total += (unsigned int)length;
		}
	}
	
	return 0;
}

//
//
// buffered output
//

int connection::put_data_buffered(char * data, unsigned long length)
{
	int	retval;
	
	// delete From and Organization header
	if (header == TRUE && (!stricmp(get_server_command(), "DATA") ||
						   !stricmp(get_server_command(), "POST"))) {
		if (!strnicmp(data, "From: ", 6) ||
			!strnicmp(data, "Organization: ", 14)) {
			length = 0;
		//} else if (!strcmp(data, "\n")) {
		//} else if (length == 1 && !memcmp(data, "\n", 1)) {	// 0.99-6
		} else if ((length == 1 && !memcmp(data, "\n", 1)) || (length == 2 && !memcmp(data, "\r\n", 2))) {	// 0.99-6
			header = FALSE;
		}
	}
	
	if (length && header == FALSE) {
		if (*data == '.') {
			if (length == 1) {
				if (out_index >= 2 && !memcmp(out_buffer + out_index - 2, "\r\n", 2)) {
					*(out_buffer + out_index - 2) = '\n';
					*(out_buffer + out_index - 1) = 0;
					out_index--;
				}
			} else {
				if (out_index >= 1 && *(out_buffer + out_index - 1) == '.') {
					*(out_buffer + out_index - 1) = 0;
					out_index--;
				}
			}
		} else {
			if (out_index >= 2 && !memcmp(out_buffer + out_index - 2, "\r\n", 2)) {
				*(out_buffer + out_index - 2) = '\n';
				*(out_buffer + out_index - 1) = 0;
				out_index--;
			}
		}
	}
	
	while (length) {
		if ((out_index + length) < SOCKET_BUFFER_SIZE) {
			// we won't overflow, simply copy into the buffer
			memcpy(out_buffer + out_index, data, (size_t)length);
			out_index += (unsigned int)length;
			length = 0;
		} else {
			unsigned int	orphaned_chunk = SOCKET_BUFFER_SIZE - out_index;
			
			// we will overflow, handle it
			memcpy(out_buffer + out_index, data, orphaned_chunk);
			
			// send this buffer...
			if ((retval = put_data(out_buffer, SOCKET_BUFFER_SIZE))) {
				return retval;
			}
			
			length -= orphaned_chunk;
			out_index = 0;
			data += orphaned_chunk;
		}
	}
	
	return 0;
}

int connection::put_data_flush(void)
{
	int	retval;
	
	if ((retval = put_data(out_buffer, out_index))) {
		return retval;
	}
	
	out_index = 0;
	return 0;
}

//
//---------------------------------------------------------------------------
//

int connection::close(void)
{
	return 0;
}

//
//---------------------------------------------------------------------------
//

#ifndef _WIN32

// the DLL entry routine
int FAR PASCAL LibMain(HINSTANCE	hinstance,
					   WPARAM		data_seg,
					   LPARAM		heap_size,
					   LPSTR		command_line)
{
	dll_module_handle = hinstance;
	return 1;
}

#else

extern "C" {
	INT APIENTRY LibMain(HANDLE	hInst,
						 ULONG	reason_called,
						 LPVOID	reserved)
	{
		switch (reason_called) {
			case DLL_PROCESS_ATTACH:
				/* init */
				dll_module_handle = hInst;
				break;
			
			case DLL_THREAD_ATTACH:
				break;
			
			case DLL_THREAD_DETACH:
				break;
			
			case DLL_PROCESS_DETACH:
				break;
			
			default:
				break;
		}
		
		return 1;
	}
	
	/*
	 * This wrapper is the actual entry point for the DLL.  It ensures
	 * that the C RTL is correctly [de]initialized.
	 */
	
	BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
	
	BOOL WINAPI dll_entry_point(HINSTANCE	hinstDLL,
								DWORD		fdwReason,
								LPVOID		lpReserved)
	{
		/* Init the C run-time before calling any of your code */
		switch (fdwReason) {
			case DLL_PROCESS_ATTACH:
			case DLL_THREAD_ATTACH:
				if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved)) {
					return FALSE;
				} else {
					LibMain(hinstDLL, fdwReason, lpReserved);
				}
				
				break;
			
			case DLL_PROCESS_DETACH:
			case DLL_THREAD_DETACH:
				if (!_CRT_INIT(hinstDLL, fdwReason, lpReserved)) {
					return FALSE;
				}
				
				break;
		}
		
		return TRUE;
	}

}
#endif

// ---------------------------------------------------------------------------
// C/DLL interface
//

int FAR PASCAL GENSOCK_EXPORT
gensock_connect(char FAR * hostname, char FAR * service, socktag FAR * pst)
{
	connection *	conn = new connection;
	int				retval;
	
	if (!conn) {
		return ERR_INITIALIZING;
	}
	
	if ((retval = conn->get_connected(hostname, service))) {
		gensock_close(conn);
		*pst = 0;
		return retval;
	}
	
	*pst = (void FAR *)conn;
	
	return 0;
}

//
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_getchar(socktag st, int wait, char FAR * ch)
{
	connection *	conn = (connection *)st;
	int				retval = 0;
	
	if (!conn) {
		return ERR_NOT_A_SOCKET;
	}
	
	if ((retval = conn->getchar(wait, ch))) {
		return retval;
	}
	
	return 0;
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_put_data(socktag st, char FAR * data, unsigned long length)
{
	connection *	conn = (connection *)st;
	int				retval = 0;
	
	if (!conn) {
		return ERR_NOT_A_SOCKET;
	}
	
	if ((retval = conn->put_data(data, length))) {
		return retval;
	}
	
	return 0;
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_put_data_buffered(socktag st, char FAR * data, unsigned long length)
{
	connection *	conn = (connection *)st;
	int				retval = 0;
	
	if (!conn) {
		return ERR_NOT_A_SOCKET;
	}
	
	if ((retval = conn->put_data_buffered(data, length))) {
		return retval;
	}
	
	return 0;
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_put_data_flush(socktag st)
{
	connection *	conn = (connection *)st;
	int				retval = 0;
	
	if (!conn) {
		return ERR_NOT_A_SOCKET;
	}
	
	if ((retval = conn->put_data_flush())) {
		return retval;
	}
	
	return 0;
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_gethostname(char FAR * name, int namelen)
{
	return 0;
}

//---------------------------------------------------------------------------
//
//

int FAR PASCAL GENSOCK_EXPORT
gensock_close(socktag st)
{
	connection *	conn = (connection *)st;
	int				retval;
	
	if (!conn) {
		return ERR_NOT_A_SOCKET;
	}
	
	if ((retval = conn->close())) {
		return retval;
	}
	
	delete conn;
	
	return 0;
}
