/*
**  TitleClock - throw up a clock in the top right
**
**  Copyright ©1992-94 by Anders Hammarquist, permission granted for
**  non-commercial use and distribution. See documentation for further
**  information.
**
**  $Id: TitleClock.c,v 3.3 94/02/12 16:08:52 Viking Exp Locker: Viking $
**
**  $Revision: 3.3 $
**  $Date: 94/02/12 16:08:52 $
**
*/

#define __USE_SYSBASE

#include <dos/datetime.h>
#include <dos/dos.h>
#include <exec/memory.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <exec/libraries.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <graphics/gfx.h>
#include <libraries/commodities.h>
#include <devices/timer.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/icon.h>
#include <proto/utility.h>
#include <proto/commodities.h>
#include <proto/timer.h>
#include <workbench/workbench.h>
#include <workbench/startup.h>
#include "titleclock_protos.h"

#include <libraries/locale.h>
#include <proto/locale.h>

/* Stuff */
#define MAXCLOCKLEN 256
#define CLOCKOFFSET 5 /* Number of pixels between screen depth gadget */
		      /* and clock text */
#define TEMPLATE "UPDATE/N/K,SHOWDATE/S,SHOWDAY/S,SHORTDAY/S,SHOWSECS/S,FORMAT/K,DATEFORMAT/K,PUBSCREEN/K,SCREENPAT/K,FRONTSCREEN/S,DEFSCREEN/S,CX_PRIORITY/N/K"
enum ArgsEnum
{
    UPDATE,
    SHOWDATE,
    SHOWDAY,
    SHORTDAY,
    SHOWSECS,
    FORMAT,
    DATEFORMAT,
    PUBSCREEN,
    SCREENPAT,
    FRONTSCREEN,
    DEFSCREEN,
    CX_PRI,
    NUMARGS
};

#ifndef BARDETAILPEN
#define BARDETAILPEN	(0x0009)
#define BARBLOCKPEN	(0x000A)
#endif

#define min(a,b) ((a)>(b)?(b):(a))
#define max(a,b) ((a)<(b)?(b):(a))
#define tolower(a) ((a)|0x20) /* Quick-and-dirty */
#define isnum(a) (((a)>0x2F)&&((a)<0x3A))

/* From sysiclass docs */
#define LOWDEPTHWIDTH 17
#define HIDEPTHWIDTH  23

/* More stuff */
static struct RastPort *MyRPort=NULL;
static struct BitMap   *MyBitMap=NULL;

struct ExecBase 	*SysBase;
struct DosLibrary	*DOSBase;
struct GfxBase		*GfxBase;
struct IntuitionBase	*IntuitionBase;
struct Library		*UtilityBase;
struct Library		*CxBase;
struct Library		*IconBase;
struct Library		*TimerBase;
struct LocaleBase	*LocaleBase;

struct Locale		*Locale;

static const char *Ver="$VER: TitleClock 3.3 (12.2.94)";

static struct NewBroker newbrok =
{
    NB_VERSION,
    "TitleClock",
    "TitleClock",
    "Displays clock in screen titlebar",
    NBU_UNIQUE|NBU_NOTIFY,
    0,
    0,
    0,
    0
};

static LONG Update=1;
static LONG Fmt=FORMAT_DOS;
static LONG Args[NUMARGS] = { (LONG)&Update, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (LONG)&(newbrok.nb_Pri)};
static int LeftEdge;

static char *PubPat=NULL, *ScrPat=NULL;

static struct timerequest *TimerIO=NULL;

static BOOL TimerPending=0, WildPub;

/* Da main routine */
int __saveds TitleClock(void)
{
    struct Process *MyProc;
    struct WBStartup *WBs=NULL;
    struct DiskObject *Do=NULL;
    struct MsgPort  *CXPort=NULL, *TimerPort=NULL;
    struct RDArgs     *rdargs=NULL;
    CxObj *Broker=NULL;
    BOOL TimerFail=TRUE;
    int CXSig, TimerSig;
    long sig;
    CxMsg *CXMsg;

    /* set up ExecBase */
    SysBase = *((struct ExecBase **)(4L));

    if(!((MyProc=(struct Process *)FindTask(NULL))->pr_CLI))
    {
	/* Started from WB */

	/* Get Message */
	WaitPort(&(MyProc->pr_MsgPort));
	WBs=(struct WBStartup *)GetMsg(&(MyProc->pr_MsgPort));
    }

    /* Check version */
    if(SysBase->LibNode.lib_Version<37) /* Too Old */
	goto exit;

    /* Open needed libraries */
    DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",37);
    GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",37);
    IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",37);
    UtilityBase=OpenLibrary("utility.library",37);
    CxBase=OpenLibrary("commodities.library",37);
    IconBase=OpenLibrary("icon.library",37);
    LocaleBase=(struct LocaleBase *)OpenLibrary("locale.library",38);

    if(!(DOSBase&&GfxBase&&IntuitionBase&&UtilityBase&&CxBase&&IconBase))
	/* didn't open */
	goto cleanup;

    if(LocaleBase)
	Locale=OpenLocale(NULL);

    if(WBs)
    {
        BPTR oldLock;
      
	oldLock=CurrentDir(WBs->sm_ArgList[0].wa_Lock);

	if(Do=GetDiskObject(WBs->sm_ArgList[0].wa_Name))
	{
	    char *ToolValue;

	    /* Update */
	    if(ToolValue=FindToolType(Do->do_ToolTypes,"UPDATE"))
		StrToLong(ToolValue,&Update);

	    /* Showdate */
	    Args[SHOWDATE]=(long)FindToolType(Do->do_ToolTypes,"SHOWDATE");

	    /* Showday */
	    Args[SHOWDAY]=(long)FindToolType(Do->do_ToolTypes,"SHOWDAY");

	    /* Shortday */
	    Args[SHORTDAY]=(long)FindToolType(Do->do_ToolTypes,"SHORTDAY");

	    /* Showsecs */
	    Args[SHOWSECS]=(long)FindToolType(Do->do_ToolTypes,"SHOWSECS");

	    /* Format */
	    Args[FORMAT]=(long)FindToolType(Do->do_ToolTypes,"FORMAT");

	    /* DateFormat */
	    Args[DATEFORMAT]=(long)FindToolType(Do->do_ToolTypes,"DATEFORMAT");

	    /* PubScreen */
	    Args[PUBSCREEN]=(long)FindToolType(Do->do_ToolTypes,"PUBSCREEN");

	    /* NonPubScreen */
	    Args[SCREENPAT]=(long)FindToolType(Do->do_ToolTypes,"SCREENPAT");

	    /* FrontScreen */
	    Args[FRONTSCREEN]=(long)FindToolType(Do->do_ToolTypes,"FRONTSCREEN");

	    /* DefScreen */
	    Args[DEFSCREEN]=(long)FindToolType(Do->do_ToolTypes,"DEFSCREEN");

	    /* CX_Priority */
	    if(ToolValue=FindToolType(Do->do_ToolTypes,"CX_PRIORITY"))
		StrToLong(ToolValue,&Args[CX_PRI]);
	}

	CurrentDir(oldLock);
    }
    else
	if(!(rdargs=ReadArgs(TEMPLATE,Args,NULL)))
	{
	    PrintFault(IoErr(),"TitleClock");
	    goto cleanup;
	}

    newbrok.nb_Pri=*(int *)Args[CX_PRI];

    if(Args[FORMAT])
    {
	switch(tolower(((char *)Args[FORMAT])[1]))
	{
	    case 'o': /* DOS */
		Fmt=FORMAT_DOS;
		break;

	    case 'n': /* International */
		Fmt=FORMAT_INT;
		break;

	    case 'm': /* American */
		Fmt=FORMAT_USA;
		break;

	    case 'a': /* Canadian */
		Fmt=FORMAT_CDN;
		break;
	}
    }

    if(Args[PUBSCREEN])
    {
	int len;

	if(PubPat=AllocVec(len=StrLen((char *)Args[PUBSCREEN])*2+2,MEMF_ANY))
	{
	    if((WildPub=ParsePattern((char *)Args[PUBSCREEN],PubPat,len))==-1)
	    {
		FreeVec(PubPat);
		PubPat=NULL;
	    }
	}

    }

    if(Args[SCREENPAT])
    {
	int len;

	if(ScrPat=AllocVec(len=StrLen((char *)Args[SCREENPAT])*2+2,MEMF_ANY))
	{
	    if(ParsePattern((char *)Args[SCREENPAT],ScrPat,len)==-1)
	    {
		FreeVec(ScrPat);
		ScrPat=NULL;
	    }
	}

    }

    /* Args read */

    /* Allocate BitMap and RastPort */
    if(!(MyBitMap=AllocVec(sizeof(struct BitMap),MEMF_CLEAR|MEMF_PUBLIC)))
	goto cleanup;

    if(!(MyRPort=AllocVec(sizeof(struct RastPort),MEMF_CLEAR|MEMF_PUBLIC)))
	goto cleanup;

    InitRastPort(MyRPort);
    MyRPort->BitMap=MyBitMap;

    /* Set up timer.device */
    if(!(TimerPort=CreateMsgPort()))
	goto cleanup;

    TimerSig= 1L << TimerPort->mp_SigBit;

    if(!(TimerIO=(struct timerequest *)
		    CreateIORequest(TimerPort,sizeof(struct timerequest))))
	goto cleanup;

    if(TimerFail=OpenDevice("timer.device",UNIT_WAITUNTIL,TimerIO,0))
	goto cleanup;

    /* Library pointer */
    TimerBase=TimerIO->tr_node.io_Device;

    /* Init timerequest */
    TimerIO->tr_node.io_Command=TR_ADDREQUEST;
    TimerIO->tr_time.tv_micro=0;

    if(!(CXPort=CreateMsgPort()))
	goto cleanup;

    CXSig   = 1L << CXPort->mp_SigBit;

    newbrok.nb_Port=CXPort;

    if(!(Broker=CxBroker(&newbrok,NULL)))
	goto cleanup;

    ActivateCxObj(Broker,1L);

    /* Setup done */

    {
	int Abort=0, Enabled=1;

	UpdateClk();

	while(!Abort)
	{
	    sig=Wait(CXSig|TimerSig|SIGBREAKF_CTRL_C);

	    if(sig&TimerSig)
	    {
		if(GetMsg(TimerPort))
		{
		    TimerPending--;
		    if(Enabled) UpdateClk();
		}
	    }

	    while(CXMsg=(CxMsg *)GetMsg(CXPort))
	    {
		const int msgid=CxMsgID(CXMsg);
		const int msgtype=CxMsgType(CXMsg);

		ReplyMsg((struct Message *)CXMsg);

		if(msgtype==CXM_COMMAND)
		    switch(msgid)
		    {
			case CXCMD_DISABLE:
			    Enabled=0;
			    ActivateCxObj(Broker,0);
			    break;

			case CXCMD_ENABLE:
			    Enabled=1;
			    if(!TimerPending) UpdateClk();
			    ActivateCxObj(Broker,1);
			    break;

			case CXCMD_KILL:
			case CXCMD_UNIQUE:
			    Abort=1;
		    }
	    }

	    if(sig&SIGBREAKF_CTRL_C) Abort=1;
	}
	if(TimerPending)
	{
	    if(!CheckIO(TimerIO)) AbortIO(TimerIO);
	    WaitIO(TimerIO);
	}
    }

cleanup:
    CleanUp();

    /* Commodity stuff */
    if(Broker)
    {
	DeleteCxObj(Broker);
	while(CXMsg=(CxMsg *)GetMsg(CXPort))
	    ReplyMsg((struct Message *)CXMsg);
    }
    if(CXPort) DeleteMsgPort(CXPort);

    /* Timer stuff */
    if(!TimerFail)
	CloseDevice(TimerIO);
    if(TimerIO) DeleteIORequest(TimerIO);
    if(TimerPort) DeleteMsgPort(TimerPort);

    FreeVec(PubPat);
    FreeVec(ScrPat);

    if(rdargs) FreeArgs(rdargs);
    if(Do) FreeDiskObject(Do);

    CloseLocale(Locale);
    CloseLibrary(LocaleBase);
    CloseLibrary(IconBase);
    CloseLibrary(UtilityBase);
    CloseLibrary(CxBase);
    CloseLibrary((struct Library *)IntuitionBase);
    CloseLibrary((struct Library *)GfxBase);
    CloseLibrary((struct Library *)DOSBase);

exit:
    if(WBs)
    {
	/* Reply WBStartup */
	Forbid();
	ReplyMsg(WBs);
    }

    MyProc->pr_Result2=0;
    return 0;
}

void __asm SPFunc(register __a0 struct Hook *pHook, register __a1 char pChar)
{
  if ((pChar&0x60) && ((int)pHook->h_Data - (int)pHook->h_SubEntry <
			MAXCLOCKLEN)) /* Quick-and-dirty isprint() + */
				      /* make sure that we're not */
				      /* printing too many chars. */
	*((*(char **)&(pHook->h_Data))++)=pChar;
}

/* Print hook */
struct Hook PrintHook =
{
  NULL, NULL, 		/* MinNode */
  (ULONG (* )())SPFunc,	/* h_Entry, PrintFunc */
  NULL,			/* h_SubEntry. Dirtily used as pointer for */
			/* where the string began. */
  NULL			/* h_Data, where to put char */
};

/* print date */
int SPrintD(char *Dest, char *Fmt, struct DateStamp *Date)
{
    PrintHook.h_SubEntry=(ULONG (* )())(PrintHook.h_Data=Dest);
    FormatDate(Locale,Fmt,Date,&PrintHook);
    *((char *)PrintHook.h_Data)='\0';

    return ((int)((char *)PrintHook.h_Data-Dest)); /* return length of string */
}

/* Display clocks on appropriate screens */
static void DisplayClk(char *clkstr, int clklen, struct Screen *cs)
{
    if(AttemptLockLayerRom(cs->BarLayer))
    {
        struct DrawInfo *dri;
	struct TextFont *MyFont;
	int i, BltLen;
	UWORD FrontPen, BackPen;
	WORD dSize;

	MyFont=cs->BarLayer->rp->Font;	/* No need to 'open' it */

	if(MyBitMap->Depth<min(cs->RastPort.BitMap->Depth,8)||MyBitMap->Rows<MyFont->tf_YSize||MyBitMap->BytesPerRow<cs->RastPort.BitMap->BytesPerRow)
	{
	    /* Need 'better' rastport */

	    FreeVec(MyBitMap->Planes[0]);
	    InitBitMap(MyBitMap,max(MyBitMap->Depth,min(cs->RastPort.BitMap->Depth,8)),cs->Width,max(MyBitMap->Rows,MyFont->tf_YSize));

	    if(!(MyBitMap->Planes[0]=AllocVec(MyBitMap->BytesPerRow*MyBitMap->Rows*MyBitMap->Depth,MEMF_CHIP)))
	    {
		MyBitMap->Depth=0;
		goto next;
	    }

	    for(i=1;i<min(MyBitMap->Depth,8);i++)
		MyBitMap->Planes[i]=((UBYTE *)MyBitMap->Planes[i-1])+MyBitMap->BytesPerRow*MyBitMap->Rows;
	}

	if(dri=GetScreenDrawInfo(cs))
	{
	    if(dri->dri_NumPens>=0x000A)
	    {
		FrontPen=dri->dri_Pens[BARDETAILPEN];
		BackPen=dri->dri_Pens[BARBLOCKPEN];
	    }
	    else
	    {
		FrontPen=dri->dri_Pens[DETAILPEN];
		BackPen=dri->dri_Pens[BLOCKPEN];
	    }
	    FreeScreenDrawInfo(cs,dri);
	}
	/* If we don't get DrawInfo, the pens will be somewhat random */

	SetFont(MyRPort,MyFont);
	SetDrMd(MyRPort,JAM2);
	SetAPen(MyRPort,FrontPen);
	SetBPen(MyRPort,BackPen);
	SetRast(MyRPort,BackPen);

	Move(MyRPort, MyFont->tf_XSize, MyFont->tf_Baseline);

	Text(MyRPort, clkstr, clklen);

	dSize = (cs->Flags&SCREENHIRES?HIDEPTHWIDTH:LOWDEPTHWIDTH);

	LeftEdge = cs->Width - (BltLen=MyRPort->cp_x+CLOCKOFFSET) - dSize;

	if(BltLen+dSize>cs->Width)
	    BltLen=cs->Width-dSize;

	ClipBlit(MyRPort,0,0,cs->BarLayer->rp,LeftEdge,1,BltLen,MyFont->tf_YSize,0x0C0);
next:
	UnlockLayerRom(cs->BarLayer);
    }
}

struct PSList
{
  struct PSList *psl_Next;
  struct Screen *psl_Screen;
};

static struct PSList *LockPubScreens(UBYTE *Pattern)
{
  struct PubScreenNode *psnode;
  struct PSList *mylist=NULL, *tmp, *last;

  if(psnode=(struct PubScreenNode *)LockPubScreenList())
    {
      while((psnode=(struct PubScreenNode *)psnode->ln_Succ)->ln_Succ)
	{
	  if(!(psnode->psn_Flags&PSNF_PRIVATE)&&MatchPattern(Pattern,psnode->ln_Name))
	    {
	      if(tmp=AllocVec(sizeof(struct PSList),0L))
		{
		  psnode->psn_VisitorCount++;
		  tmp->psl_Screen=psnode->psn_Screen;
#pragma msg 94 ignore push
		  if(mylist) last->psl_Next=tmp;
#pragma msg 94 pop
		  else mylist=tmp;
		  last=tmp;
		}
	    }
	}
      tmp->psl_Next=NULL;
      UnlockPubScreenList();
    }

  return mylist;
}

static void UnlockPubScreens(struct PSList *list)
{
  struct PSList *tmp;
  
  do {
    tmp=list->psl_Next;
    UnlockPubScreen(NULL,list->psl_Screen);
    FreeVec(list);
  }
  while(list=tmp);
}

/* Update clock */
static void UpdateClk(void)
{
    struct DateTime dt;
    static char DispStr[MAXCLOCKLEN];
    int StringLen=0;

    DateStamp(&dt);

    if(!Args[DATEFORMAT] || !LocaleBase)
    {
	dt.dat_Format = Fmt;
	dt.dat_Flags  = 0;
	dt.dat_StrDay = NULL;
	dt.dat_StrDate= NULL;
	dt.dat_StrTime= NULL;

	DispStr[0]='\0';

	if(Args[SHOWDAY])
	{
	    dt.dat_StrDay = DispStr;

	    DateToStr(&dt);
	    if(Args[SHORTDAY]) DispStr[3] = '\0';

	    DispStr[StringLen=StrLen(DispStr)]=' ';
	    StringLen++;
	}

	if(Args[SHOWDATE])
	{
	    dt.dat_StrDay = NULL;
	    dt.dat_StrDate= &DispStr[StringLen];

	    DateToStr(&dt);

	    DispStr[StringLen=StrLen(DispStr)]=' ';
	    StringLen++;
	}

	dt.dat_StrDate= NULL;
	dt.dat_StrTime= &DispStr[StringLen];

	DateToStr(&dt);

	StringLen=StrLen(DispStr);

	if(!Args[SHOWSECS]) StringLen -= 3;
    }
    else
    {
	/* Use FormatDate */
	StringLen=SPrintD(DispStr,(char *)Args[DATEFORMAT],&dt);
    }

    /* Now display the things on the right screens */
    {
	ULONG ILock;
	struct Screen *cs;

	ILock=LockIBase(0);

	/* This is part while the IntuitionBase lock is held not-quite */
	/* legal. The AutoDoc says not to call any intuition, */
	/* graphics, etc functions while holding the lock, but it */
	/* seems to work. My guess is that the warning in the AutoDoc */
	/* is to keep people from locking up the machine trying to */
	/* draw while someone has a layer locked. If this is the */
	/* reason, then the following should not cause any problems */
	/* what so ever, since I won't draw unless I get to lock the */
	/* layer. If there is some other obscure reason, then this may */
	/* cause problems on some systems. But so far I haven't seen */
	/* or heard of any problems with it, and it's really the only */
	/* way to do what we want to do. Also, we probably hold the */
	/* lock for way too long trying to draw the clock on all the */
	/* right screens :-) */ 

	cs=IntuitionBase->FirstScreen;

	if(cs&&Args[FRONTSCREEN])                   /* First Screen */
	{
	    DisplayClk(DispStr,StringLen,cs);
	    cs=cs->NextScreen;
	}

	if(ScrPat&&cs)                          /* Match screen title */
	{
	    do
	    {
		if(cs->DefaultTitle&&MatchPattern(ScrPat,cs->DefaultTitle))
		    DisplayClk(DispStr,StringLen,cs);
	    }
	    while(cs=cs->NextScreen);
	}

	UnlockIBase(ILock);

	/* Public screens */
	if(PubPat)
	{
	    if(WildPub)
	    {
		struct PSList *pslist, *psl;

		if(psl=pslist=LockPubScreens(PubPat)) {
		  do {
		    DisplayClk(DispStr,StringLen,pslist->psl_Screen);
		  }
		  while(pslist=pslist->psl_Next);
		}

		UnlockPubScreens(psl);
	    }
	    else
	    {
		if(cs=LockPubScreen((char *)Args[PUBSCREEN]))
		{
		    DisplayClk(DispStr,StringLen,cs);
		    UnlockPubScreen(NULL,cs);
		}
	    }
	}

	/* Default public screen */
	if(Args[DEFSCREEN]&&(cs=LockPubScreen(NULL)))
	{
	    DisplayClk(DispStr,StringLen,cs);
	    UnlockPubScreen(NULL,cs);
	}
    }

    /* Rescheduele timer */
    GetSysTime(&TimerIO->tr_time);

    TimerIO->tr_time.tv_secs+=*(int *)Args[UPDATE];
    TimerIO->tr_time.tv_micro=0;
    SendIO(TimerIO);
    TimerPending++;
}

static void CleanUp(void)
{
    if(MyBitMap)
    {
	FreeVec(MyBitMap->Planes[0]);
	FreeVec(MyBitMap);
    }
    FreeVec(MyRPort);
}

static int StrLen(const char *str)
{
    int i;

    for(i=0;str[i];i++);

    return (i);
}
