/*
 *  toBack&Front.c
 *
 *  Commodity
 *
 *  Author: Stefan Sticht
 *
 *  Copyright: source is public domain, no copyright
 *
 *  Version history:
 *
 *  V1.00   initial release
 *  V1.01   some minor changes
 *  V1.02   changed myparseix for commodities.library 37.27
 *          uses now utility.library's Stricmp()
 *  V1.03   added some LockIBase()
 */

#define VERSION "V1.03"

/********************************************************************
 *                             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>
#include <clib/layers_protos.h>
#include <pragmas/layers_pragmas.h>
#include <clib/utility_protos.h>
#include <pragmas/utility_pragmas.h>

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

/*
 *  prototypes
 */
struct Library *myopenlibrary(char *name, unsigned long version);
void processmessages(void);
void backorfront(unsigned short mode);

/* located in myparseix.c */
long myparseix(char *description, IX *ix);

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

/*
 *  definition of messages
 */
#ifdef GERMAN
/*
 *  Wenn jemand bessere Übersetzungsvorschläge hat, soll er mir die bitte mitteilen!
 *  (This is german. If you don't understand, doesn't matter)
 */
#define COM_NAME                "toBack&Front"
#define RETRY_GADGETS           "Wiederholen|Abbrechen"
#define RESUME_GADGETS          "Weiter"
#define MSG_LIBRARY_OPENERR     "Die %s (V%ld+) kann nicht geöffnet werden!"
#define COM_DESCR               "Fenster o. Schirm nach hinten o. vorne"
#define YES                     "JA"
#define NO                      "NEIN"

#else

#define COM_NAME                "toBack&Front"
#define RETRY_GADGETS           "Retry|Cancel"
#define RESUME_GADGETS          "Resume"
#define MSG_LIBRARY_OPENERR     "%s (V%ld+) can't be opened!"
#define COM_DESCR               "Window or screen to back or front"
#define YES                     "YES"
#define NO                      "NO"

#endif

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

#define TT_FRONT_ACTION         "FRONT_ACTION"
#define TT_FRONT_CLICKS         "FRONT_CLICKS"
#define TT_FRONT_REMOVE         "FRONT_REMOVE"
#define DEF_TT_FRONT_ACTION     "rawmouse leftbutton lbuttoncode"
#define DEF_TT_FRONT_CLICKS     2
#define DEF_TT_FRONT_REMOVE     NO
#define TT_BACK_ACTION          "BACK_ACTION"
#define TT_BACK_CLICKS          "BACK_CLICKS"
#define TT_BACK_REMOVE          "BACK_REMOVE"
#define DEF_TT_BACK_ACTION      "rawmouse lalt leftbutton lbuttoncode"
#define DEF_TT_BACK_CLICKS      2
#define DEF_TT_BACK_REMOVE      YES

/*
 *  data for cback.o
 */
#ifndef DEBUG
long _stack = 2048l;
char *_procname = COM_NAME;
long _priority = 21l;
long _BackGroundIO = 1;
extern BPTR _Backstdout;
#endif

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

/*
 *  message ports
 */
struct MsgPort *cxport;

/*
 *  signal flags
 */
unsigned long cxsigflag;

/*
 *  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_FRONT_ACTION "=<Aktion>]"\
                    " [" TT_FRONT_REMOVE "=" YES "|" NO "] [" TT_FRONT_CLICKS "=<n>] ["\
                    TT_BACK_ACTION "=<Aktion>] [" TT_BACK_REMOVE "=" YES "|" NO
                    "] [" TT_BACK_CLICKS "=<n>]\n";
#else
char helpstring[] = "\033[1m" COM_NAME "\033[0m " VERSION " (Public Domain) by Stefan Sticht\n"
                    "Usage: " COM_NAME " [" CX_PRIORITY "=<n>] [" TT_FRONT_ACTION "=<action>]"\
                    " [" TT_FRONT_REMOVE "=" YES "|" NO "] [" TT_FRONT_CLICKS "=<n>] ["\
                    TT_BACK_ACTION "=<action>] [" TT_BACK_REMOVE "=" YES "|" NO
                    "] [" TT_BACK_CLICKS "=<n>]\n";
#endif

/*
 *  number of clicks required
 */
unsigned char requiredbackclicks;
unsigned char requiredfrontclicks;

#define FRONT 1
#define BACK  2

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

/*
 *  our broker
 */
CxObj *broker;

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

IX backix = {
    IX_VERSION,                 /* UBYTE ix_version     */
    0,                          /* UBYTE ix_Class       */
    0,                          /* UWORD ix_Code        */
    0,                          /* UWORD ix_CodeMask    */
    0,                          /* UWORD ix_Qualifier   */
    0,                          /* UWORD ix_QualMask    */
    0                           /* UWORD ix_QualSame    */
    };

IX frontix = {
    IX_VERSION,                 /* UBYTE ix_version     */
    0,                          /* UBYTE ix_Class       */
    0,                          /* UWORD ix_Code        */
    0,                          /* UWORD ix_CodeMask    */
    0,                          /* UWORD ix_Qualifier   */
    0,                          /* UWORD ix_QualMask    */
    0                           /* UWORD ix_QualSame    */
    };

/********************************************************************
 *                             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 main(int argc, char *argv[])
{
    CxObj *obj;
    CxObj *frontfilter = NULL;
    CxObj *backfilter = NULL;
    char *frontaction;
    char *backaction;
    struct Message *msg;

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

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

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

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

                if (LayersBase = myopenlibrary("layers.library", 37l)) {

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

                    backaction = ArgString(tooltypes, TT_BACK_ACTION, DEF_TT_BACK_ACTION);
                    frontaction = ArgString(tooltypes, TT_FRONT_ACTION, DEF_TT_FRONT_ACTION);
                    if (backaction && *backaction) {
                        myparseix(backaction, &backix);
                        requiredbackclicks = (unsigned char)ArgInt(tooltypes, TT_BACK_CLICKS, DEF_TT_BACK_CLICKS);
                        }
                    else backaction = NULL;
                    if (frontaction && *frontaction) {
                        myparseix(frontaction, &frontix);
                        requiredfrontclicks = (unsigned char)ArgInt(tooltypes, TT_FRONT_CLICKS, DEF_TT_FRONT_CLICKS);
                        }
                    else frontaction = NULL;

                    if (frontaction || backaction) {
                        /*
                         *  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 (frontaction && (frontfilter = CxFilter(NULL))) {
                                    AttachCxObj(broker, frontfilter);
                                    SetFilterIX(frontfilter, &frontix);
                                    }

                                if (backaction && (backfilter = CxFilter(NULL))) {
                                    AttachCxObj(broker, backfilter);
                                    SetFilterIX(backfilter, &backix);
                                    }

                                if (frontfilter || backfilter) {

                                    if (UtilityBase = myopenlibrary("utility.library", 37l)) {

                                        if (frontfilter && (obj = CxSender(cxport, FRONT))) {
                                            AttachCxObj(frontfilter, obj);
                                            /*
                                             *  check if input-event shall be removed
                                             */
                                            if (!Stricmp((char *)ArgString(tooltypes, TT_FRONT_REMOVE, DEF_TT_FRONT_REMOVE), YES)) {
                                                #ifdef DEBUG
                                                printf("main(): installing NULL-translator for front-action\n");
                                                #endif
                                                /*
                                                 *  install a NULL-translator, which removes the ie
                                                 */
                                                if (obj = CxTranslate(NULL)) AttachCxObj(frontfilter, obj);
                                                }
                                            }

                                        if (backfilter && (obj = CxSender(cxport, BACK))) {
                                            AttachCxObj(backfilter, obj);
                                            /*
                                             *  check if input-event shall be removed
                                             */
                                            if (!Stricmp((char *)ArgString(tooltypes, TT_FRONT_REMOVE, DEF_TT_FRONT_REMOVE), YES)) {
                                                #ifdef DEBUG
                                                printf("main(): installing NULL-translator for front-action\n");
                                                #endif
                                                /*
                                                 *  install a NULL-translator, which removes the ie
                                                 */
                                                if (obj = CxTranslate(NULL)) AttachCxObj(backfilter, obj);
                                                }
                                            }

                                        /*
                                         *  close library here, we don't need it any longer
                                         */
                                        CloseLibrary(UtilityBase);

                                        if ((frontfilter && !CxObjError(frontfilter)) ||
                                            (backfilter && !CxObjError(backfilter))) {
                                            /*
                                             *  activate our commodity
                                             */
                                            ActivateCxObj(broker, 1l);
                                            /*
                                             *  now watch our numerous ports
                                             */
                                            processmessages();

                                            } /* if !CxObjError() */

                                        } /* if UtilityBase */

                                    } /* if frontfilter || backfilter */

                                DeleteCxObjAll(broker);

                                } /* 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(): CreateMsgPort() failed!\n");
                        #endif

                        } /* if (frontaction || backaction) */

                    ArgArrayDone();

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

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

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

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

} /* main() */

void processmessages(void)
{
    static unsigned long lastfrontmicros = 0l;
    static unsigned long lastfrontsecs = 0l;
    static unsigned long lastbackmicros = 0l;
    static unsigned long lastbacksecs = 0l;
    static unsigned char frontclicks = 0;
    static unsigned char backclicks = 0;
    struct InputEvent *ie;
    struct Message *msg;
    unsigned long sigreceived;
    unsigned long msgtype;
    unsigned long msgid;
    unsigned long micros;
    unsigned long secs;
    unsigned char quit = FALSE;

    if (requiredfrontclicks == 1) frontclicks = 1;
    if (requiredbackclicks == 1) backclicks = 1;

    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);
                
                if ((msgtype == CXM_IEVENT) &&
                    (ie = (struct InputEvent *)CxMsgData((CxMsg *)msg))) {
                    /*
                     *  copy the interesting data of the inputevent
                     */
                    secs = ie->ie_TimeStamp.tv_secs;
                    micros = ie->ie_TimeStamp.tv_micro;
                    #ifdef DEBUG
                    if (msgid == FRONT) printf("action = FRONT\n");
                    else printf("action = BACK\n");
                    printf("ie_Class = 0x%lx\n", ie->ie_Class);
                    printf("ie_SubClass = 0x%lx\n", ie->ie_SubClass);
                    printf("ie_Code= 0x%lx\n", ie->ie_Code);
                    printf("ie_Qualifier = 0x%lx\n", ie->ie_Qualifier);
                    printf("secs = 0x%lx\n", secs);
                    printf("micros = 0x%lx\n", micros);
                    #endif
                    }

                ReplyMsg(msg);

                switch (msgtype) {

                    case CXM_IEVENT:
                        switch (msgid) {

                            case FRONT:
                                if (requiredfrontclicks > 1) {
                                    if (DoubleClick(lastfrontsecs, lastfrontmicros, secs, micros))
                                        frontclicks++;
                                    else frontclicks = 1;

                                    lastfrontsecs = secs;
                                    lastfrontmicros = micros;
                                    }
                                if (frontclicks == requiredfrontclicks) {
                                    backorfront(FRONT);
                                    if (requiredfrontclicks > 1) frontclicks = 0;
                                    }
                                break;

                            case BACK:
                                if (requiredbackclicks > 1) {
                                    if (DoubleClick(lastbacksecs, lastbackmicros, secs, micros))
                                        backclicks++;
                                    else backclicks = 1;

                                    lastbacksecs = secs;
                                    lastbackmicros = micros;
                                    }
                                if (backclicks == requiredbackclicks) {
                                    backorfront(BACK);
                                    if (requiredbackclicks > 1) backclicks = 0;
                                    }
                                break;

                            }

                    case CXM_COMMAND:
                        switch (msgid) {

                            case CXCMD_UNIQUE:
                            case CXCMD_KILL:
                                quit = TRUE;

                            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);
}

void backorfront(unsigned short mode)
{
    unsigned long lock;
    register struct Screen *scr;
    register struct Window *win;
    register struct Layer *layer;

    #ifdef DEBUG
    if (mode == BACK) printf("backorfront(BACK)\n");
    else printf("backorfront(FRONT)\n");
    #endif

    /*
     *  here we go: find screen
     */
    lock = LockIBase(0l);
    for (scr = IntuitionBase->FirstScreen;
         scr && (scr->TopEdge > 0) && (scr->MouseY < 0);
         scr = scr->NextScreen);

    if (scr) {

        #ifdef DEBUG
        printf("backorfront(): scr = 0x%lx\n", scr);
        #endif

        /*
         *  get layer
         */
        LockLayerInfo(&scr->LayerInfo);
        layer = WhichLayer(&scr->LayerInfo, (long)scr->MouseX, (long)scr->MouseY);
        UnlockLayerInfo(&scr->LayerInfo);

        #ifdef DEBUG
        printf("backorfront(): layer = 0x%lx\n", layer);
        #endif

        if (layer && layer != scr->BarLayer) {

            if ((win = (struct Window *)layer->Window) && !(win->Flags & BACKDROP)) {

                if (win->NextWindow || win->WScreen->FirstWindow != win) {

                    #ifdef DEBUG
                    printf("backorfront(): using win 0x%lx for action\n", win);
                    #endif

                    UnlockIBase(lock);
                    if (mode == BACK) WindowToBack(win);
                    else WindowToFront(win);

                    }

                else UnlockIBase(lock);

                } /* if win */

            else {
                /*
                 *  no win or win = backdrop => switch screen to back
                 */
                #ifdef DEBUG
                printf("backorfront(): using scr 0x%lx for action (no win)\n", scr);
                #endif

                UnlockIBase(lock);
                if (mode == BACK) ScreenToBack(scr);
                else if (scr != IntuitionBase->FirstScreen) ScreenToFront(scr);

                }

            } /* if layer */

        else {
            /*
             *  no layer:
             */
            #ifdef DEBUG
            printf("backorfront(): using scr 0x%lx for action (no layer)\n", scr);
            #endif

            UnlockIBase(0l);
            if (mode == BACK) ScreenToBack(scr);
            else if (scr != IntuitionBase->FirstScreen) ScreenToFront(scr);

            }

        } /* if scr */

    else UnlockIBase(0l);

}
