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

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

#ifdef __UNIX__
#if (defined(ultrix) && defined(mips)) || defined(_AIX) || defined(sgi)
#    include <fcntl.h>
#else
#    include <sys/fcntl.h>
#endif /* (ultrix && mips) || _AIX || sgi */
#if defined(__hpux)
#    include <sys/file.h>
#endif /* __hpux */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#endif /* __UNIX__ */

#ifdef OS2GCC
#    include <os2.h>
HFILE IritPipe;
#endif /* OS2GCC */

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

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

/* #define DEBUG_ECHO */

static char
    GlblClientUnReadChar = 0;

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

static void SocketPrintError(char *Str);
static int SocReadObjPrefix(void);

/*****************************************************************************
*  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__ */
}

/*****************************************************************************
*  Set echo printing of read input.					     *
*****************************************************************************/
void SocClientEchoInput(int EchoInput)
{
    GlblEchoInput = EchoInput;
}

/*****************************************************************************
*  Non blocking read of a single char. Returns EOF if no data is found.	     *
*****************************************************************************/
int SocClientReadCharNonBlock(void)
{
    int c;
    static unsigned char Buffer[LINE_LEN_LONG];
    static int
	BufferSize = 0,
	BufferPtr = 0;	

    if (GlblClientUnReadChar != 0) {
	c = GlblClientUnReadChar;
	GlblClientUnReadChar = 0;
	return c;
    }
    else if (BufferPtr < BufferSize) {
	c = Buffer[BufferPtr++];
	return c;
    }

#ifdef OS2GCC
    DosRead(IritPipe, Buffer, LINE_LEN_LONG, (PULONG) &BufferSize);
#endif /* OS2GCC */
#if defined(__UNIX__) || defined(__WINNT__)
    BufferSize = recv(GlblCommuSoc, Buffer, LINE_LEN_LONG, 0);
#endif /* __UNIX__ || __WINNT__ */

    if (BufferSize > 0) {
	if (GlblEchoInput) {
	    int i;
	    unsigned char
		*p = Buffer;

	    if (GlblBinaryIPC) {
		for (i = 0; i < BufferSize; i++) {
		    if (i % 16 == 0)
			printf("\n%04x: ", i);
		    printf("%02x ", *p++);
		}
		printf("\n");
	    }
	    else
		for (i = 0; i < BufferSize; i++)
		    putc(*p++, stdout);
	}
	BufferPtr = 0;
	c = Buffer[BufferPtr++];
    }
    else
	c = EOF;

    return c;
}

/*****************************************************************************
*  Unget one char from client socket.					     *
*****************************************************************************/
void SocClientUnReadChar(char c)
{
    GlblClientUnReadChar = c;
}

/*****************************************************************************
* Get a single line for syncronization purposes that will prefix an object.  *
*   Returns TRUE if prefix found, FALSE otherwise.			     *
*****************************************************************************/
static int SocReadObjPrefix(void)
{
    if (GlblBinaryIPC) {
	int c;

	if ((c = SocClientReadCharNonBlock()) != EOF) {
	    SocClientUnReadChar(c);
	    return TRUE;
	}
    }
    else {
	if (SocClientReadCharNonBlock() == '[') {
	    SocClientUnReadChar('[');
	    return TRUE;
	}
    }

    return FALSE;
}

/*****************************************************************************
*  Non blocking read of a single line. Returns NULL if no line is available. *
*****************************************************************************/
char *SocClientReadLineNonBlock(void)
{
    static char Line[LINE_LEN_LONG];
    static int
	LineLen = 0;

    while (TRUE) {
	int c;

	if ((c = SocClientReadCharNonBlock()) != EOF) {
	    if (c == '\n' || c == '\r') {
		Line[LineLen++] = c;
		Line[LineLen] = 0;

		LineLen = 0;
		return Line;
	    }
	    else if (LineLen >= LINE_LEN_LONG - 1) {
		IritPrsrFatalError("Socket read line too long\n");
		exit(1);
	    }
	    else {
		Line[LineLen++] = c;
	    }
	}
	else
	    return NULL;
    }
}

/*****************************************************************************
*  Creates a client socket. Returns TRUE if succesful.			     *
*****************************************************************************/
int SocClientCreateSocket(void)
{
#if defined(__UNIX__) || defined(__WINNT__)
    int s;
    char *hostname, *portnum, hostnamestr[LINE_LEN];
    struct sockaddr_in sain;
    struct hostent *host;
    struct servent *serv;

    GlblCommuSoc = 0;

#ifdef __WINNT__
    {
	WSADATA WSAData;

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

    /* Get port address. */
    gethostname(hostnamestr, LINE_LEN);
    if ((hostname = getenv("IRIT_SERVER_HOST")) == NULL)
	hostname = hostnamestr;

    if ((host = gethostbyname(hostname)) == NULL) {
	SocketPrintError("iritclient: hostname unknown\n");
	return FALSE;
    }

    ZAP_MEM(&sain, sizeof(struct sockaddr_in));
    GEN_COPY(&sain.sin_addr, host -> h_addr_list[0], host -> h_length);
    sain.sin_family = AF_INET;

    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, make it nonblocking and connect. */
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	SocketPrintError("iritclient: socket");
	return FALSE;
    }

#ifdef __UNIX__
#ifdef __hpux
    if (fcntl(s, F_SETFL, O_NDELAY) < 0) {
#else
    if (fcntl(s, F_SETFL, FNDELAY) < 0) {
#endif /* __hpux */
	SocketPrintError("iritclient: fcntl");
	return FALSE;
    }
#endif /* __UNIX__ */

    while (connect(s, (struct sockaddr *) &sain,
		   sizeof(struct sockaddr_in)) < 0) {
#	ifdef __UNIX__
	    if (errno == EINPROGRESS)
		break;
#	endif /* __UNIX__ */
#	ifdef __WINNT__
	    if (WSAGetLastError() == WSAEINPROGRESS)
		break;
#	endif /* __WINNT__ */
	SocketPrintError("iritclient: connect");
	return FALSE;
    }

    GlblCommuSoc = s;
#endif /* __UNIX__ || __WINNT__ */

#ifdef OS2GCC
    ULONG rc,
	ulActionTaken, ulFileSize, ulFileAttribute, ulOpenFlag, ulOpenMode;
    
    DosWaitNPipe(IRIT_PIPE, NP_WAIT);
    ulFileSize = 0;
    ulFileAttribute = 0;
    ulOpenFlag = OPEN_ACTION_OPEN_IF_EXISTS;
    ulOpenMode = OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE;
    rc = DosOpen(IRIT_PIPE,
		 &IritPipe,
		 &ulActionTaken,
		 ulFileSize,
		 ulFileAttribute,
		 ulOpenFlag,
		 ulOpenMode,
		 NULL);

    if (rc != 0) {
    	DosBeep(1000, 100);
    	exit(1);
    }

    GlblCommuSoc = 1;
#endif /* OS2GCC */

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

    return TRUE;
}

/*****************************************************************************
*  Close a client socket.						     *
*****************************************************************************/
void SocClientCloseSocket(void)
{
    if (GlblCommuSoc > 0) {
#ifdef __UNIX__
	if (close(GlblCommuSoc) != 0)
	    SocketPrintError("iritclient: close");
#endif /* __UNIX__ */
#ifdef __WINNT__
	closesocket(GlblCommuSoc);
#endif /* __WINNT__ */
#ifdef OS2GCC
	DosClose(IritPipe);
#endif /* OS2GCC */

	GlblCommuSoc = 0;
    }
}

/*****************************************************************************
* Attempt to read (non blocking) from socket an object.			     *
* If read is successful the object is returned, otherwise NULL is returned.  *
*****************************************************************************/
IPObjectStruct *SocClientReadOneObject(void)
{
    char *ErrorMsg;
    IPObjectStruct *PObj;

    if (GlblCommuSoc > 0 && SocReadObjPrefix()) {
	IritPrsrSetReadOneObject(TRUE);

	IritPrsrReadSocket(TRUE);
	if (GlblBinaryIPC) {
	    PObj = IritPrsrGetBinObject(NULL);
	}
	else {
	    PObj = IritPrsrGetObjects(NULL);
	}
	IritPrsrReadSocket(FALSE);
    }
    else
	PObj = NULL;

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

    return PObj;
}
