/*
 *  Uhr.c
 *
 *  Author: Stefan Sticht
 *
 *  Copyright: source is public domain, no copyright
 *
 *  Version history:
 *
 *  V0.01   initial release
 *  V0.02   using now cback.o
 *  V0.03   RMBTRAP flag set
 *  V0.04   added LockIBase before looking at IntuitionBase
 *          (thanks to Holger Gzella for bug report),
 *          added screen title
 *  V0.05   structure for timerequest is now in MEMF_PUBLIC
 *          (thanks to Holger Gzella)
 *          now using CreateMsgPort() from exec.library instead of CreatePort() from amiga.lib
 *  V0.06   changed LockPubScreen("Workbench") to LockPubScreen(NULL)
 *          now using CreateIORequest(); using WaitIO() instead of Wait() after AbortIO()
 *          removed LockIBase(), redefined SAS/C chkabort() to prevent CTRL/C trapping
 *  V0.07 14 Sep 1991 small changes to reduce size
 *  V1.00 13 May 1992 mow uses locale.library's FormatDate()
 *  V1.01 19 Sep 1992 small changes, reduced size
 *  V1.02 24 Sep 1992 added SPACE option
 *  V1.03 19 Mar 1993 recompiled with SAS/C 6.2
 */

#define VERSION "1.03"

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <devices/timer.h>
#include <exec/memory.h>
#include <intuition/intuitionbase.h>
#include <libraries/locale.h>
#include <workbench/icon.h>
#include <workbench/startup.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/graphics_protos.h>
#include <clib/icon_protos.h>
#include <clib/intuition_protos.h>
#include <clib/locale_protos.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_sysbase_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#include <pragmas/icon_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#include <pragmas/locale_pragmas.h>

/*
 *  prototypes
 */
long __stdargs request(char *title, char *gadgets, char *text, ...);
struct Library __regargs *myopenlibrary(char *name, unsigned long version);
void __regargs getmessages(struct Window *win);
void __saveds __asm putcharfunc(register __a0 struct Hook *h, register __a2 void *object, register __a1 char c);

/*
 *  global data
 */
#define PROGRAMTITLE    "Uhr"
#define TITLE           PROGRAMTITLE " " VERSION " © Stefan Sticht"

#define MSG_ERROR_LIBRARY   "Couldn't open %s (V%ld)!"
#define MSG_ERROR_OPENPORT  "Couldn't open message port!"
#define MSG_ATTENTION       PROGRAMTITLE ":"
#define RESUME_GADGETS      "Resume"
#define RETRY_GADGETS       "Retry|Cancel"

#ifdef __SASC
extern struct WBStartup *WBenchMsg;
extern struct Library *DOSBase;
extern struct Library *SysBase;
#endif

#define BUFFERSIZE 100
char buffer[BUFFERSIZE];

struct Hook putcharhook = {
    {NULL, NULL},
    (unsigned long (*)())putcharfunc,
    NULL,
    buffer
    };

#define TITLE_OFFSET 7
static char version[] ="\0$VER: " TITLE;

short bufferpos;
struct DateStamp datestamp;
struct Library *GfxBase;
struct Library *LocaleBase;
struct Locale *locale;
struct IntuitionBase *IntuitionBase;
struct timerequest *timereq;
struct MsgPort *timerport;

unsigned long updateseconds = 1;
unsigned long updatemicros  = 0;
long priority = -1;
long space = 20;

#define OPTION_FORMAT           "FORMAT"
#define OPTION_UPDATESECONDS    "UPDATESECONDS"
#define OPTION_UPDATEMICROS     "UPDATEMICROS"
#define OPTION_LEFT             "LEFT"
#define OPTION_TOP              "TOP"
#define OPTION_WINDOWTOFRONT    "WINDOWTOFRONT"
#define OPTION_PRIORITY         "PRIORITY"
#define OPTION_PUBSCREEN        "PUBSCREEN"
#define OPTION_SPACE            "SPACE"
#define TEMPLATE OPTION_FORMAT "/K," "S=" OPTION_UPDATESECONDS "/K/N," "M=" OPTION_UPDATEMICROS "/K/N,"\
                 OPTION_LEFT "/K/N," OPTION_TOP "/K/N," "WTF=" OPTION_WINDOWTOFRONT "/S,"\
                 "PRI=" OPTION_PRIORITY "/K/N," OPTION_PUBSCREEN "/K," OPTION_SPACE "/K/N"
#define OPTN_FORMAT             0
#define OPTN_UPDATESECONDS      1
#define OPTN_UPDATEMICROS       2
#define OPTN_LEFT               3
#define OPTN_TOP                4
#define OPTN_WINDOWTOFRONT      5
#define OPTN_PRIORITY           6
#define OPTN_PUBSCREEN          7
#define OPTN_SPACE              8
#define OPTN_COUNT              9
long options[OPTN_COUNT];

#define FORMAT          (options[OPTN_FORMAT])
#define WINDOWTOFRONT   (options[OPTN_WINDOWTOFRONT])
#define PUBSCREEN       (options[OPTN_PUBSCREEN])

/*
 *  functions
 */

/*
 *  request(): a glue routine to EasyRequest as simple as printf plus
 *             titlestring, gadgettexts
 *
 *  Input: char *title:         pointer to the title of the requester
 *         char *gadgets:       pointer to gadgettext
 *         char *text:          text displayed in requester
 *
 *  Result: same as EasyrequestArgs()
 *
 * !!! for more info see EasyRequestArgs() in Autodocs/intuition.doc !!!
 */
long __stdargs request(char *title, char *gadgets, char *text, ...)
{
    /*
     *  structure textreq only needed in this function, so hide it here
     *  must be static, in order to be initialized only once
     */
    long rc = 0;
    struct EasyStruct textreq = {
        sizeof (struct EasyStruct), /* ULONG es_StructSize      */
        0l,                         /* ULONG es_Flags           */
        NULL,                       /* UBYTE *es_Title          */
        NULL,                       /* UBYTE *es_TextFormat     */
        NULL,                       /* UBYTE *es_GadgetFormat   */
        };
    struct Process *process;
    struct Window *win;
    va_list ap;

    /*
     *  get start of variable arguments
     */
    va_start(ap, text);

    if (process = (struct Process *)FindTask(NULL)) {
        if ((long)(win = process->pr_WindowPtr) != -1) {

            /*
             *  update textreq
             */
            textreq.es_Title = (UBYTE *)title;
            textreq.es_TextFormat = (UBYTE *)text;
            textreq.es_GadgetFormat = (UBYTE *)gadgets;

            rc = EasyRequestArgs(win, &textreq, NULL, ap);

            }

        }

    va_end(ap);

    return(rc);
}

/*
 *  myopenlibrary(): same as OpenLibrary(), but opens a retry-requester
 *                   if OpenLibrary() fails, to give the user a chance to
 *                   copy the library to libs: and retry
 *                   requires request(), see above
 */
struct Library __regargs *myopenlibrary(char *name, unsigned long version)
{
    struct Library *libptr;
    long ok = TRUE;

    do {
        if (!(libptr = OpenLibrary((UBYTE *)name, version))) {
            ok = request(MSG_ATTENTION, RETRY_GADGETS, MSG_ERROR_LIBRARY, name, version);
            }
        } while (!libptr && ok);

    return(libptr);
}

void __main(char *line)
{
    long rc = RETURN_FAIL;
    struct DiskObject *diskobj = NULL;
    struct Library *IconBase = NULL;
    struct RDArgs *args = NULL;
    struct Screen *scr;
    struct Task *mytask;
    struct Window *win;
    char startpri;
    #define WIDTH_OF_CLOSE_GADGET 20
    #define WIDTH_OF_DEPTH_GADGET 23
    #define P_WA_Left       0
    #define P_WA_Top        1
    #define P_WA_Width      2
    #define P_WA_Height     3
    #define P_WA_PubScreen  6
    static struct TagItem mywindowtags[] = {
        WA_Left,        0l,
        WA_Top,         0l,
        WA_Width,       0l,
        WA_Height,      0l,
        WA_IDCMP,       IDCMP_CLOSEWINDOW,
        WA_Flags,       WFLG_DRAGBAR | WFLG_CLOSEGADGET | WFLG_SMART_REFRESH | WFLG_RMBTRAP,
        WA_PubScreen,   0l,
        WA_ScreenTitle, (ULONG)&version[TITLE_OFFSET],
        TAG_END
        };

    if (IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 37l)) {

        unsigned long left = -1;
        unsigned long top = -1;

        if (!WBenchMsg) {
            /*
             *  started from Shell
             *
             *  change name of program
             */
            SetProgramName(PROGRAMTITLE);
            /*
             *  display banner
             */
            PutStr(&version[TITLE_OFFSET]); PutStr("\n");
            /*
             *  parse command line
             */
            if (args = ReadArgs((UBYTE *)TEMPLATE, options, NULL)) {
                if (options[OPTN_UPDATESECONDS]) updateseconds = *((long *)options[OPTN_UPDATESECONDS]);
                if (options[OPTN_UPDATEMICROS]) updatemicros = *((long *)options[OPTN_UPDATEMICROS]);
                if (options[OPTN_TOP]) top = *((long *)options[OPTN_TOP]);
                if (options[OPTN_LEFT]) left = *((long *)options[OPTN_LEFT]);
                if (options[OPTN_PRIORITY]) priority = *((long *)options[OPTN_PRIORITY]);
                if (options[OPTN_SPACE]) space = *((long *)options[OPTN_SPACE]);
                rc = 0;
                }
            else {
                /*
                 *  error parsing args
                 */
                PrintFault(IoErr(), NULL);
                }
            }

        else if (IconBase = myopenlibrary(ICONNAME, 37l)) {

            BPTR olddir = -1l;
            char **tooltypesarray;
            char *tooltype;
            struct WBArg *wbarg;

            if (wbarg = &(WBenchMsg->sm_ArgList)[WBenchMsg->sm_NumArgs - 1l]) {

                if (wbarg->wa_Name) {

                    if (wbarg->wa_Lock) olddir = CurrentDir(wbarg->wa_Lock);

                    if (diskobj = GetDiskObject(wbarg->wa_Name)) {

                        tooltypesarray = diskobj->do_ToolTypes;

                        if (tooltype = FindToolType(tooltypesarray, OPTION_FORMAT)) FORMAT = (long)tooltype;
                        if (tooltype = FindToolType(tooltypesarray, OPTION_UPDATESECONDS)) updateseconds = atol(tooltype);
                        if (tooltype = FindToolType(tooltypesarray, OPTION_UPDATEMICROS)) updatemicros = atol(tooltype);
                        if (FindToolType(tooltypesarray, OPTION_WINDOWTOFRONT)) WINDOWTOFRONT = TRUE;
                        if (tooltype = FindToolType(tooltypesarray, OPTION_LEFT)) left = atol(tooltype);
                        if (tooltype = FindToolType(tooltypesarray, OPTION_TOP)) top = atol(tooltype);
                        if (tooltype = FindToolType(tooltypesarray, OPTION_PRIORITY)) priority = atol(tooltype);
                        if (tooltype = FindToolType(tooltypesarray, OPTION_SPACE)) space = atol(tooltype);
                        if (tooltype = FindToolType(tooltypesarray, OPTION_PUBSCREEN)) PUBSCREEN = (long)tooltype;
                        rc = 0;

                        } /* if diskobj */

                    if (olddir != -1l) CurrentDir(olddir);

                    } /* if wbarg->wa_Name */

                } /* if wbarg */

            } /* if WBenchMsg && IconBase */

        if (!rc && !CheckSignal(SIGBREAKF_CTRL_C) && (GfxBase = myopenlibrary("graphics.library", 37l))) {

            rc = RETURN_FAIL;

            if (mytask = FindTask(NULL)) {

                if (LocaleBase = myopenlibrary("locale.library", 38l)) {

                    if (locale = OpenLocale(NULL)) {

                        if (!FORMAT) FORMAT = (long)locale->loc_ShortDateTimeFormat;

                        if (timerport = CreateMsgPort()) {

                            if (timereq = CreateIORequest(timerport, sizeof(struct timerequest))) {

                                if (!OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)timereq, 0l)) {

                                    if (scr = LockPubScreen((UBYTE *)PUBSCREEN)) {

                                        DateStamp(&datestamp);
                                        bufferpos = 0;
                                        FormatDate(locale, (STRPTR)FORMAT, &datestamp, &putcharhook);

                                        mywindowtags[P_WA_Width].ti_Data = (ULONG)(TextLength(&scr->RastPort, buffer, strlen(buffer)) + WIDTH_OF_CLOSE_GADGET + space);
                                        if (left != -1) mywindowtags[P_WA_Left].ti_Data = left;
                                        else mywindowtags[P_WA_Left].ti_Data = (ULONG)(scr->Width - mywindowtags[P_WA_Width].ti_Data - WIDTH_OF_DEPTH_GADGET);
                                        if (top != -1) mywindowtags[P_WA_Top].ti_Data = top;
                                        mywindowtags[P_WA_Height].ti_Data = (ULONG)(scr->BarHeight + 1);
                                        mywindowtags[P_WA_PubScreen].ti_Data = (ULONG)scr;

                                        if (win = OpenWindowTagList(NULL, mywindowtags)) {

                                            timereq->tr_node.io_Command = TR_ADDREQUEST;
                                            timereq->tr_time.tv_secs = 0l;
                                            timereq->tr_time.tv_micro = 10l;
                                            SendIO(&(timereq->tr_node));

                                            startpri = SetTaskPri(mytask, priority);

                                            getmessages(win);

                                            SetTaskPri(mytask, (long)startpri);

                                            CloseWindow(win);

                                            rc = 0;

                                            }

                                        UnlockPubScreen(NULL, scr);

                                        }

                                    CloseDevice((struct IORequest *)timereq);

                                    } /* if OpenDevice() */

                                else request(MSG_ATTENTION, RESUME_GADGETS, MSG_ERROR_LIBRARY, TIMERNAME, 0);

                                DeleteIORequest(timereq);

                                } /* if timereq = CreateIORequest() */

                            DeleteMsgPort((struct MsgPort *)timerport);

                            } /* if timerport */

                        else request(MSG_ATTENTION, RESUME_GADGETS, MSG_ERROR_OPENPORT);

                        CloseLocale(locale);

                        }

                    CloseLibrary(LocaleBase);

                    }

                } /* if mytask */

            CloseLibrary(GfxBase);

            } /* if GfxBase */

        if (!WBenchMsg) {
            if (args) FreeArgs(args);
            }
        else {
            if (diskobj) FreeDiskObject(diskobj);
            if (IconBase) CloseLibrary(IconBase);
            }

        CloseLibrary((struct Library *)IntuitionBase);

        } /* if IntutionBase */

    __exit(rc);
}

void __saveds __asm putcharfunc(register __a0 struct Hook *h, register __a2 void *object, register __a1 char c)
{
    if (bufferpos < BUFFERSIZE) ((char *)h->h_Data)[bufferpos++] = c;
}

void __regargs getmessages(struct Window *win)
{
    struct IntuiMessage *msg;
    unsigned long sigreceived;
    unsigned long idcmpsigflag = 1 << win->UserPort->mp_SigBit;
    unsigned long timersigflag = 1 << timerport->mp_SigBit;
    unsigned long sigstowaitfor = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_F | idcmpsigflag | timersigflag;
    unsigned short quit = FALSE;

    while (!quit) {

        sigreceived = Wait(sigstowaitfor);

        if (sigreceived & timersigflag) {
            /*
             *  signal at timerport
             */
            if (GetMsg(timerport) == (struct Message *)timereq) {
                /*
                 *  arrived any message ?
                 */
                timereq->tr_time.tv_secs = updateseconds;
                timereq->tr_time.tv_micro = updatemicros;
                SendIO(&(timereq->tr_node));

                bufferpos = 0;
                DateStamp(&datestamp);
                FormatDate(locale, (STRPTR)FORMAT, &datestamp, &putcharhook);
                SetWindowTitles(win, buffer, (UBYTE*)~0);
                if (WINDOWTOFRONT && (IntuitionBase->FirstScreen == win->WScreen)) WindowToFront(win);
                }
            }

        if (sigreceived & idcmpsigflag) {

            while ((msg = (struct IntuiMessage *)GetMsg(win->UserPort)) && !quit) {
                /*
                 *  check and free message
                 */
                if (msg->Class == CLOSEWINDOW) quit = TRUE;
                ReplyMsg((struct Message *)msg);
                } /* while msg */

            }

        if (sigreceived & SIGBREAKF_CTRL_C) quit = TRUE;
        if (sigreceived & SIGBREAKF_CTRL_F) WindowToFront(win);

        } /* while !quit */

    /*
     *  cancel pending IORequest
     */
    AbortIO(&(timereq->tr_node));
    WaitIO((struct IORequest *)timereq);

}
