/*
 *  WindowShuffle.c
 *
 *  Commodity
 *
 *  Author: Stefan Sticht
 *
 *  Copyright: source is public domain, no copyright
 *
 *  Version history:
 *
 *  V1.00   initial release
 *  V1.02   recompiled with main.c V1.02
 *  V1.03   completly rewritten; shared commodity code thrown away; smaller, uses less CPU time
 *  V1.04   now refuses to activate windows specified with REFUSE
 *          second pair of hotkeys for only Activate without WindowToFront()
 *          changed Priority to 21 to block out intuition for safety reasons
 *  V1.05   some really minor changes
 */

#define VERSION "V1.05"

/********************************************************************
 *                             interfacing                          *
 ********************************************************************/

/*
 *  include files
 */

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <intuition/intuitionbase.h>
#include <libraries/commodities.h>

#include <clib/alib_protos.h>
#include <clib/commodities_protos.h>
#include <pragmas/commodities_pragmas.h>
#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>
#include <clib/exec_protos.h>
#include <pragmas/exec_pragmas.h>
#include <clib/intuition_protos.h>
#include <pragmas/intuition_pragmas.h>

#ifdef DEBUG
#define printf KPrintF
#include <clib/dlib_protos.h>
#endif

/*
 *  prototypes
 */
long request(char *title, char *gadgets, char *text, ...);
struct Library *myopenlibrary(char *name, unsigned long version);
struct Window *lastwindow(struct Window *win);
struct Window *nextwindow(struct Window *win);
struct Window *prevwindow(struct Window *win);
void *retryallocmem(unsigned long size, long mode);
void processmessages(void);

/*
 *  global data defined in other moduls
 *
 *  libraries opened by startup code; basepointers needed by function pragmas
 */
extern struct Library *DOSBase;
extern struct Library *SysBase;

/*
 *  Disable SAS/C CTRL/C handling
 */
void chkabort(void) {}

/********************************************************************
 *                             global data                          *
 ********************************************************************/

#define TT_PREVAC  "PREV_ACTIVE"
#define TT_NEXTAC  "NEXT_ACTIVE"
#define TT_PREVBO  "PREV_BOTH"
#define TT_NEXTBO  "NEXT_BOTH"

/*
 *  definition of all messages (multi language support not completed yet)
 */
#ifdef GERMAN

#define RETRY_GADGETS           "Wiederholen|Abbrechen"
#define RESUME_GADGETS          "Weiter"
#define MSG_OUTOFMEM            "Leider ein Speicherblock von %ld bytes\nnicht alloziert werden!"
#define MSG_LIBRARY_OPENERR     "Die %s (V%ld+) kann nicht geöffnet werden!"
#define COM_NAME                "Fensterln"
#define COM_DESCR               "Nächstes/letztes Fenster aktivieren"
#define TT_BACKDROP             "BACKDROP"
#define TT_NOWINTITLE           "OHNETITEL"
#define TT_REFUSE               "DIESENICHT"
#define MSG_REFUSE_TOO_COMPLEX  "Leider muß " TT_REFUSE " ignoriert werden,\nda der Ausdruck zu komplex ist."
#define NO                      "NEIN"
#define YES                     "JA"

#else

#define RETRY_GADGETS           "Retry|Cancel"
#define RESUME_GADGETS          "Resume"
#define MSG_OUTOFMEM            "Failure allocting %ld bytes of memory!"
#define MSG_LIBRARY_OPENERR     "%s (V%ld+) can't be opened!"
#define COM_NAME                "WindowShuffle"
#define COM_DESCR               "Activate next/previous window"
#define TT_BACKDROP             "BACKDROP"
#define TT_NOWINTITLE           "WITHOUTTITLE"
#define TT_REFUSE               "REFUSE"
#define MSG_REFUSE_TOO_COMPLEX  TT_REFUSE " will be ignored\nas the pattern is too complex!"
#define YES                     "YES"
#define NO                      "NO"

#endif

#define COM_TITLE           COM_NAME " " VERSION
#define CX_PRIORITY         "CX_PRIORITY"
#define DEF_CX_PRIORITY     0

#define DEF_TT_PREVAC       "lcommand lshift j"
#define DEF_TT_NEXTAC       "lcommand lshift k"
#define DEF_TT_PREVBO       "lcommand j"
#define DEF_TT_NEXTBO       "lcommand k"
#define DEF_TT_BACKDROP     NO
#define DEF_TT_NOWINTITLE   NO
#define DEF_TT_REFUSE       ""

/*
 *  data for cback.o
 */
long _stack = 4096l;
char *_procname = COM_NAME;
long _priority = 21l;
long _BackGroundIO = 1;
extern BPTR _Backstdout;

/*
 *  library base pointers
 */
struct IntuitionBase *IntuitionBase;
struct Library *CxBase;
struct Library *IconBase;

/*
 *  message port
 */
struct MsgPort *cxport = NULL;

/*
 *  signal flag
 */
unsigned long cxsigflag = 0l;

/*
 *  programtitle and version for Version command
 */
char versionstring[] ="\0$VER: " COM_NAME " " VERSION;

/*
 *  helpstring
 */
#ifdef GERMAN
char helpstring[] =
"\033[1m" COM_NAME "\033[0m " VERSION " (Public Domain) von Stefan Sticht\n"\
"Aufruf: " COM_NAME " ["\
CX_PRIORITY "=<n>] [" TT_NEXTAC "=<Aktion>] [" TT_PREVAC "=<Aktion>] ["\
TT_NEXTBO "=<Aktion>] [" TT_PREVBO "=<Aktion>] [" TT_BACKDROP "=" YES "|" NO "] ["\
TT_NOWINTITLE "=" YES "|" NO "] [" TT_REFUSE "=<Jokerausdruck>]\n";
#else
char helpstring[] =
"\033[1m" COM_NAME "\033[0m " VERSION " (Public Domain) by Stefan Sticht\n"
"Usage: " COM_NAME " ["\
CX_PRIORITY "=<n>] [" TT_NEXTAC "=<action>] [" TT_PREVAC "=<action>] ["\
TT_NEXTBO "=<action>] [" TT_PREVBO "=<action>] [" TT_BACKDROP "=" YES "|" NO "] ["\
TT_NOWINTITLE "=" YES "|" NO "] [" TT_REFUSE "=<wildcards>]\n";
#endif

/*
 *  the tooltypearray
 */
char **tooltypes;

/*
 *  our broker
 */
CxObj *broker = NULL;

struct NewBroker newbroker = {
    NB_VERSION,                         /* BYTE nb_Version               */
    COM_NAME,                           /* BYTE *nb_Name                 */
    COM_TITLE,                          /* BYTE *nb_Title                */
    COM_DESCR,                          /* BYTE *nb_Descr                */
    NBU_NOTIFY | NBU_UNIQUE,            /* SHORT nb_Unique               */
    0,                                  /* SHORT nb_Flags                */
    0,                                  /* BYTE nb_Pri                   */
    NULL,                               /* struct MsgPort nb_Port        */
    0                                   /* WORD nb_ReservedChannel       */
};

#define NEXTAC  1
#define PREVAC  2
#define NEXTBO  3
#define PREVBO  4

/*
 *  pointer to refuse tokens
 */
char *refusetokens = NULL;
/*
 *  booleans for activation of backdrop windows and windows w/o title
 */
unsigned short backdrop;
unsigned short nowintitle;

/********************************************************************
 *                             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 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
     */
    static 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   */
        };
    va_list ap;
    long rc;

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

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

    /*
     *  win may be NULL
     */
    rc = EasyRequestArgs(NULL, &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 *myopenlibrary(char *name, unsigned long version)
{
    static char errortext[] = MSG_LIBRARY_OPENERR;
    struct Library *libptr;
    long ok = TRUE;

    do {
        if (!(libptr = OpenLibrary((UBYTE *)name, version))) {
            if (IntuitionBase) {
                ok = request(COM_NAME ":", RETRY_GADGETS, errortext, name, version);
                }
            else ok = FALSE;
            }
        } while (!libptr && ok);

    #ifdef DEBUG
    printf("myopenlibrary(%s, %ld) = 0x%lx\n", name, version, libptr);
    #endif
    return(libptr);
}

void *retryallocmem(unsigned long size, long mode)
{
    void *addr;

    do {

        if (!(addr = AllocMem(size, mode))) {

            if (!(request(COM_NAME ":", RETRY_GADGETS, MSG_OUTOFMEM, size)))
                return(NULL);

            }

        } while (!addr);

    return(addr);
}

void main(int argc, char *argv[])
{
    CxObj *nextacfilter = NULL;
    CxObj *prevacfilter = NULL;
    CxObj *nextbofilter = NULL;
    CxObj *prevbofilter = NULL;
    struct Message *msg;
    char *refuse;
    char *nextackey;
    char *prevackey;
    char *nextbokey;
    char *prevbokey;
    unsigned long size = 0l;

    if ((argc > 1) && (*argv[1] == '?')) {
        /*
         *  display help string
         */
        if (_Backstdout) {
            Write(_Backstdout, helpstring, sizeof(helpstring) - 1l);
            Close(_Backstdout);
            }
        return;
        }
    else if (argc && _Backstdout) Close(_Backstdout);

    /*
     *  open required libraries first
     */
    if (IntuitionBase = (struct IntuitionBase *)myopenlibrary("intuition.library", 37l)) {

        if (CxBase = myopenlibrary("commodities.library", 37l)) {

            if (IconBase = myopenlibrary("icon.library", 37l)) {

                /*
                 * create tooltypes array (requires icon.library open!!!)
                 */
                tooltypes = (char **)ArgArrayInit(argc, argv);

                /*
                 *  create our message port
                 */
                if (cxport = CreateMsgPort()) {

                    cxsigflag = 1l << cxport->mp_SigBit;
                    /*
                     * set up some broker data
                     */
                    newbroker.nb_Pri = ArgInt(tooltypes, CX_PRIORITY, DEF_CX_PRIORITY);
                    newbroker.nb_Port = cxport;

                    if (broker = CxBroker(&newbroker, NULL)) {

                        if ((nextackey = ArgString(tooltypes, TT_NEXTAC, DEF_TT_NEXTAC)) &&
                            *nextackey) {

                            if (nextacfilter = HotKey(nextackey, cxport, NEXTAC))
                                AttachCxObj(broker, nextacfilter);

                            } /* if nextackey */

                        if ((prevackey = ArgString(tooltypes, TT_PREVAC, DEF_TT_PREVAC)) &&
                            *prevackey) {

                            if (prevacfilter = HotKey(prevackey, cxport, PREVAC))
                                AttachCxObj(broker, prevacfilter);

                            } /* if prevackey */

                        if ((nextbokey = ArgString(tooltypes, TT_NEXTBO, DEF_TT_NEXTBO)) &&
                            *nextbokey) {

                            if (nextbofilter = HotKey(nextbokey, cxport, NEXTBO))
                                AttachCxObj(broker, nextbofilter);

                            } /* if nextbokey */

                        if ((prevbokey = ArgString(tooltypes, TT_PREVBO, DEF_TT_PREVBO)) &&
                            *prevbokey) {

                            if (prevbofilter = HotKey(prevbokey, cxport, PREVBO))
                                AttachCxObj(broker, prevbofilter);

                            } /* if prevbokey */

                        backdrop = (strcmpi((char *)ArgString(tooltypes, TT_BACKDROP, DEF_TT_BACKDROP),
                                    YES)) ? FALSE : TRUE;

                        nowintitle = (strcmpi((char *)ArgString(tooltypes, TT_NOWINTITLE, DEF_TT_NOWINTITLE),
                                    YES)) ? FALSE : TRUE;

                        refuse = (char *)ArgString(tooltypes, TT_REFUSE, DEF_TT_REFUSE);

                        #ifdef DEBUG
                        if (refuse) printf("WindowShuffle: refuse = %s\n", refuse);
                        #endif

                        if (*refuse && (size = (strlen(refuse) << 1)) &&
                            (refusetokens = retryallocmem(size, 0l))) {

                            #ifdef DEBUG
                            printf("WindowShuffle: size = %ld, refusetokens = 0x%lx\n", size, refusetokens);
                            #endif

                            if (ParsePattern(refuse, refusetokens, size) == -1l)
                                request(COM_NAME ":", RESUME_GADGETS, MSG_REFUSE_TOO_COMPLEX);

                            #ifdef DEBUG
                            printf("WindowShuffle: refusetokens = %s\n", refusetokens);
                            #endif

                            }

                        if ((nextacfilter && !CxObjError(nextacfilter)) ||
                            (prevacfilter && !CxObjError(prevacfilter)) ||
                            (nextbofilter && !CxObjError(nextbofilter)) ||
                            (prevbofilter && !CxObjError(prevbofilter))) {

                            /*
                             *  activate our commodity
                             */
                            ActivateCxObj(broker, 1l);
                            /*
                             *  now watch our numerous ports
                             */
                            processmessages();

                            } /* if !CxObjError() */

                        DeleteCxObjAll(broker);

                        if (refusetokens) FreeMem(refusetokens, size);

                        } /* if broker */

                    #ifdef DEBUG
                    else printf("main(): CxBroker() failed!\n");
                    #endif

                    /*
                     *  delete our message port after replying all pending messages
                     */
                    while (msg = GetMsg(cxport)) ReplyMsg(msg);
                    DeleteMsgPort(cxport);
                    } /* if cxport */

                #ifdef DEBUG
                else printf("main(): CraeteMsgPort() failed!\n");
                #endif

                ArgArrayDone();

                CloseLibrary(IconBase);
                } /* if IconBase */

            CloseLibrary(CxBase);
            } /* if CxBase */

    CloseLibrary((struct Library *)IntuitionBase);
    } /* if IntuitionBase */

} /* main() */

#define PREV    1
#define TOF     2
#define AC      4
#define BO      6

void processmessages(void)
{
    struct Window *win;
    struct Message *msg;
    unsigned long sigreceived;
    unsigned long msgtype;
    unsigned long msgid;
    unsigned long lock;
    unsigned short quit = FALSE;
    unsigned char mode;

    while (!quit) {

        sigreceived = Wait(SIGBREAKF_CTRL_C | cxsigflag);

        #ifdef DEBUG
        printf("processmessages(): signal received\n");
        #endif

        if (sigreceived & SIGBREAKF_CTRL_C) quit = TRUE;

        if (sigreceived & cxsigflag) {

            while (msg = (struct Message *)GetMsg(cxport)) {

                msgid = CxMsgID((CxMsg *)msg);
                msgtype = CxMsgType((CxMsg *)msg);

                ReplyMsg(msg);

                switch (msgtype) {

                    case CXM_IEVENT:
                        mode = 0;
                        switch (msgid) {

                            case PREVAC:    mode = PREV;
                            case NEXTAC:    mode |= AC;
                                            break;

                            case PREVBO:    mode = PREV;
                            case NEXTBO:    mode |= BO;
                                            break;

                            } /* switch msgid */

                        lock = LockIBase(0l);
                        if (mode & PREV) {
                            /*
                             *  get previous window
                             */
                            if (!(win = prevwindow(IntuitionBase->ActiveWindow))) {
                                /*
                                 *  no prev window; get last window;
                                 *  if last window = backdrop || nowintitle ||
                                 *  refusewindow get predecessor of lastwindow
                                 */
                                if ((win = lastwindow(IntuitionBase->ActiveWindow)) &&
                                    ((!backdrop && (win->Flags & WFLG_BACKDROP)) ||
                                    (!nowintitle && !win->Title)) ||
                                    (refusetokens && win->Title && MatchPattern(refusetokens, win->Title)))
                                    win = prevwindow(win);
                                    }
                            }
                        else {
                            /*
                             *  get next window
                             */
                            if (!(win = nextwindow(IntuitionBase->ActiveWindow))) {
                                /*
                                 *  no next window; get first window;
                                 *  if first window = backdrop || nowintitle ||
                                 *  refusewindow get first window->NextWindow
                                 */
                                if ((win = IntuitionBase->ActiveScreen->FirstWindow) &&
                                    ((!backdrop && (win->Flags & WFLG_BACKDROP)) ||
                                    (!nowintitle && !win->Title)) ||
                                    (refusetokens && win->Title && MatchPattern(refusetokens, win->Title)))
                                    win = nextwindow(win);
                                }
                            }
                        UnlockIBase(lock);

                        /*
                         *  now activate and windowtofront the window if (win != NULL)
                         */
                        if (win) {

                            if ((mode & AC) && (win != IntuitionBase->ActiveWindow))
                                ActivateWindow(win);
                            if ((mode & TOF) && (!(win->Flags & WFLG_BACKDROP)))
                                WindowToFront(win);

                            }
                        break;

                    case CXM_COMMAND:
                        switch (msgid) {

                            case CXCMD_UNIQUE:
                            case CXCMD_KILL:
                                quit = TRUE;
                                break;

                            case CXCMD_DISABLE:
                                ActivateCxObj(broker, 0l);
                                break;

                            case CXCMD_ENABLE:
                                ActivateCxObj(broker, 1l);
                                break;

                            }
                        break;

                    } /* switch msgtype */

                } /* while CxMsg */

            } /* if (sigreceived & cxsigflag) */

        } /* while !quit */

    ActivateCxObj(broker, 0l);
}

/*
 *  commodity functions
 */

/*
 *  get next window
 */
struct Window *nextwindow(register struct Window *win)
{
    register struct Window *w = NULL;

    if (win) {

        while (!w && win->NextWindow) {

            w = win = win->NextWindow;

            if ((!backdrop && (w->Flags & WFLG_BACKDROP)) ||
                (!nowintitle && !w->Title) ||
                (refusetokens && w->Title && MatchPattern(refusetokens, w->Title)))
                w = NULL;

            } /* while !w */

        } /* if win */

    return(w);
}

/*
 *  get previous window
 */
struct Window *prevwindow(register struct Window *win)
{
    register struct Window *w = NULL;

    if (win) {

        while (!w && (win != win->WScreen->FirstWindow)) {

            for (w = win->WScreen->FirstWindow; w->NextWindow != win; w = w->NextWindow);

            if ((!backdrop && (w->Flags & WFLG_BACKDROP)) ||
                (!nowintitle && !w->Title) ||
                (refusetokens && w->Title && MatchPattern(refusetokens, w->Title))) {

                win = w;
                w = NULL;

                }

            } /* while !w */

        } /* if win */

    return(w);
}

/*
 *  get last window
 */
struct Window *lastwindow(register struct Window *win)
{
    if (win) {

        while (win->NextWindow) win = win->NextWindow;

        } /* if win */

    return(win);
}
