/* Copyright ) Darin Johnson, 1989 */

/* This file has routines to allow us to 'monitor' an Intuition port */

/* What happens, is that we create a port, myPort, and set things up */
/* so that GetMsg works normally for myPort, and the original port,  */
/* winPort.  However, PutMsg to myPort will put the new message      */
/* on winPort, and vice-versa.  Basically, the heads of both message */
/* lists are normal, but the two tails have been swapped.  Since     */
/* GetMsg uses the head of the list, it acts normally.  PutMsg uses  */
/* the tail of the list, so it acts differently.  (the sigBit and    */
/* sigTasks have also been swapped)                                  */

/* What this means, is that anything PutMsg'ed to the winPort, will  */
/* signal us, and we can GetMsg it.  When we are finished looking at */
/* that message, we can give it to winPort by PutMsg'ing to myPort.  */

#include <exec/ports.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuitionbase.h>
#include <devices/inputevent.h>
#include <functions.h>
#include "mymenu.h"

#ifdef DO_WB
extern struct MsgPort *wb_reply_port;
extern int wb_cnt;
#endif

static struct MsgPort *winPort;
static struct List *winMsgList;
static LONG winSignal, our_signal;
static LONG winMask;
static BYTE orig_pri;

/* our port */
static struct MsgPort myPort =
{
   {NULL, NULL, NT_MSGPORT, 0, NULL},
   PA_SIGNAL,
   0,
   NULL,
   {NULL, NULL, NULL, NT_MESSAGE, 0}
};

static struct List *myMsgList = &(myPort.mp_MsgList);

/* fiddle with the two ports, setting things up */
make_MsgPort()
{
  Forbid();
    /* get the info we need */
  winPort    = MM->WBWindow->UserPort;
  winMsgList = &(winPort->mp_MsgList);
  winSignal  = winPort->mp_SigBit;
  winMask    = 1L << winSignal;

    /* setup our port */  
  myPort.mp_SigBit    = winSignal;
  myPort.mp_SigTask   = winPort->mp_SigTask;

    /* flip things around */
  winPort->mp_SigTask = MM->handler_task;
  myMsgList->lh_Head = (struct Node *)&(winMsgList->lh_Tail);
  myMsgList->lh_TailPred = winMsgList->lh_TailPred;
  winMsgList->lh_TailPred = (struct Node *)&(myMsgList->lh_Head);
  myMsgList->lh_TailPred->ln_Succ = (struct Node *)&(myMsgList->lh_Tail);

  Permit();

    /* prevent deadlocks */
  orig_pri = SetTaskPri(MM->handler_task,
		myPort.mp_SigTask->tc_Node.ln_Pri+1);
}

/* Restore things the way they were before make_MsgPort()           */
/* Note that we don't refer to winPort.  This is because we         */
/* may have had someone else 'monitor' this port (such as MonIDCMP) */
/* besides us.                                                      */
del_MsgPort() {
  struct Message *tmpMsg;
  struct MsgPort *tmpPort;
  
  Forbid();
    /* clean out our list */
  while ((tmpMsg = GetMsg(&myPort)) != NULL) PutMsg(&myPort, tmpMsg);

     /* find MsgPort that myMsgList->lh_Head belongs to */
     /*      (the port we've been PutMsg'ing to)        */
  tmpPort = (struct MsgPort *) (
    (UBYTE*) &myPort - (UBYTE*) &(myPort.mp_MsgList.lh_Tail)
    + (UBYTE*)myMsgList->lh_Head);

    /* restore things */
  myMsgList->lh_Head->ln_Pred = myMsgList->lh_TailPred;
  myMsgList->lh_TailPred->ln_Succ = myMsgList->lh_Head;
  tmpPort->mp_SigTask = myPort.mp_SigTask;
  Permit();
  
  SetTaskPri(MM->handler_task, orig_pri);
}

/* do all appropriate initialization of monitor */
BOOL setup_mon()
{
  make_MsgPort();
    /* we need to allocate the same signal, so that it doesn't get
       allocated to something else, messing us up */
  our_signal = AllocSignal(winSignal);
  if (our_signal != winSignal) {
    finish_mon();
    return FALSE;
  }
  return TRUE;
}

/* clean up everything */
finish_mon() {
  del_MsgPort();
  FreeSignal(our_signal);
}

/* See if we handle this event.  If so, return ext_MenuItem structure */
struct ext_MenuItem *
match_item(Class, Code)
  ULONG Class;
  USHORT Code;
{
  register struct ext_MenuItem *tmp;
  if (Class != MENUPICK || Code == MENUNULL)
    return NULL;
  for (tmp = MM->item_list; tmp; tmp=tmp->next_item)
    if (tmp->id == Code)
      return tmp;
  return NULL;
}

/* The actual monitor.  Just keep waiting for messages, and acting on them */
/* Note that if we don't use a certain IDCMP message, we PutMsg it to get  */
/* it back to the window it was meant for.                                 */
monitor()
{
  register struct IntuiMessage *msg;
  register void *item;
  register ULONG mask, wb_mask;

#ifdef DO_WB
  winMask |= (wb_mask = (1L << wb_reply_port->mp_SigBit));
#endif

  winMask |= SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D;

  while (TRUE) {
    mask = Wait(winMask);
    if (mask & SIGBREAKF_CTRL_C) {
	/* caught ^C, time to close up */
      break;
    }
    if (mask & SIGBREAKF_CTRL_D) {
        /* reparse menus */
      del_menu_strip();
      Signal(MM->parent_task, (1 << MM->parent_sig));
      Wait(SIGBREAKF_CTRL_D);
      add_menu_strip();
      continue;
    }
#ifdef DO_WB
    if (mask & wb_mask) {
        /* caught a reply from a WorkBench program exitting */
      register struct Message *wb_msg;
      while ((wb_msg = GetMsg(wb_reply_port)) != NULL) {
        wbfree(wb_msg);
	wb_cnt = (wb_cnt>0 ? (wb_cnt-1) : 0);
      }
      continue;
    }
#endif
      /* if we got here, we have an IDCMP message */
    while ((msg = (struct IntuiMessage *)GetMsg(&myPort)) != NULL) {
          /* check if we deal with this or not */
      if ((item=(void *)match_item(msg->Class, msg->Code))!=NULL) {
        ReplyMsg(msg);		/* always reply */
        run_item(item);		/* run the indicated program */
      } else {
        PutMsg(&myPort, msg);	/* pass on to original destination */
      }
    }
  }
}
