/*****************************************************************************
*   Sockets routines to handle socket io of an objects.			     *
*									     *
* Written by:  Gershon Elber				Ver 0.1, June 1993.  *
*****************************************************************************/

#include <stdio.h>
#include <sys/types.h>

#ifdef __UNIX__
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#endif /* __UNIX__ */

#ifdef __WINNT__
#include <stdlib.h>
#include <windows.h>
#include <winsock.h>
#include <io.h>
#endif /* __WINNT__ */

#include "irit_sm.h"
#include "irit_soc.h"

#ifdef OS2GCC
#define INCL_DOSPROCESS
#include <os2.h>

static HPIPE
    pipIrit = 0;
#endif /* OS2GCC */

static int
    GlblBinaryIPC = FALSE,
    GlblMasterSoc = 0,
    GlblCommuSoc = 0;

static void SocketPrintError(char *Str);
#ifdef __UNIX__
#if defined(_AIX) || defined(__hpux)
typedef void (*SignalFuncType)(int); 
#else
typedef void (*SignalFuncType)(void); 
#endif /* _AIX || __hpux */
static void SocNoConnectionHandler(void);
static void SocNoConnectionDummyHandler(void);
#endif /* __UNIX__ */

/*****************************************************************************
*  IO error print routine.						     *
*****************************************************************************/
static void SocketPrintError(char *Str)
{
#   if defined(__UNIX__) || defined(OS2GCC)
	perror(Str);
#   endif /* __UNIX__ || OS2GCC */
#   ifdef __WINNT__
	fprintf(stderr, "iritserver: %s error %d\n", Str, WSAGetLastError());
#   endif /* __WINNT__ */
}

/*****************************************************************************
*  Write a single char.							     *
*****************************************************************************/
void SocServerWriteChar(char c)
{
#if defined(__UNIX__) || defined(__WINNT__)
    int i;

    if (GlblCommuSoc <= 0) {
	IritPrsrFatalError("Attempt to write to a closed (broken!?) socket");
	return;
    }

    while ((i = send(GlblCommuSoc, &c, 1, 0)) != 1) {
	if (i < 0)			       /* Lost connection probably. */
	    SocServerCloseSocket();
	IritSleep(10);
    }
#endif /* __UNIX__ || __WINNT__ */
#ifdef OS2GCC
    ULONG BytesWritten;
    ULONG rc;

    rc = DosWrite(pipIrit, &c, 1, &BytesWritten);
#endif /* OS2GCC */
}

/*****************************************************************************
*  Write a single line of line length characters.			     *
*****************************************************************************/
void SocServerWriteLine(char *Line, int LineLen)
{
#if defined(__UNIX__) || defined(__WINNT__)
    int i;

    if (GlblCommuSoc <= 0) {
	IritPrsrFatalError("Attempt to write to a closed (broken!?) socket");
	return;
    }

    while ((i = send(GlblCommuSoc, Line, LineLen, 0)) < LineLen) {
	if (i < 0)			       /* Lost connection probably. */
	    SocServerCloseSocket();
	IritSleep(10);
	LineLen = LineLen - i;
	Line = &Line[i];
    }
#endif /* __UNIX__ || __WINNT__ */
#ifdef OS2GCC
    ULONG BytesWritten;
    ULONG rc;

    rc = DosWrite(pipIrit, Line, LineLen, &BytesWritten);
#endif /* OS2GCC */
}

/*****************************************************************************
*  Creates a server socket. Returns TRUE if successful.			     *
*****************************************************************************/
int SocServerCreateSocket(void)
{
#if defined(__UNIX__) || defined(__WINNT__)
    int s;
    char *portnum;
    struct sockaddr_in sain;
    struct servent *serv;

    ZAP_MEM(&sain, sizeof(struct sockaddr_in));
    sain.sin_addr.s_addr = htonl(INADDR_ANY);
    sain.sin_family = AF_INET;

#ifdef __WINNT__
    {
	WSADATA WSAData;

	if ((s = WSAStartup(MAKEWORD(1, 1), &WSAData)) != 0) {
	     fprintf(stderr, "iritserver: WSAStartup: error %d\n",
		     WSAGetLastError());
	     exit(1);
	}
    }
#endif

    if ((portnum = getenv("IRIT_SERVER_PORT")) != NULL)
	sain.sin_port = atoi(portnum);
    else if ((serv = getservbyname(IRIT_TCP_SERVICE, "tcp")) == NULL)
	sain.sin_port = htons(IRIT_TCP_PORT);
    else
	sain.sin_port = serv->s_port;

    /* Create the socket and bind. */
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	IritPrsrFatalError("iritserver: socket");
	return FALSE;
    }
    if (bind(s, (struct sockaddr *) &sain, sizeof(struct sockaddr_in)) < 0) {
	IritPrsrFatalError("iritserver: bind");
	return FALSE;
    }

    if (listen(s, 5) < 0) {
	IritPrsrFatalError("iritserver: listen");
	return FALSE;
    }

#ifdef __UNIX__
    signal(SIGPIPE, (SignalFuncType) SocNoConnectionHandler);
#endif /* __UNIX__ */

    GlblMasterSoc = s;

#endif /* __UNIX__ || __WINNT__ */

    GlblBinaryIPC = getenv("IRIT_BIN_IPC") != NULL;

    return TRUE;
}

/*****************************************************************************
*  Accept a client socket connection. Blocks until connection is established.*
*****************************************************************************/
void SocServerAcceptConnection(void)
{
#if defined(__UNIX__) || defined(__WINNT__)
    struct sockaddr_in client_sain;
    int len = sizeof(client_sain);

    while ((GlblCommuSoc = accept(GlblMasterSoc,
				  (struct sockaddr *) &client_sain,
				  &len)) < 0);
#endif /* __UNIX__ || __WINNT__ */
#ifdef OS2GCC
    ULONG rc;
    rc = DosCreateNPipe(IRIT_PIPE,	    /* Drawing command pipe. */
			&pipIrit,
			NP_ACCESS_DUPLEX | NP_NOWRITEBEHIND,
			NP_WAIT | NP_TYPE_BYTE | NP_READMODE_BYTE | 1,
			IRIT_PIPE_BUFFER_SIZE,
			IRIT_PIPE_BUFFER_SIZE,
			0);

    if (rc != 0) {
	fprintf(stderr,
		"DosCreateNPipe: error in creating %s\n", IRIT_PIPE);
	exit(1);
    }

    if ((rc = DosConnectNPipe(pipIrit)) != 0) {
	fprintf(stderr,
		"DosConnectNPipe: error in connection %s\n",
		IRIT_PIPE);
	exit(1);
    }

    GlblCommuSoc = (int) pipIrit;
#endif /* OS2GCC */
}

/*****************************************************************************
*  Close a server socket.						     *
*****************************************************************************/
void SocServerCloseSocket(void)
{
    if (GlblCommuSoc > 0) {
#ifdef __UNIX__
	char Line[LINE_LEN];

	/* Force the system to realize that the connection is down. This is */
	/* a kluge to solve the time out problem that happens under unix.   */
	IritSleep(1000);
	signal(SIGPIPE, (SignalFuncType) SocNoConnectionDummyHandler);
	send(GlblCommuSoc, Line, LINE_LEN, 0);

	if (close(GlblCommuSoc) != 0)
	    SocketPrintError("iritserver: close");
#endif /* __UNIX__ */
#ifdef __WINNT__
	closesocket(GlblCommuSoc);
#endif /* __WINNT__ */

	GlblCommuSoc = 0;
    }
}

/*****************************************************************************
*  Returns TRUE if connection is active.				     *
*****************************************************************************/
int SocServerActive(void)
{
    return GlblCommuSoc > 0;
}

#ifdef __UNIX__
/*****************************************************************************
*  Clean the server socket in case of a broken pipe.			     *
*****************************************************************************/
static void SocNoConnectionHandler(void)
{
    SocServerCloseSocket();
    signal(SIGPIPE, (SignalFuncType) SocNoConnectionHandler);
    IritPrsrParserAbort(IP_ERR_SOCKET_BROKEN, "");
}
/*****************************************************************************
*  Dummy handler							     *
*****************************************************************************/
static void SocNoConnectionDummyHandler(void)
{
}
#endif /* __UNIX__ */

/*****************************************************************************
* Attempt to write an object to a socket.				     *
*****************************************************************************/
void SocServerWriteOneObject(IPObjectStruct *PObj)
{
    char *ErrorMsg;

    if (GlblCommuSoc <= 0) {
	char *DisplayStr;

	/* Needs to start up the client display - it is not up yet */
	if ((DisplayStr = getenv("IRIT_DISPLAY")) != NULL) {
#ifdef OS2GCC
	    char LoadError[LINE_LEN], Env[LINE_LEN];
	    RESULTCODES Results;
	    ULONG rc;
	    int i;

	    strcpy(Env, "IRIT_DISPLAY_S=1");
	    i = strlen(Env) + 1;
	    if (GlblBinaryIPC) {
	        strcpy(&Env[i], "IRIT_BIN_IPC=1");
		i += strlen(&Env[i]) + 1;
	    }
	    Env[i] = '\0';

	    LoadError[0] = 0;
	    rc = DosExecPgm(LoadError,
			    sizeof(LoadError),
			    EXEC_BACKGROUND,
			    "",
			    Env,
			    &Results,
			    DisplayStr);
	    if (rc != 0) {
		fprintf(stderr,
			"DosExecPgm: (Error %d) \"%s\"\n", rc, LoadError);
		exit(1);
	    }
#else
#ifdef __UNIX__
	    char DisplayCommand[LINE_LEN];

	    sprintf(DisplayCommand, "%s &", DisplayStr);
	    system(DisplayCommand);
#else
#ifdef __WINNT__
	    char DisplayCommand[LINE_LEN];

	    sprintf(DisplayCommand, "start %s", DisplayStr);
	    system(DisplayCommand);
#else
#endif /* __WINNT__ */
#endif /* __UNIX__ */
#endif /* OS2GCC */
	}
	else
	    fprintf(stderr,
		    "Irit: Startup your display device - I am waiting...\n");
	SocServerAcceptConnection();
	fprintf(stderr, "Irit: Connection with client established.\n");

#ifdef __UNIX__
	signal(SIGPIPE, (SignalFuncType) SocNoConnectionHandler);
#endif /* __UNIX__ */
    }

    if (IP_IS_UNDEF_OBJ(PObj)) {
	fprintf(stderr, "Socket: Attempt to write an undefined object.\n");
	return;
    }

    IritPrsrWriteSocket(TRUE);
    if (GlblBinaryIPC) {
	IritPrsrPutBinObject(NULL, PObj);
    }
    else {
	IritPrsrPutObject(NULL, PObj);
    }
    IritPrsrWriteSocket(FALSE);

    if (IritPrsrParseError(&ErrorMsg)) {
	fprintf(stderr, "Socket: %s\n", ErrorMsg);
    }
}

#ifdef DEBUG_SERVER

/*****************************************************************************
* Simple test for the server - reads file and dumps one object every key.    *
*****************************************************************************/
void main(int argc, char ** argv)
{
    FILE
	*f = argc == 2 ? fopen(argv[1], "r") : stdin;
    IPObjectStruct *PObj,
        *PObjClear = IPAllocObject("_COMMAND_", IP_OBJ_STRING, NULL),
	*PObjs = IritPrsrGetObjects(f);

    if (argc == 2)
	fclose(f);
    strcpy(PObjClear -> U.Str, "CLEAR");

    SocServerCreateSocket();

    while (TRUE) {
	for (PObj = PObjs; PObj != NULL; PObj = PObj -> Pnext) {
	    fprintf(stderr, "Press return to send data.\n");
	    getchar();
	    SocServerWriteOneObject(PObj);
	}

	fprintf(stderr, "Done. Press return to send clear.\n");
	getchar();
	SocServerWriteOneObject(PObjClear);
    }

    SocServerCloseSocket();
}

#endif /* DEBUG_SERVER */
