/* 
** openurl.library - universal URL display and browser launcher library
** Written by Troels Walsted Hansen <troels@thule.no>
** Placed in the public domain.
**
** Module with utility functions used by the other library functions.
*/

#include "library_common.h"
#include "library_api.h"
#include "library_prefs.h"
#include "library_util.h"
#include "handler.h"

/**************************************************************************
*
* Definitions.
*
*/

#define FINDPORT_NUM  100  /* how many FindPort() to do while waiting */
#define FINDPORT_TIME  10  /* how many seconds to spread those FindPort() over */

struct PlaceHolder
{
	char ph_Char;
	char *ph_String;
};

#define PH_COUNT_BROWSER	2
#define PH_COUNT_MAILER		6

/**************************************************************************
*
* Prototypes.
*
*/

static STRPTR ExpandPlaceHolders(STRPTR template, struct PlaceHolder *ph, int num);
static BOOL WriteToFile(STRPTR filename, STRPTR str);
static STRPTR WaitForRexxPort(STRPTR port);
static STRPTR FindRexxPort(struct List *list, STRPTR name);
static BOOL SendRexxMsg(struct MsgPort *replyport, STRPTR rxport, STRPTR rxcmd);

/**************************************************************************
*
* Externally visible functions.
*
*/

BOOL SendToBrowser(STRPTR url, struct List *portlist, struct MsgPort *mp, 
                   BOOL Show, BOOL ToFront, BOOL NewWindow, BOOL Launch, STRPTR PubScreenName)
{
	BOOL retval = FALSE;
	STRPTR cmd = NULL;
	struct PlaceHolder ph[PH_COUNT_BROWSER];
	struct URL_BrowserNode *bn;


/* DEBUG 
		kprintf("SendToBrowser()\n");
 DEBUG */

	/* set up the placeholder mapping */

	ph[0].ph_Char = 'u'; ph[0].ph_String = url;
	ph[1].ph_Char = 'p'; ph[1].ph_String = PubScreenName ? PubScreenName : "Workbench";

	/* try to find one of the browsers in the list */

	for(bn = (struct URL_BrowserNode *)Prefs->up_BrowserList.mlh_Head;
	    bn->ubn_Node.mln_Succ;
	    bn = (struct URL_BrowserNode *)bn->ubn_Node.mln_Succ)
	{
		STRPTR port = FindRexxPort(portlist, bn->ubn_Port);
		if(port)
		{
			/* send uniconify msg */

			if(Show && *bn->ubn_ShowCmd)
				SendRexxMsg(mp, port, bn->ubn_ShowCmd);

			/* send screentofront command */

			if(ToFront && *bn->ubn_ToFrontCmd)
				SendRexxMsg(mp, port, bn->ubn_ToFrontCmd);

			/* try sending openurl msg */

			if(!(cmd = ExpandPlaceHolders(NewWindow ? bn->ubn_OpenURLWCmd : bn->ubn_OpenURLCmd, ph, PH_COUNT_BROWSER)))
				goto done;

			if(!(retval = SendRexxMsg(mp, port, cmd)))
			{
				FreeVec(cmd);
				cmd = NULL;
			}
			else goto done;
		}
	}

	/* no running browser, launch a new one */

	if(!Launch) goto done;

	for(bn = (struct URL_BrowserNode *)Prefs->up_BrowserList.mlh_Head;
	    bn->ubn_Node.mln_Succ;
	    bn = (struct URL_BrowserNode *)bn->ubn_Node.mln_Succ)
	{
		BOOL startonly;
		STRPTR filepart;
		char c = '\0';
		BPTR lock;
		LONG error;

		if(!*bn->ubn_Path) continue;

		/* compose commandline */

		if(strstr(bn->ubn_Path, "%u"))
			startonly = TRUE;
		else
			startonly = FALSE;

		if(!(cmd = ExpandPlaceHolders(bn->ubn_Path, ph, PH_COUNT_BROWSER)))
			goto done;

		filepart = FilePart(bn->ubn_Path);

		if(filepart)
		{
			c         = *filepart;
			*filepart = '\0';
		}

		lock = Lock(bn->ubn_Path, ACCESS_READ);

		if(filepart) *filepart = c;

		/* start the browser */

		error = SystemTags(cmd, SYS_Asynch,    TRUE,
		                        SYS_Input,     Open("NIL:", MODE_NEWFILE),
		                        SYS_Output,    NULL,
		                 lock ? NP_CurrentDir : TAG_IGNORE, lock,
		                        TAG_DONE);
/* DEBUG 
		kprintf("cmd = [%s]\n",cmd);
 DEBUG */



		FreeVec(cmd);
		cmd = NULL;
		
		if(error)
		{
			if(lock) UnLock(lock);
			continue;
		}

		if(!startonly)
		{
			STRPTR rxport;

			/* send urlopen command */

			if(!(cmd = ExpandPlaceHolders(bn->ubn_OpenURLCmd, ph, PH_COUNT_BROWSER)))
				goto done;

			/* wait for the port to appear */

			if((rxport = WaitForRexxPort(bn->ubn_Port)))
				retval = SendRexxMsg(mp, rxport, cmd);

			break;
		}
		else
		{
			retval = TRUE;
			break;
		}
	}

done:
	if(cmd) FreeVec(cmd);
	return(retval);
}

/**************************************************************************/

BOOL SendToMailer(STRPTR url, struct List *portlist, struct MsgPort *mp, 
                  BOOL Show, BOOL ToFront, BOOL Launch, STRPTR PubScreenName)
{
	BOOL retval = FALSE, written = FALSE;
	struct PlaceHolder ph[PH_COUNT_MAILER];
	STRPTR start, end, data, address = NULL, subject = NULL, body = NULL, cmd = NULL;
	char filename[32];
	struct URL_MailerNode *mn;
	STRPTR *tag;
	UWORD offset, len;

	/* this would be better prepared somewhere statically */

	WORD trans[256];
	for(len=0;len<256;len++) trans[len]=-1;
    trans['0']=0;
	trans['1']=1;
	trans['2']=2;
	trans['3']=3;
	trans['4']=4;
	trans['5']=5;
	trans['6']=6;
	trans['7']=7;
	trans['8']=8;
	trans['9']=9;
	trans['A']=trans['a']=10;
	trans['B']=trans['b']=11;
	trans['C']=trans['c']=12;
	trans['D']=trans['d']=13;
	trans['E']=trans['e']=14;
	trans['F']=trans['f']=15;

	/* parse the URL "mailto:user@host.domain?subject=Subject&body=Body" */

	start = url;
	while(start)
	{
		tag = NULL;
		end = NULL;
		offset = 1;

		if(!strnicmp(start, "mailto:", 7))
		{
			tag = &address;
			offset = 7;
			end = strchr(start + offset, '?');
		}
		else if((!strnicmp(start, "?subject=", 9)) || (!strnicmp(start, "&subject=", 9)))
		{
			tag = &subject;
			offset = 9;
			end = strchr(start + offset, '&');
		}
		else if((!strnicmp(start, "?body=", 6)) || (!strnicmp(start, "&body=", 6)))
		{
			tag = &body;
			offset = 6;
			end = strchr(start + offset, '&');
		}

		/* if we found some data && we even found it the first time ! */
		if(tag && !*tag)
		{
			data=start+offset;

			if(end) len=end-data;
			else len=strlen(data);

			if(!(*tag = AllocVec(len+1, MEMF_ANY)))
				goto done;

			strncpy(*tag, data, len);
			*((*tag)+len)='\0';

			/* decode %XX sequences in urls */
			data=*tag;
			while(data)
			{
				if((data=strchr(data,'%')) && (trans[data[1]]!=-1) && (trans[data[2]]!=-1))
				{
					*data=(trans[data[1]]<<4)|trans[data[2]];
					data++;
					memmove(data,data+2,strlen(data+2)+1);
				}
			}
		}

		start = end;
	}

	if(body)
		SPrintf(filename, "T:OpenURL-MailBody.%08lx", FindTask(NULL));
	else
	{
		written = TRUE;
		strcpy(filename, "NIL:");
	}

	/* set up the placeholder mapping */

	ph[0].ph_Char = 'a'; ph[0].ph_String = address ? address : (STRPTR)"";
	ph[1].ph_Char = 's'; ph[1].ph_String = subject ? subject : url;
	ph[2].ph_Char = 'b'; ph[2].ph_String = body ? body : (STRPTR)"";
	ph[3].ph_Char = 'f'; ph[3].ph_String = filename;
	ph[4].ph_Char = 'u'; ph[4].ph_String = url;
	ph[5].ph_Char = 'p'; ph[5].ph_String = PubScreenName ? PubScreenName : "Workbench";

	/* try to find one of the mailers in the list */

	for(mn = (struct URL_MailerNode *)Prefs->up_MailerList.mlh_Head;
	    mn->umn_Node.mln_Succ;
	    mn = (struct URL_MailerNode *)mn->umn_Node.mln_Succ)
	{
		STRPTR rxport = FindRexxPort(portlist, mn->umn_Port);

		if(rxport)
		{
			/* send uniconify msg */

			if(Show && *mn->umn_ShowCmd)
				SendRexxMsg(mp, rxport, mn->umn_ShowCmd);

			/* send screentofront command */

			if(ToFront && *mn->umn_ToFrontCmd)
				SendRexxMsg(mp, rxport, mn->umn_ToFrontCmd);

			/* write to temp file */

			if(!written && strstr(mn->umn_WriteMailCmd, "%f"))
				written = WriteToFile(filename, body);

			/* try sending writemail msg */

			if(!(cmd = ExpandPlaceHolders(mn->umn_WriteMailCmd, ph, PH_COUNT_MAILER)))
				goto done;

/* DEBUG
		kprintf("cmd = [%s]\n",cmd);
DEBUG */

			/* now split each message at the ';' and send fragments */
			start = end = cmd;
			while(*start)
			{
				while((*end) && (*end!=';'))
				{
					end++;
					/* skip data, which is enclosed in "" */
					if(*end=='"')
					{
						end++;
						while((*end) && (*end!='"')) end++;
						if(*end=='"') end++;
					}
				}
				/* are there more commands */
				if(*end==';')
				{
					*end='\0';
					end++;
				}
/* DEBUG
				kprintf("    = [%s]\n",start);
DEBUG */
				if(!(retval = SendRexxMsg(mp, rxport, start)))
				{
					/* send failed, try next mailer */
					FreeVec(cmd);
					start = cmd = NULL;
				}
				else start=end;
			}
			/* cmd processed succesfully, return */
			if(cmd) goto done;
		}
	}

	/* no running browser, launch a new one */

	if(!Launch) goto done;

	for(mn = (struct URL_MailerNode *)Prefs->up_MailerList.mlh_Head;
	    mn->umn_Node.mln_Succ;
	    mn = (struct URL_MailerNode *)mn->umn_Node.mln_Succ)
	{
		BOOL startonly;
		STRPTR filepart;
		char c = '\0';
		BPTR lock;
		LONG error;

		if(!*mn->umn_Path) continue;

		/* compose commandline */

		if(strstr(mn->umn_Path, "%a"))
			startonly = TRUE;
		else
			startonly = FALSE;

		if(!written && strstr(mn->umn_Path, "%f"))
			written = WriteToFile(filename, body);

		if(!(cmd = ExpandPlaceHolders(mn->umn_Path, ph, PH_COUNT_MAILER)))
			goto done;
		
		filepart = FilePart(mn->umn_Path);

		if(filepart)
		{
			c         = *filepart;
			*filepart = '\0';
		}

		lock = Lock(mn->umn_Path, ACCESS_READ);

		if(filepart) *filepart = c;

		/* start the mailer */

		error = SystemTags(cmd, SYS_Asynch,    TRUE,
		                        SYS_Input,     Open("NIL:", MODE_NEWFILE),
		                        SYS_Output,    NULL,
		                 lock ? NP_CurrentDir : TAG_IGNORE, lock,
		                        TAG_DONE);

		FreeVec(cmd);
		cmd = NULL;
		
		if(error)
		{
			if(lock) UnLock(lock);
			continue;
		}

		if(!startonly)
		{
			STRPTR rxport;

			/* send write mail command */

			if(!written && strstr(mn->umn_WriteMailCmd, "%f"))
				written = WriteToFile(filename, body);

			if(!(cmd = ExpandPlaceHolders(mn->umn_WriteMailCmd, ph, PH_COUNT_MAILER)))
				goto done;

			/* wait for the port to appear */

			if((rxport = WaitForRexxPort(mn->umn_Port)))
			{
				start = end = cmd;
				while(*start)
				{
					while((*end) && (*end!=';'))
					{
						end++;
						/* skip data, which is enclosed in "" */
						if(*end=='"')
						{
							end++;
							while((*end) && (*end!='"')) end++;
							if(*end=='"') end++;
						}
					}
					/* are there more commands */
					if(*end==';')
					{
						*end='\0';
						end++;
					}
					if(!(retval = SendRexxMsg(mp, rxport, start)))
					{
						/* send failed, try next mailer */
						FreeVec(cmd);
						start = cmd = NULL;
					}
					else start=end;
				}
			}
			break;
		}
		else
		{
			retval = TRUE;
			break;
		}
	}

done:
	if(cmd)     FreeVec(cmd);
	if(body)    FreeVec(body);
	if(subject) FreeVec(subject);
	if(address) FreeVec(address);

	return(retval);
}

/**************************************************************************/

BOOL CopyList(struct List *dst, struct List *src, ULONG size)
{
	struct Node *n, *new;

	/* copy src list into dst, and return success */

	for(n = src->lh_Head; n->ln_Succ; n = n->ln_Succ)
	{
		if(!(new = AllocMem(size, MEMF_ANY)))
		{
			FreeList(dst, size);
			return(FALSE);
		}

		memcpy(new, n, size);
		AddTail(dst, new);
	}

	return(TRUE);
}

/**************************************************************************/

VOID FreeList(struct List *list, ULONG size)
{
	struct Node *n;

	while((n = RemHead(list)))
		FreeMem(n, size);
}

/**************************************************************************/

BOOL isdigits(STRPTR str)
{
	for(;;)
	{
		if(!*str)
			return(TRUE);
		else if(!isdigit(*str))
			return(FALSE);

		str++;
	}
}

/**************************************************************************/

VOID SPrintf(STRPTR to, STRPTR fmt, ...)
{
	static ULONG fmtfunc = 0x16C04E75;
 	RawDoFmt(fmt, &fmt + 1, (APTR)&fmtfunc, to);
}

/**************************************************************************
*
* Local functions.
*
*/

static STRPTR ExpandPlaceHolders(STRPTR template, struct PlaceHolder *ph, int num)
{
	STRPTR p, result;
	int i, length = 0;

	/* calculate length of result string */

	for(p = template; *p; p++)
	{
		for(i = 0; i < num; i++)
		{
			if((*p == '%') && (*(p + 1)== ph[i].ph_Char))
				length += strlen(ph[i].ph_String);
		}
		length++;
	}

	/* allocate result string */

	if(!(result = AllocVec(length + 1, MEMF_PUBLIC)))
		return(NULL);

	/* perform substitution */

	for(p = result; *template; template++)
	{
		for(i = 0; i < num; i++)
			if((*template == '%') && (*(template + 1)== ph[i].ph_Char))
				break;

		if(i < num)
		{
			strcpy(p, ph[i].ph_String);
			p += strlen(ph[i].ph_String);
			template++;
			continue;
		}

		*p++ = *template;
	}

	*p = '\0';

	return(result);
}

/**************************************************************************/

static BOOL WriteToFile(STRPTR filename, STRPTR str)
{
	BOOL retval = FALSE;
	BPTR fh;
	LONG len = strlen(str);

	if((fh = Open(filename, MODE_NEWFILE)))
	{
		if(Write(fh, str, len) == len)
			retval = TRUE;

		Close(fh);
	}

	return(retval);
}

/**************************************************************************/

static STRPTR WaitForRexxPort(STRPTR port)
{
	LONG i, delay;

	/* (busy) wait for the port to appear */

	delay = (FINDPORT_TIME * TICKS_PER_SECOND) / FINDPORT_NUM;

	for(i = 0; i < FINDPORT_NUM; i++)
	{
		STRPTR rxport;

		Forbid();
		rxport = FindRexxPort(&SysBase->PortList, port);
		Permit();

		if(rxport) return(rxport);

		if(SetSignal(0, 0) & SIGBREAKF_CTRL_C)
			return(NULL);
	
		Delay(delay);
	}

	return(NULL);
}

/**************************************************************************/

static STRPTR FindRexxPort(struct List *list, STRPTR name)
{
	struct Node *n;
	ULONG len;

	/* find a rexx port, allowing a .<number> extension */

	len = strlen(name);

	for(n = list->lh_Head; n->ln_Succ; n = n->ln_Succ)
	{
		if(n->ln_Name && !strncmp(n->ln_Name, name, len) &&
		  (n->ln_Name[len] == '\0' ||
		  (n->ln_Name[len] == '.' && isdigits(&n->ln_Name[len+1]))))
		{
			return(n->ln_Name);
		}
	}

	return(NULL);
}

/**************************************************************************/

static BOOL SendRexxMsg(struct MsgPort *replyport, STRPTR rxport, STRPTR rxcmd)
{
	struct HandlerMsg hm = {0};
	hm.hm_Msg.mn_ReplyPort = replyport;
	hm.hm_Msg.mn_Length    = sizeof(struct HandlerMsg);
	hm.hm_Type             = HMT_AREXX;
	hm.hm_ARexxPort        = rxport;
	hm.hm_ARexxCmd         = rxcmd;

	/* check that the handler is still there */

	if(!AttemptSemaphore(&HandlerSemaphore))
	{
		/* yup, still there */

		PutMsg(HandlerMsgPort, (struct Message *)&hm);
		WaitPort(replyport);
		GetMsg(replyport);
		return(hm.hm_Success);
	}
	else
	{
		/* eek, gone */

		ReleaseSemaphore(&HandlerSemaphore);
		return(FALSE);
	}
}
