#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "mosaic.h"
#include <X11/Intrinsic.h>
#include "cciServer.h"
#include "list.h"
#include "memStuffForPipSqueeks.h"

#include "HTFormat.h"

static Boolean cciAccepting = 0; 
static int listenPortNumber=0;
static XtInputId connectInputID;
static List listOfConnections;
static List listOfSendOutput;
static List listOfSendAnchorTo; /* client in list if should receive*/
static List listOfSendBrowserView;

extern XtAppContext app_context;
/***/ extern HTStream* CCIPresent();
extern char *machine_with_domain; /* host name */

struct Connection {
	XtInputId inputId;
	MCCIPort client;
	};

struct SendWhatToWhom {
	MCCIPort client;
	char	*contentType;
	};

cciStat *cciStatListFindEntry(MCCIPort findMe)
{
 /* this finds an entry in listOfSendAnchorTo based on MCCIPort client. */

  cciStat *current;
  char notDone = 1;
  current = (cciStat *) ListHead(listOfSendAnchorTo);
  while (notDone)
    {
      if ((current == NULL) || (current->client == findMe))
	notDone = 0;
      else
	current = (cciStat *) ListNext(listOfSendAnchorTo);
    }
  return current;
}
  

cciStat *cciStatListDeleteEntry(MCCIPort deleteMe)
{
  cciStat *current;
  
  if (current = cciStatListFindEntry(deleteMe))
    {
      ListDeleteEntry(listOfSendAnchorTo,current);
      return current;
    }
  else
    return NULL;
}

void cciStatPreventSendAnchor(MCCIPort client, char *url)
{
  /* this sets the flag that is checked for in cciSafeToSend, but
   * only if sendAnchor is on for this client. 
   */
  
  cciStat *current;

  if ((current = cciStatListFindEntry(client)) && (url != NULL))
    current->url = strdup(url);
}
  
int cciSafeToSend(cciStat *current, char *url)
{
/* ensures that Mosaic does not send an ANCHOR in response to a GET 
 * if SEND ANCHOR is turned on.
 *
 * INPUTS:
 * 		url	The url for which the flag is to be set.
 * RETURNS:
 * 		returns 0 if flag set for (client,url) pair (not safe to send)
 *                      1 if flag is not set for (client,url) pair
 *			(safe to send)
 */
  
  int rc = 1;

  if (current->url != NULL)
    {
      rc = strcmp(url,current->url);
      free(current->url);
      current->url = NULL;
    }
  return rc;
} 

void cciStatFree(cciStat *i)
{
  free(i);
}

cciStat *cciStatNew(MCCIPort client, int status)
{
  cciStat *new;

  new = (cciStat *) malloc( sizeof(cciStat) );
  if (new)
    {
      new->client = client;
      new->status = status;
      new->url = NULL;
    }
  return new;
}
  
void MoCCISendAnchor(client,sendIt)
/* set the state of sending anchors */
MCCIPort client;
int sendIt; /* 0, don't send, 1 send before browser gets document, */
	    /* 2 send after browser gets document */
{
  cciStat *statElt;

  statElt = cciStatListDeleteEntry(client);
  switch (sendIt)
    {
    case 0:
      if (statElt)
	cciStatFree(statElt);
      break;
    case 1: /* send before browser gets document */
      if (statElt)
	statElt->status = 1;
      else
	statElt = cciStatNew(client,1);
      if (statElt)
	ListAddEntry(listOfSendAnchorTo,statElt);
      break;
    case 2: /* send after browser gets document */
      if (statElt)
	statElt->status = 2;
      else
	statElt = cciStatNew(client,2);
      if (statElt)
	ListAddEntry(listOfSendAnchorTo,statElt);
      break;
    default:
      ;
    }
}

void MoCCISendAnchorToCCI(url, beforeAfter)
char *url;
int beforeAfter;
/***************************************************************
 * beforeAfter - 	0: send to all clients in list
 * 			1: send to all clients with status == 1;
 *			2: send to all clients with status == 2:
 ***************************************************************/
{
  cciStat *client;
  
  client = (cciStat *) ListHead(listOfSendAnchorTo);
  while(client) 
    {
      if (cciSafeToSend(client, url)) /* test flag */
	switch (beforeAfter)
	  {
	  case 0:
	    MCCISendAnchorHistory(client->client,url);
	    break;
	  case 1:
	    if (client->status == 1)
	      MCCISendAnchorHistory(client->client,url);
	    break;
	  case 2:
	    if (client->status == 2)
	      MCCISendAnchorHistory(client->client,url);
	  default:
	    ;
	  }
      client = (cciStat *) ListNext(listOfSendAnchorTo);
    }
}

MoCCISendBrowserView(client,on)
MCCIPort client;
int on;
{
	if (on) {
		ListAddEntry(listOfSendBrowserView,client);
		}
	else {
		ListDeleteEntry(listOfSendBrowserView,client);
		}
}


int MoCCIInitialize(portNumber)
int portNumber;
{
int retVal;
#if !defined(__FreeBSD__) && !defined(__NetBSD__)
extern char *sys_errlist[];
#endif
extern int errno;

	listOfConnections = ListCreate();
	listOfSendOutput = ListCreate();
	listOfSendAnchorTo = ListCreate();
	listOfSendBrowserView = ListCreate();

	retVal = MCCIServerInitialize(portNumber);
	if (retVal) {
		  /* Write port number to .mosaiccciport */
    		char *home = getenv ("HOME"), *fnam;
    		FILE *fp;
		
    		if (!home)
      			home = "/tmp";
		
    		fnam = (char *)malloc (strlen (home) + 32);
    		sprintf (fnam, "%s/.mosaiccciport", home);
		
    		fp = fopen (fnam, "w");
    		if (fp) {
			fprintf(fp,"%s:%d\n",machine_with_domain,portNumber);
        		fclose (fp);
			if (chmod(fnam, Rdata.cciPortFileMode) == -1) {
				fprintf(stderr,sys_errlist[errno]);
				mo_exit();
			}
		
    		free (fnam);
  		}
	}
	return(retVal);
}

int MoCCITerminateAllConnections()
{
struct Connection *con;
struct SendWhatToWhom *sendOutput;

	con = (struct Connection *) ListHead(listOfConnections);
	while(con) {
		XtRemoveInput(con->inputId);
		NetCloseConnection(con->client);
		MoCCISendAnchor(con->client,0);
		MoCCISendBrowserView(con->client,0);

		sendOutput=(struct SendWhatToWhom *)ListHead(listOfSendOutput);
		while(sendOutput){          /* remove sendOutputs */
			HTRemoveConversion(sendOutput->contentType,
				     "www/present",CCIPresent);
			ListDeleteEntry(listOfSendOutput,sendOutput);
			FREE(sendOutput->contentType);
			FREE(sendOutput);
		  
			sendOutput = (struct SendWhatToWhom *)
				ListCurrent(listOfSendOutput);
			}

		ListDeleteEntry(listOfConnections,con);
		con = (struct Connection *) ListHead(listOfConnections);
		}
}

int MoCCITerminateAConnection(client)
MCCIPort client;
{
struct Connection *con;
struct SendWhatToWhom *sendOutput;

	con = (struct Connection *) ListHead(listOfConnections);
	while(con) {
		if (con->client == client) {
			XtRemoveInput(con->inputId);
			MoCCISendAnchor(con->client,0);
			MoCCISendBrowserView(con->client,0);

			sendOutput=(struct SendWhatToWhom *)
			  ListHead(listOfSendOutput);
			while(sendOutput){          /* remove sendOutputs */
			  if(sendOutput->client == client){
			    HTRemoveConversion(sendOutput->contentType,
					       "www/present",CCIPresent);
			    ListDeleteEntry(listOfSendOutput,sendOutput);
			    FREE(sendOutput->contentType);
			    FREE(sendOutput);
			  }
			  sendOutput = (struct SendWhatToWhom *)
			    ListCurrent(listOfSendOutput);
			}
			    
			ListDeleteEntry(listOfConnections,con);
			con = (struct Connection *)
					ListCurrent(listOfConnections);
			}
		else {
			con = (struct Connection *) ListNext(listOfConnections);
			}
		}
}

MoCCIHandleInput(client,source)
MCCIPort client;
int source;
{
	if (MCCIIsThereInput(client)) {
		MCCIHandleInput(client);
		}
}

void MoCCINewConnection(app_context,source,inputID)
XtAppContext app_context;  
int *source;
XtInputId *inputID; 
{
MCCIPort client;
struct Connection *con;

#ifdef DEBUG
	printf("MoCCINewConnection(): I've been called\n");
#endif
	client = MCCICheckAndAcceptConnection();
	if (!client) {
		/* nothing here */
		return;
		}
#ifdef DEBUG
	printf("Mosaic: got a CCI connection\n");
#endif
/*	
	this determines wether there is one or many cci clients connecting 
	XtRemoveInput(*inputID);
*/

	MCCISendResponseLine(client,MCCIR_OK,"VERSION 01 X Mosaic 2.6");

	con = (struct Connection *) malloc(sizeof(struct Connection));
	con->client = client;
	con->inputId = XtAppAddInput(app_context,
		MCCIGetSocketDescriptor(client),
		(XtPointer)  XtInputReadMask,
		(XtInputCallbackProc) MoCCIHandleInput, (XtPointer) client);

	ListAddEntry(listOfConnections,con);

}

static XmxCallback (MoCCIWindowCallBack)
{
Boolean newState;
mo_window *win = mo_fetch_window_by_id (XmxExtractUniqid ((int)client_data));
int newPort;
char *portString;
char buff[256];

   newState = cciAccepting;
   switch (XmxExtractToken ((int)client_data)) {
	case 0: /*ok*/
  		newState = XmToggleButtonGetState(win->cci_accept_toggle);
		XtUnmanageChild(win->cci_win);
		break;
	case 1: /*Cancel*/
		if (cciAccepting != XmToggleButtonGetState
						(win->cci_accept_toggle)) {
			/* reset toggle state */
			XmxSetToggleButton(win->cci_accept_toggle, 
							cciAccepting);
			XmxSetToggleButton(win->cci_off_toggle, 
							!cciAccepting);
			}
		XtUnmanageChild(win->cci_win);
		break;
	case 2: /*Help*/
		mo_open_another_window (win,
		        mo_assemble_help_url("docview-menubar-file.html#open"),
			NULL, NULL);

		break;
	}
   if (newState != cciAccepting) {
	/* toggle state has changed */
	cciAccepting = newState;
	if (cciAccepting) {
		newPort = 0;
	        portString= XmxTextGetString (win->cci_win_text);
		if (portString) {
			newPort = atoi(portString);
			}
		if ((newPort < 1024) || (newPort > 65535)) {
                        XmxMakeErrorDialog (win->cci_win,
                                "CCI port address must be between 1024 and 65535",
                                "CCI port address error");
			XtManageChild (Xmx_w);
			cciAccepting = False;
			listenPortNumber = 0;
			XmxSetToggleButton(win->cci_accept_toggle, 
							cciAccepting);
			XmxSetToggleButton(win->cci_off_toggle, 
							!cciAccepting);
			return;
			}

		if (MoCCIInitialize(newPort)) {
			listenPortNumber = newPort;
			connectInputID = XtAppAddInput(app_context,
                        	MCCIReturnListenPortSocketDescriptor(),
	                        (XtPointer) XtInputReadMask,
				(XtInputCallbackProc) MoCCINewConnection,
				app_context);
			sprintf(buff,"CCI Now listening on port %d",newPort);
			XmxMakeInfoDialog(win->cci_win,buff,"CCI port status");
			XtManageChild(Xmx_w);
			}
		else {
			cciAccepting = False;
			listenPortNumber = 0;
			XmxSetToggleButton(win->cci_accept_toggle, 
							cciAccepting);
			XmxSetToggleButton(win->cci_off_toggle, 
							!cciAccepting);
			/* Can't accept on this port...*/
                        XmxMakeErrorDialog (win->cci_win,
                                "CCI Can't accept requests on this port. May be in use already",
                                "CCI port address error");
			XtManageChild(Xmx_w);
			}
		}
	else {
		XtRemoveInput(connectInputID); /* stop accepting connections*/
		MCCICloseAcceptPort(); /* kill accept port */

		/* terminate existing connections */
		MoCCITerminateAllConnections();
		listenPortNumber = 0;
		sprintf(buff,"CCI Is no longer listening");
		XmxMakeInfoDialog(win->cci_win, buff,"CCI port status");
		XtManageChild(Xmx_w);
		}
	}
}

mo_status MoDisplayCCIWindow(win)
mo_window *win;
{
Widget dialogFrame;
Widget dialogSeparator;
/*Widget buttonsForm;*/
Widget cciForm,label;
Widget toggleBox;
Widget buttonBox;

	if (!win->cci_win) {
		XmxSetUniqid (win->id);
		win->cci_win = XmxMakeFormDialog(win->base, 
				    "NCSA Mosaic: Common Client Interface ");
		dialogFrame = XmxMakeFrame(win->cci_win, XmxShadowOut);
		XmxSetConstraints
		        (dialogFrame, XmATTACH_FORM, XmATTACH_FORM,
		         XmATTACH_FORM, XmATTACH_FORM, NULL, NULL, NULL, NULL);
		/* Main form. */
		cciForm = XmxMakeForm(dialogFrame);

		label = XmxMakeLabel(cciForm, "CCI Port Address: ");
#ifdef L10N
#ifdef MOTIF_I18N
		if(Rdata.half_sized_textfield)
		XmxSetArg(XmNcolumns, 13);
		else
#endif /* MOTIF_I18N */
#endif /* L10N */
		XmxSetArg(XmNcolumns, 25);
			win->cci_win_text= XmxMakeText (cciForm);
			XmxAddCallbackToText (win->cci_win_text, 
			MoCCIWindowCallBack, 0);

		toggleBox = XmxMakeRadioBox(cciForm);

		win->cci_accept_toggle = XmxMakeToggleButton
			(toggleBox, "Accept requests", NULL, 0);


		win->cci_off_toggle = XmxMakeToggleButton
			(toggleBox, "Interface off", NULL, 0);

		dialogSeparator= XmxMakeHorizontalSeparator(cciForm);
		buttonBox = XmxMakeFormAndThreeButtons(cciForm,
				MoCCIWindowCallBack,
				"Ok","Dismiss","Help...",0,1,2);

		XmxSetOffsets (label, 13, 0, 10, 0);
		XmxSetConstraints
			(label, XmATTACH_FORM, XmATTACH_NONE, 
			XmATTACH_FORM, XmATTACH_NONE, NULL, NULL, NULL, NULL);

		XmxSetOffsets (win->cci_win_text, 10, 0, 5, 8);
		XmxSetConstraints
		        (win->cci_win_text, XmATTACH_FORM, XmATTACH_NONE, 
			XmATTACH_WIDGET,
			XmATTACH_FORM, NULL, NULL, label, NULL);

		XmxSetConstraints (toggleBox, XmATTACH_WIDGET, 
			XmATTACH_NONE, XmATTACH_WIDGET, XmATTACH_NONE,
			win->cci_win_text, NULL, label, NULL);
		XmxSetOffsets (toggleBox, 8, 0, 2, 0);

		XmxSetConstraints (dialogSeparator, XmATTACH_WIDGET, 
			XmATTACH_NONE, XmATTACH_FORM, XmATTACH_FORM,
			toggleBox, NULL, NULL, NULL);

		XmxSetConstraints (buttonBox, XmATTACH_WIDGET, 
			XmATTACH_FORM, XmATTACH_FORM, XmATTACH_FORM,
			dialogSeparator, NULL, NULL, NULL);
		}

	if (cciAccepting) {
		char buff[10];
		sprintf(buff,"%d",listenPortNumber);
		XmxTextSetString(win->cci_win_text,buff);
		}
	else {
		XmxTextSetString(win->cci_win_text,"");
		}
	XmxSetToggleButton(win->cci_accept_toggle, cciAccepting);
	XmxSetToggleButton(win->cci_off_toggle, !cciAccepting);

	XmxManageRemanage (win->cci_win);
	return mo_succeed;

}

void MoCCISendOutputToClient(contentType,fileName)
char *contentType; /* string name of data type */
char *fileName; /* data stored here */
{
struct SendWhatToWhom *sendOutput;


	/* send data to all clients that registered for this content type*/
	sendOutput=(struct SendWhatToWhom *)ListHead(listOfSendOutput);
	while(sendOutput){
		if (!strcmp(sendOutput->contentType,contentType)) {
#ifdef DEBUG
			printf("Sending output through cci of type %s\n",
					sendOutput->contentType);
#endif
			/* prep data for sending here */

			/* send it back to client */
			MCCISendOutputFile(sendOutput->client,
					contentType,fileName);
			}
		sendOutput=(struct SendWhatToWhom *)ListNext(listOfSendOutput);
		}
	
	return;

}

/* this function is a callback from the libwww2 Converter */


void MoCCISendOutput(client,sendIt,contentType)
MCCIPort client;
Boolean sendIt;
char *contentType;
{
struct SendWhatToWhom *sendOutput;

	if (sendIt) {
		if (!(sendOutput = (struct SendWhatToWhom *) 
		    MALLOC(sizeof(struct SendWhatToWhom)))) {
			return /*mem error*/;
			}
		sendOutput->client = client;
		sendOutput->contentType = strdup(contentType);
		ListAddEntry(listOfSendOutput,sendOutput);

		/* set up call back for this content Type */
		HTSetConversion(contentType,"www/present",CCIPresent,
				1.0, 0.0, 0.0);
/*If we use this, out put a message like....sent to cci
*/

/**		HTSetPresentation(contentType,MoCCISendOutputCB,
					1.0, 0.0, 0.0);
*/
		}
	else {
		sendOutput=(struct SendWhatToWhom *)ListHead(listOfSendOutput);
		while(sendOutput){
			if ((sendOutput->client == client) &&
			    (!strcmp(sendOutput->contentType,contentType))) {
				HTRemoveConversion(contentType,
						"www/present",CCIPresent);
				ListDeleteEntry(listOfSendOutput,sendOutput);
				FREE(sendOutput->contentType);
				FREE(sendOutput);

				sendOutput = (struct SendWhatToWhom *)
						ListCurrent(listOfSendOutput);
				}
			else {
				sendOutput = (struct SendWhatToWhom *)
						ListNext(listOfSendOutput);
				}
			}
		
		}
}

void MoCCIStartListening(w,port)
Widget w;
int port;
{
/*int listenPort;*/

        if (MoCCIInitialize(port)) {
		connectInputID = XtAppAddInput(app_context,
                        MCCIReturnListenPortSocketDescriptor(),
                        (XtPointer) XtInputReadMask,
                        (XtInputCallbackProc) MoCCINewConnection,
			app_context);
		cciAccepting = True; 
		listenPortNumber = port;
		XmxMakeInfoDialog(w,"Mosaic CCI port is listening",
				"CCI port status");
		XtManageChild(Xmx_w);
                }
}



void MoCCISendBrowserViewOutput(url, contentType, data, dataLength)
/* Handle SendBrowserViewOutput if needed */
/* send BrowserView data to all the clients that have requested it */
char *url;
char *contentType;
char *data;
int  dataLength;
{
MCCIPort client;

	if (dataLength == 0) {
		return;
		}
	if (!data) {
		return;
		}
	if ((!url) ||  (!strlen(url))) {
		return;
		}
	if ((!contentType) ||  (!strlen(contentType))) {
		contentType = "unknown";
		}

	client = (MCCIPort) ListHead(listOfSendBrowserView);
	while (client) {
		MCCISendBrowserViewOutput(client,url,contentType,
					data,dataLength);
		client = (MCCIPort) ListNext(listOfSendBrowserView);
		}
}

int MoCCISendBrowserViewFile(url, contentType, filename)
char *url;
char *contentType;
char *filename;
{
struct stat fileInfo;
FILE *fp;
char *data;

	if (!ListHead(listOfSendBrowserView)) {
		return(MCCI_OK);
		}
	if ((!filename) || (!strlen(filename))) {
		return(MCCI_OK);
		}
	if ((!url) || (!strlen(url))) {
		return(MCCI_OK);
		}
	if ((!contentType) || (!strlen(contentType))) {
		contentType = "unknown";
		}



        if (stat(filename,&fileInfo)) { /* get the length of the file */
                return(MCCI_FAIL);
                }
	

	if (!(fp = fopen(filename,"r"))) {
                return(MCCI_FAIL);
		}
	if (!(data = (char *) malloc(fileInfo.st_size))) {
		fclose(fp);
                return(MCCI_OUTOFMEMORY);
		}
	if (fileInfo.st_size != fread(data,sizeof(char),fileInfo.st_size,fp)){
		fclose(fp);
		free(data);
                return(MCCI_FAIL);
		}
	MoCCISendBrowserViewOutput(url, contentType, data, fileInfo.st_size);

	free(data);
	fclose(fp);
	return(MCCI_OK);
}


int MoCCIMaxNumberOfConnectionsAllowed()
/* return number of connections allowed.  This is set in the X resources.
 * if it's zero, then treat it as unlimited.
 */
{
	return(Rdata.max_num_of_cci_connections);
}

int MoCCICurrentNumberOfConnections()
{
	return(ListCount(listOfConnections));
}
