/* 
** openurl.library - universal URL display and browser launcher library
** Written by Troels Walsted Hansen <troels@stud.cs.uit.no>
** Placed in the public domain.
**
** This is the one and only source file for the library.
*/

#include <proto/exec.h>

#include <clib/dos_protos.h>
#include <clib/utility_protos.h>
#include <clib/iffparse_protos.h>
#include <clib/rexxsyslib_protos.h>
#include <clib/openurl_protos.h>

#include <pragmas/dos_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/iffparse_pragmas.h>
#include <pragmas/rexxsyslib_pragmas.h>
#include <pragmas/openurl_pragmas.h>

#include <exec/memory.h>
#include <dos/dostags.h>
#include <libraries/iffparse.h>
#include <prefs/prefhdr.h>
#include <rexx/rxslib.h>
#include <rexx/errors.h>
#include <libraries/openurl.h>

#include <string.h>

extern void kprintf(const char *, ...);

#define REG(x) register __ ## x
#define LIB __saveds __asm

/**************************************************************************
*
* Prototypes for library and Local, non-library functions.
*
*/

LIB BOOL LIB_URL_OpenA(REG(a0) STRPTR url, REG(a1) struct TagItem *tags);
LIB struct URL_Prefs *LIB_URL_GetPrefs(VOID);
LIB VOID LIB_URL_FreePrefs(REG(a0) struct URL_Prefs *up);
LIB BOOL LIB_URL_SetPrefs(REG(a0) struct URL_Prefs *up, REG(d0) BOOL permanent);

static BOOL DoRexxMsg(struct MsgPort *replyport, STRPTR port, struct RexxMsg *rxmsg, STRPTR rxcmd);
static struct MsgPort *FindRexxPort(STRPTR port);

static struct URL_Prefs *CopyPrefs(struct URL_Prefs *old_p);
static BOOL SavePrefs(STRPTR filename, struct URL_Prefs *up);
static BOOL LoadPrefs(STRPTR filename);
static BOOL SetDefaultPrefs(VOID);

static VOID SPrintf(STRPTR to, STRPTR fmt, ...);

/**************************************************************************
*
* Definitions and global variables.
*
*/

#define PORTNUM_MAX     6  /* look for PORT.0 to PORT.<max-1> */
#define FINDPORT_NUM  100  /* how many FindPort() to do while waiting */
#define FINDPORT_TIME  10  /* how many seconds to spread those FindPort() over */

#define PREFS_VERSION ((UBYTE)1)

#define PREFS_NAME_USE  "ENV:OpenURL.prefs"
#define PREFS_NAME_SAVE "ENVARC:OpenURL.prefs"

#define ID_BRWS MAKE_ID('B','R','W','S')

struct DosLibrary *DOSBase;
struct Library *UtilityBase;
struct Library *IFFParseBase;
struct RxsLib *RexxSysBase;
struct URL_Prefs *Prefs;
struct SignalSemaphore PrefsSemaphore;

/**************************************************************************
*
* SAS/C library init
*
*/

LIB int __UserLibInit(REG(a6) struct Library *OpenURLBase)
{
	InitSemaphore(&PrefsSemaphore);

	if((DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36)))
	{
		if((UtilityBase = OpenLibrary("utility.library", 36)))
		{
			if((IFFParseBase = OpenLibrary("iffparse.library", 36)))
			{
				if((RexxSysBase = (struct RxsLib *)OpenLibrary("rexxsyslib.library", 33)))
				{
					if(LoadPrefs(PREFS_NAME_USE) || SetDefaultPrefs())
						return(0);

					CloseLibrary((struct Library *)RexxSysBase);
				}
				CloseLibrary(IFFParseBase);
			}
			CloseLibrary(UtilityBase);
		}
		CloseLibrary((struct Library *)DOSBase);
	}

	return(1);
}

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

LIB void __UserLibCleanup(REG(a6) struct Library *OpenURLBase)
{
	if(Prefs) LIB_URL_FreePrefs(Prefs);
	CloseLibrary((struct Library *)RexxSysBase);
	CloseLibrary(IFFParseBase);
	CloseLibrary(UtilityBase);
	CloseLibrary((struct Library *)DOSBase);
}

/**************************************************************************
*
* Library functions
*
*/

LIB BOOL LIB_URL_OpenA(REG(a0) STRPTR url, REG(a1) struct TagItem *tags)
{
	BOOL retval = FALSE;
	struct MsgPort *mp;
	struct RexxMsg *rxmsg = NULL;
	STRPTR rxcmd = NULL;

	BOOL Show, ToFront, NewWindow, Launch;
	struct URL_BrowserNode *bn;

	ObtainSemaphore(&PrefsSemaphore);

	/* some arexx initialization */

	if(!(mp = CreatePort(NULL, 0)))
		goto done;

	if(!(rxmsg = CreateRexxMsg(mp, NULL, NULL)))
		goto done;

	rxmsg->rm_Action = RXCOMM | RXFF_STRING | RXFF_NOIO;

	if(!(rxcmd = AllocVec(REXX_CMD_LEN + strlen(url) + 1, MEMF_PUBLIC)))
		goto done;

	/* parse the arguments */

	Show      = GetTagData(URL_Show,         TRUE,  tags);
	ToFront   = GetTagData(URL_BringToFront, TRUE,  tags);
	NewWindow = GetTagData(URL_NewWindow,    FALSE, tags);
	Launch    = GetTagData(URL_Launch,       TRUE,  tags);

	/* open the URL */

	for(bn = (struct URL_BrowserNode *)Prefs->up_BrowserList.mlh_Head;
	    bn->ubn_Node.mln_Succ;
	    bn = (struct URL_BrowserNode *)bn->ubn_Node.mln_Succ)
	{
		/* send uniconify msg */

		if(Show && *bn->ubn_ShowCmd)
			DoRexxMsg(mp, bn->ubn_Port, rxmsg, bn->ubn_ShowCmd);

		/* send screentofront command */

		if(ToFront && *bn->ubn_ToFrontCmd)
			DoRexxMsg(mp, bn->ubn_Port, rxmsg, bn->ubn_ToFrontCmd);

		/* try sending openurl msg */

		SPrintf(rxcmd, NewWindow ? bn->ubn_OpenURLWCmd : bn->ubn_OpenURLCmd, url);
		if(!*rxcmd) continue;
		retval = DoRexxMsg(mp, bn->ubn_Port, rxmsg, rxcmd);
		if(retval) break;

		if(Launch && *bn->ubn_Path)
		{
			STRPTR shellcmd, filepart;
			char c = '\0';
			BPTR lock;

			if(bn->ubn_Flags & UBNF_URLONCMDLINE)
			{
				if(!(shellcmd = AllocVec(strlen(bn->ubn_Path) + strlen(url) + 4, MEMF_ANY)))
					break;

				SPrintf(shellcmd, "%s \"%s\"", bn->ubn_Path, url);
			}
			else shellcmd = bn->ubn_Path;

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

			if(SystemTags(shellcmd, SYS_Asynch,    TRUE,
			                        SYS_Input,     Open("NIL:", MODE_NEWFILE),
			                        SYS_Output,    NULL,
			                 lock ? NP_CurrentDir : TAG_IGNORE, lock,
			                        TAG_DONE))
			{
				if(lock) UnLock(lock);
				FreeVec(shellcmd);
				continue;
			}
			else FreeVec(shellcmd);

			if(!(bn->ubn_Flags & UBNF_URLONCMDLINE))
			{
				LONG i, delay;

				/* don't want to send a new window cmd now */

				if(NewWindow)
				{
					SPrintf(rxcmd, bn->ubn_OpenURLCmd, url);
					if(!*rxcmd) goto done;
				}

				/* (busy) wait for the port to appear */
	
				delay = (FINDPORT_TIME * TICKS_PER_SECOND) / FINDPORT_NUM;
	
				for(i = 0; i < FINDPORT_NUM; i++)
				{
					retval = DoRexxMsg(mp, bn->ubn_Port, rxmsg, rxcmd);
					if(retval) goto done;
	
					if(SetSignal(0, 0) & SIGBREAKF_CTRL_C)
						goto done;
	
					Delay(delay);
				}
			}
			else
			{
				retval = TRUE;
				break;
			}
		}
	}

done:
	ReleaseSemaphore(&PrefsSemaphore);

	if(rxcmd) FreeVec(rxcmd);
	if(rxmsg) DeleteRexxMsg(rxmsg);
	if(mp)    DeletePort(mp);

	return(retval);
}

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

LIB struct URL_Prefs *LIB_URL_GetPrefs(VOID)
{
	struct URL_Prefs *p;

	/* make a copy of the prefs structure and return that */

	ObtainSemaphoreShared(&PrefsSemaphore);
	p = CopyPrefs(Prefs);
	ReleaseSemaphore(&PrefsSemaphore);
	return(p);
}

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

LIB VOID LIB_URL_FreePrefs(REG(a0) struct URL_Prefs *up)
{
	struct URL_BrowserNode *bn;

	/* free a prefs structure */

	while((bn = (struct URL_BrowserNode *)RemHead((struct List *)&up->up_BrowserList)))
		FreeMem(bn, sizeof(struct URL_BrowserNode));

	FreeMem(up, sizeof(struct URL_Prefs));
}

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

LIB BOOL LIB_URL_SetPrefs(REG(a0) struct URL_Prefs *p, REG(d0) BOOL permanent)
{
	BOOL retval = FALSE;
	struct URL_Prefs *new_p;

	/* copy prefs structure */

	ObtainSemaphore(&PrefsSemaphore);

	if(!(new_p = CopyPrefs(p)))
		goto done;

	LIB_URL_FreePrefs(Prefs);
	Prefs = new_p;

	/* and save it to disk */

	if(!SavePrefs(PREFS_NAME_USE, Prefs)) goto done;
	if(permanent && !SavePrefs(PREFS_NAME_SAVE, Prefs))
		goto done;

	retval = TRUE;
done:
	ReleaseSemaphore(&PrefsSemaphore);
	return(retval);
}

/**************************************************************************
*
* Utility functions used by the library functions.
*
*/

static BOOL DoRexxMsg(struct MsgPort *replyport, STRPTR port, struct RexxMsg *rxmsg, STRPTR rxcmd)
{
	struct MsgPort *targetport;

	/* create the arg string */

	if(!(rxmsg->rm_Args[0] = CreateArgstring(rxcmd, strlen(rxcmd))))
		return(FALSE);

	/* find a named rexx port, put a rexx msg to it and wait for the reply */

	Forbid();

	if((targetport = FindRexxPort(port)))
	{
		PutMsg(targetport, (struct Message *)rxmsg);
		Permit();
		WaitPort(replyport);
		GetMsg(replyport);
		DeleteArgstring(rxmsg->rm_Args[0]);
		return(TRUE);
	}
	else Permit();

	DeleteArgstring(rxmsg->rm_Args[0]);
	return(FALSE);
}

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

static struct MsgPort *FindRexxPort(STRPTR port)
{
	struct MsgPort *mp;
	TEXT buf[UBN_PORT_LEN + 10];
	LONG i;

	/* find a rexx port, trying various permutations of the name */

	if((mp = FindPort(port)))
		return(mp);

	for(i = 0; i < PORTNUM_MAX; i++)
	{
		SPrintf(buf, "%s.%ld", port, i);
		if((mp = FindPort(buf))) break;
	}

	return(mp);
}

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

static struct URL_Prefs *CopyPrefs(struct URL_Prefs *old_p)
{
	struct URL_Prefs *new_p;
	struct URL_BrowserNode *bn, *new_bn;

	/* make a copy of a prefs structure */

	if(!(new_p = AllocMem(sizeof(struct URL_Prefs), MEMF_ANY)))
		return(NULL);

	memcpy(new_p, old_p, sizeof(struct URL_Prefs));
	NewList((struct List *)&new_p->up_BrowserList);

	for(bn = (struct URL_BrowserNode *)old_p->up_BrowserList.mlh_Head;
	    bn->ubn_Node.mln_Succ;
	    bn = (struct URL_BrowserNode *)bn->ubn_Node.mln_Succ)
	{
		if(!(new_bn = AllocMem(sizeof(struct URL_BrowserNode), MEMF_ANY)))
		{
			LIB_URL_FreePrefs(new_p);
			return(NULL);
		}

		memcpy(new_bn, bn, sizeof(struct URL_BrowserNode));
		AddTail((struct List *)&new_p->up_BrowserList, (struct Node *)new_bn);
	}

	return(new_p);
}

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

static BOOL SavePrefs(STRPTR filename, struct URL_Prefs *up)
{
	BOOL retval = FALSE;
	struct IFFHandle *iffh;
	struct PrefHeader prhd;
	struct URL_BrowserNode *bn;
	ULONG size;

	/* init iff handle */

	if(!(iffh = AllocIFF()))
		goto done;

	if(!(iffh->iff_Stream = Open(filename, MODE_NEWFILE)))
		goto done;

	InitIFFasDOS(iffh);

	if(OpenIFF(iffh, IFFF_WRITE))
		goto done;

	/* init file as IFF PREF FORM */

	if(PushChunk(iffh, ID_PREF, ID_FORM, IFFSIZE_UNKNOWN))
		goto done;

	/* write pref header */

	if(PushChunk(iffh, ID_PREF, ID_PRHD, sizeof(struct PrefHeader)))
		goto done;

	prhd.ph_Version = up->up_Version;
	prhd.ph_Type    = 0;
	prhd.ph_Flags   = 0;

	if(WriteChunkBytes(iffh, &prhd, sizeof(struct PrefHeader)) != sizeof(struct PrefHeader))
		goto done;

	if(PopChunk(iffh))
		goto done;

	/* write browser nodes */

	size = sizeof(struct URL_BrowserNode) - sizeof(struct MinNode);

	for(bn = (struct URL_BrowserNode *)Prefs->up_BrowserList.mlh_Head;
	    bn->ubn_Node.mln_Succ;
	    bn = (struct URL_BrowserNode *)bn->ubn_Node.mln_Succ)
	{
		if(PushChunk(iffh, ID_PREF, ID_BRWS, size))
			goto done;

		if(WriteChunkBytes(iffh, &bn->ubn_Flags, size) != size)
			goto done;

		if(PopChunk(iffh))
			goto done;
	}

	if(PopChunk(iffh))
		goto done;

	retval = TRUE;
done:
	if(iffh)
	{
		CloseIFF(iffh);
		Close(iffh->iff_Stream);
		FreeIFF(iffh);
	}

	if(!retval) DeleteFile(filename);	

	return(retval);
}

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

static BOOL LoadPrefs(STRPTR filename)
{
	BOOL retval = FALSE;
	struct IFFHandle *iffh = NULL;
	struct ContextNode *cn;
	struct PrefHeader prhd;
	ULONG size;

	ObtainSemaphore(&PrefsSemaphore);

	/* allocate main prefs structure */

	if(!(Prefs = AllocMem(sizeof(struct URL_Prefs), MEMF_CLEAR)))
		goto done;

	Prefs->up_Version = PREFS_VERSION;
	NewList((struct List *)&Prefs->up_BrowserList);

	/* init iff handle */

	if(!(iffh = AllocIFF()))
		goto done;

	if(!(iffh->iff_Stream = Open(filename, MODE_OLDFILE)))
		goto done;

	InitIFFasDOS(iffh);

	if(OpenIFF(iffh, IFFF_READ))
		goto done;

	/* stop at these chunks */

	if(StopChunk(iffh, ID_PREF, ID_PRHD))
		goto done;

	if(StopChunk(iffh, ID_PREF, ID_BRWS))
		goto done;

	/* check that we got a prefheader of the right version */

	if(ParseIFF(iffh, IFFPARSE_SCAN))
		goto done;

	if(!(cn = CurrentChunk(iffh)))
		goto done;

	if((cn->cn_Type != ID_PREF) || (cn->cn_ID != ID_PRHD) || (cn->cn_Size != sizeof(struct PrefHeader)))
		goto done;

	if(ReadChunkBytes(iffh, &prhd, cn->cn_Size) != cn->cn_Size)
		goto done;

	if(prhd.ph_Version != PREFS_VERSION)
		goto done;

	/* read browser nodes */

	size = sizeof(struct URL_BrowserNode) - sizeof(struct MinNode);

	while(1)
	{
		ULONG error;

		error = ParseIFF(iffh, IFFPARSE_SCAN);
		if(error == IFFERR_EOF) break;
		else if(error) goto done;

		if(!(cn = CurrentChunk(iffh)))
			goto done;

		if((cn->cn_Type == ID_PREF) && (cn->cn_ID == ID_BRWS) && (cn->cn_Size == size))
		{
			struct URL_BrowserNode *bn;

			if(!(bn = AllocMem(sizeof(struct URL_BrowserNode), MEMF_CLEAR)))
				goto done;

			if(ReadChunkBytes(iffh, &bn->ubn_Flags, cn->cn_Size) != cn->cn_Size)
			{
				FreeMem(bn, sizeof(struct URL_BrowserNode));
				goto done;
			}

			AddTail((struct List *)&Prefs->up_BrowserList, (struct Node *)bn);
		}
	}

	retval = TRUE;
done:
	if(iffh)
	{
		CloseIFF(iffh);
		Close(iffh->iff_Stream);
		FreeIFF(iffh);
	}

	if(Prefs && !retval)
	{
		LIB_URL_FreePrefs(Prefs);
		Prefs = NULL;
	}

	ReleaseSemaphore(&PrefsSemaphore);

	return(retval);
}

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

static BOOL SetDefaultPrefs(VOID)
{
	BOOL retval = FALSE;
	struct URL_BrowserNode *bn;

	ObtainSemaphore(&PrefsSemaphore);

	if(!(Prefs = AllocMem(sizeof(struct URL_Prefs), MEMF_CLEAR)))
		goto done;

	Prefs->up_Version = PREFS_VERSION;
	NewList((struct List *)&Prefs->up_BrowserList);

	if(!(bn = AllocMem(sizeof(struct URL_BrowserNode), MEMF_CLEAR)))
		goto done;

	bn->ubn_Flags |= UBNF_URLONCMDLINE;
	strcpy(bn->ubn_Name, "AMosaic");
	strcpy(bn->ubn_Port, "AMOSAIC");
	strcpy(bn->ubn_ShowCmd, "SHOW");
	strcpy(bn->ubn_ToFrontCmd, "SCREEN FRONT");
	strcpy(bn->ubn_OpenURLCmd, "JUMP URL %s");

	AddTail((struct List *)&Prefs->up_BrowserList, (struct Node *)bn);

	if(!(bn = AllocMem(sizeof(struct URL_BrowserNode), MEMF_CLEAR)))
		goto done;

	bn->ubn_Flags |= UBNF_URLONCMDLINE;
	strcpy(bn->ubn_Name, "AWeb");
	strcpy(bn->ubn_Port, "AWEB");
	strcpy(bn->ubn_ShowCmd, "ICONIFY SHOW");
	strcpy(bn->ubn_ToFrontCmd, "SCREENTOFRONT");
	strcpy(bn->ubn_OpenURLCmd, "OPEN %s");
	strcpy(bn->ubn_OpenURLWCmd, "NEW %s");

	AddTail((struct List *)&Prefs->up_BrowserList, (struct Node *)bn);

	if(!(bn = AllocMem(sizeof(struct URL_BrowserNode), MEMF_CLEAR)))
		goto done;

	bn->ubn_Flags |= UBNF_URLONCMDLINE;
	strcpy(bn->ubn_Name, "IBrowse");
	strcpy(bn->ubn_Port, "IBROWSE");
	strcpy(bn->ubn_ShowCmd, "SHOW");
	strcpy(bn->ubn_ToFrontCmd, "SCREENTOFRONT");
	strcpy(bn->ubn_OpenURLCmd, "GOTOURL %s");
	strcpy(bn->ubn_OpenURLWCmd, "NEWWINDOW %s");

	AddTail((struct List *)&Prefs->up_BrowserList, (struct Node *)bn);

	if(!(bn = AllocMem(sizeof(struct URL_BrowserNode), MEMF_CLEAR)))
		goto done;

	bn->ubn_Flags |= UBNF_URLONCMDLINE;
	strcpy(bn->ubn_Name, "Voyager");
	strcpy(bn->ubn_Port, "VOYAGER");
	strcpy(bn->ubn_ShowCmd, "SHOW");
	strcpy(bn->ubn_ToFrontCmd, "SCREENTOFRONT");
	strcpy(bn->ubn_OpenURLCmd, "OPENURL %s");
	strcpy(bn->ubn_OpenURLWCmd, "OPENURL %s NEWWIN");

	AddTail((struct List *)&Prefs->up_BrowserList, (struct Node *)bn);

	retval = TRUE;
done:
	if(!retval && Prefs)
	{
		LIB_URL_FreePrefs(Prefs);
		Prefs = NULL;
	}

	ReleaseSemaphore(&PrefsSemaphore);
	return(retval);
}

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

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