#include "PopUpMenu.h"
#include "Version.h"

#define REPLYPORTNAME  "Reply"
#define IMPDEVPORTNAME "ImpDev"
#define TIMERPORTNAME  "Timer"
#define QUITPORTNAME   "Quit"
#define INPHANDLNAME   "PopUpMenu"
#define SEMAPHORENAME  "PopUpMenu"

#define POPUPMSG       "\x9B0;33mPopUpMenu\x9B0m "
#define KILLMSG        "removed.\n"
#define STARTMSG       "installed.\n\xA9 Martin Adrian 1990\n"
#define FAILMSG        "failed.\n"

/* Let the text stay for 3s if started from WorkBench */
#define DELAYTIME      150

#define INPHANDLPRI    53

/* Must use kickstart 1.2 or higher */
#define LIBVERSION     33

/* don't know how to find these in C */
#define LVOSetMenuStrip   -0x108
#define LVOClearMenuStrip -0x36
#define LVOOnMenu	  -0xc0
#define LVOOffMenu	  -0xb4

/* this is for errors in proto/exec.h */
#undef RemSemaphore
#pragma syscall RemSemaphore 25e 901

VOID InitPopUpMenu()
{

  IMPORT struct DosLibrary     *DosBase;
  IMPORT struct IntuitionBase  *IntuitionBase;
  IMPORT struct GfxBase        *GfxBase;
  IMPORT struct LayersBase     *LayersBase;

  IMPORT struct IOStdReq       *InputReqBlock;
  IMPORT struct MsgPort        *TimerPort;
  IMPORT struct timerequest    *TimerReqBlock;
  IMPORT LONGBITS		TimerSignal;
  IMPORT struct MsgPort        *ReplyPort;
  IMPORT struct SignalSemaphore PopUpSemaphore;
  IMPORT __fptr OldSetMenuStrip, OldClearMenuStrip, OldOnMenu, OldOffMenu;
  IMPORT BPTR StdOut;
  IMPORT BPTR PopUpSeg;

  struct MsgPort      *InputDevPort;
  struct Interrupt    InputReqData;
  struct SignalData   InputSignals;
  LONG		      MenuUpSigNum, MenuDownSigNum;
  LONG		      MouseMovedSigNum, SelectDownSigNum;

  geta4();   /* load global database */

  Write(StdOut,POPUPMSG,sizeof(POPUPMSG));

  /***********************************
   * see if we are already installed *
   ***********************************/
  if (!(ReplyPort = FindPort(REPLYPORTNAME))) {
    if (!(ReplyPort = MyCreatePort(REPLYPORTNAME)))
      goto CleanUp13;
  }
  else {
    /* yes, popupmenues already installed, tell running task to quit */
    struct MsgPort *const QuitPort = MyCreatePort(QUITPORTNAME);
    struct IntuiMessage *Message;

    if (QuitPort) {
      if (Message = BuildIntuiMsg(QuitPort,QUITPOPUPMENU,NULL)) {
	/* Send quitmessage */
	PutMsg(ReplyPort, (struct Message *)Message);
	/* Wait for reply */
	WaitPort(QuitPort);
	/* get rid of the message */
	GetMsg(QuitPort);
	FreeMem(Message,sizeof(struct IntuiMessage));

	WriteAndClose(KILLMSG, sizeof(KILLMSG));
      }
      MyDeletePort(QuitPort);
    }
    goto CleanUp13;
  }

  /******************
   * open libraries *
   ******************/
  if (!(IntuitionBase = (struct IntuitionBase *)
			OpenLibrary("intuition.library", LIBVERSION)))
    goto CleanUp12;
  if (!(GfxBase       = (struct GfxBase *)
			OpenLibrary("graphics.library", LIBVERSION)))
    goto CleanUp11;
  if (!(LayersBase    = (struct LayersBase *)
			OpenLibrary("layers.library", LIBVERSION)))
    goto CleanUp10;

  /************************
   * Allocate our signals *
   ************************/
  if ((MenuUpSigNum     = AllocSignal(-1)) == -1)
    goto CleanUp9;
  if ((MenuDownSigNum   = AllocSignal(-1)) == -1)
    goto CleanUp8;
  if ((MouseMovedSigNum = AllocSignal(-1)) == -1)
    goto CleanUp7;
  if ((SelectDownSigNum = AllocSignal(-1)) == -1)
    goto CleanUp6;

 /****************************************
  * Build connection to the input.device *
  ****************************************/
  if (!(InputDevPort  = MyCreatePort(IMPDEVPORTNAME)))
    goto CleanUp5;
  if ((InputReqBlock = (struct IOStdReq *)
		       AllocMem(sizeof(struct IOStdReq),
		       MEMF_CLEAR | MEMF_PUBLIC)) == NULL)
    goto CleanUp4;

  InputReqBlock->io_Message.mn_Node.ln_Type = NT_MESSAGE;
  InputReqBlock->io_Message.mn_Length	    = sizeof(struct IOStdReq);
  InputReqBlock->io_Message.mn_ReplyPort    = InputDevPort;

  if (OpenDevice("input.device",0,(struct IORequest *)InputReqBlock,0))
    goto CleanUp3;

  /****************************************
   * Bulid connection to the timer.device *
   ****************************************/
  if (!(TimerPort = MyCreatePort(TIMERPORTNAME)))
    goto CleanUp3x3;
  TimerSignal = 1L << TimerPort->mp_SigBit;

  if (!(TimerReqBlock = (struct timerequest *)
		       AllocMem(sizeof(struct timerequest),
		       MEMF_CLEAR | MEMF_PUBLIC)))
    goto CleanUp3x2;

  TimerReqBlock->tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
  TimerReqBlock->tr_node.io_Message.mn_Length	    = sizeof(struct timerequest);
  TimerReqBlock->tr_node.io_Message.mn_ReplyPort    = TimerPort;

  if (OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)TimerReqBlock,0))
    goto CleanUp3x1;

  /* Start Timer (just to be sure that at least one request is sent. */
  /*		  CheckIO doesn't work otherwise, i think) */

  QueTimer();


  /********************
   * Make a semaphore *
   ********************/
  PopUpSemaphore.ss_Link.ln_Name = /*SEMAPHORENAME*/ NULL;
  PopUpSemaphore.ss_Link.ln_Pri  = 0;
/*
  AddSemaphore(&PopUpSemaphore);
*/
  InitSemaphore(&PopUpSemaphore);
  /**************************************************
   * patch intuition functions to use our semaphore *
   **************************************************/
  if (!(OldSetMenuStrip = SetFunction((struct Library *)IntuitionBase,
				      LVOSetMenuStrip,MySetMenuStrip)))
    goto CleanUp2x4;
  if (!(OldClearMenuStrip = SetFunction((struct Library *)IntuitionBase,
					LVOClearMenuStrip,MyClearMenuStrip)))
    goto CleanUp2x3;
  if (!(OldOnMenu = SetFunction((struct Library *)IntuitionBase,
				LVOOnMenu,MyOnMenu)))
    goto CleanUp2x2;
  if (!(OldOffMenu = SetFunction((struct Library *)IntuitionBase,
				 LVOOffMenu,MyOffMenu)))
    goto CleanUp2x1;

  /**********************************
   * init data for the inputhandler *
   **********************************/
  InputSignals.PopUpMenuTask = FindTask(0);
  InputSignals.MenuUpSig     = 1L << MenuUpSigNum;
  InputSignals.MenuDownSig   = 1L << MenuDownSigNum;
  InputSignals.MouseMovedSig = 1L << MouseMovedSigNum;
  InputSignals.SelectDownSig = 1L << SelectDownSigNum;
  InputSignals.Down	     = FALSE;  /* menubutton is not down. (who cares) */

  /****************************
   * startup the inputhandler *
   ****************************/
  InputReqData.is_Node.ln_Pri  = INPHANDLPRI;	    /* must come before intuition */
  InputReqData.is_Node.ln_Name = INPHANDLNAME;

  InputReqData.is_Data	       = (APTR)&InputSignals;
  InputReqData.is_Code	       = (VOID *)PopUpHandler;

  InputReqBlock->io_Command = IND_ADDHANDLER;
  InputReqBlock->io_Data    = (APTR)&InputReqData;

  DoIO((struct IORequest *)InputReqBlock);

  /***************************************
   * tell the user that everything is ok *
   ***************************************/
  WriteAndClose(VERSION STARTMSG, sizeof(VERSION STARTMSG));

  PopUpMainLoop(&InputSignals);

CleanUp1:

  /* remove inputhandler */
  InputReqBlock->io_Command = IND_REMHANDLER;
  InputReqBlock->io_Data    = (APTR)&InputReqData;

  DoIO((struct IORequest *)InputReqBlock);

  /* restore intuition functions */
  SetFunction((struct Library *)IntuitionBase,LVOOffMenu,OldOffMenu);
CleanUp2x1:
  SetFunction((struct Library *)IntuitionBase,LVOOnMenu,OldOnMenu);
CleanUp2x2:
  SetFunction((struct Library *)IntuitionBase,LVOClearMenuStrip,OldClearMenuStrip);
CleanUp2x3:
  SetFunction((struct Library *)IntuitionBase,LVOSetMenuStrip,OldSetMenuStrip);
CleanUp2x4:

  /* remove semaphore */
/*  RemSemaphore(&PopUpSemaphore);*/

  /* close timer.device */
  CloseDevice((struct IORequest *)TimerReqBlock);
CleanUp3x1:
  FreeMem(TimerReqBlock,sizeof(struct timerequest));
CleanUp3x2:
  MyDeletePort(TimerPort);
CleanUp3x3:

  /* close input.device */
  CloseDevice((struct IORequest *)InputReqBlock);
CleanUp3:
  DeleteStdIO(InputReqBlock);
CleanUp4:
  MyDeletePort(InputDevPort);
CleanUp5:

  /* Free allocated signals */
  FreeSignal(SelectDownSigNum);
CleanUp6:
  FreeSignal(MouseMovedSigNum);
CleanUp7:
  FreeSignal(MenuDownSigNum);
CleanUp8:
  FreeSignal(MenuUpSigNum);
CleanUp9:

  /* close libraries */
  CloseLibrary((struct Library *)LayersBase);
CleanUp10:
  CloseLibrary((struct Library *)GfxBase);
CleanUp11:
  CloseLibrary((struct Library *)IntuitionBase);
CleanUp12:
  MyDeletePort(ReplyPort);
CleanUp13:
  if (StdOut)
    WriteAndClose(FAILMSG,sizeof(FAILMSG));
  if (PopUpSeg) {
    /* if loaded from CLI unload us */
    Forbid();
    UnLoadSeg(PopUpSeg);
  }
  CloseLibrary((struct Library *)DOSBase);
}

/***************************************
 * WriteAndClose(Text,Length)          *
 *				       *
 * Input:			       *
 *   Text   - Text to write to StdOut. *
 *   Length			       *
 ***************************************/
VOID WriteAndClose(Text, Length)
  STRPTR Text;
  ULONG Length;
{
  IMPORT BPTR StdOut;

  Write(StdOut,Text,Length);
  Delay(DELAYTIME);
  Close(StdOut);
  StdOut = NULL;
}

/************************************************
 * PopUpMainLoop(InputSignals,ReplyPort)        *
 *						*
 * Input:					*
 *   InputSignals - Allocated signals.		*
 *   ReplyPort	  - Port for MENUVERIFY replies *
 * Output:					*
 *   none					*
 ************************************************/
VOID PopUpMainLoop(InputSignals)
  struct SignalData *const InputSignals;
{
  IMPORT struct Window	*ActiveWindow;
  IMPORT struct Screen	*Screen;
  IMPORT struct Menu	*Menues;
  IMPORT struct MsgPort *ReplyPort;

  const LONGBITS ReplySig    = 1L << (LONG)ReplyPort->mp_SigBit;

  WORD	NrOfMessages = 0;
  WORD	Flags = 0;

  FOREVER {
    const LONGBITS SignalBits = Wait(ReplySig |
				     InputSignals->MenuUpSig |
				     InputSignals->MenuDownSig);

    if (SignalBits & InputSignals->MenuUpSig) {
      ActiveWindow = NULL;
      Flags &= QUIT;
    }

    if (SignalBits & ReplySig) {
      struct IntuiMessage *Message;

      while (Message = (struct IntuiMessage *)GetMsg(ReplyPort)) {
	if (Message->Class & MENUVERIFY) {
	  if (Message->IDCMPWindow == ActiveWindow)
	    if (Message->Code == MENUCANCEL)
	      Flags &= QUIT;   /* Verify not OK */
	    else
	      Flags |= VERIFYOK;
	  NrOfMessages--;
	  FreeMem(Message,sizeof(struct IntuiMessage));
	}
	else { /* Message->Class == QUITPOPUPMENU or some strange message */
	  ReplyMsg((struct Message *)Message); /* Message does not belong to this task */
	  Flags |= QUIT;
	}

      } /* while */
      if (NrOfMessages == 0) {
	if (Flags & QUIT)
	  break;
	if (Flags & VERIFYOK) {
	  PopUpMenu(InputSignals);
	  Flags = 0;
	}
      }
    }

    if ((SignalBits & (InputSignals->MenuUpSig |
		       InputSignals->MenuDownSig)) == InputSignals->MenuDownSig) {
      const LONG  Lock = LockIBase(0);

      ActiveWindow = IntuitionBase->ActiveWindow;

      if (ActiveWindow AND
	 !(ActiveWindow->Flags & RMBTRAP) AND
	 (ActiveWindow->MenuStrip)) {
	Screen = ActiveWindow->WScreen;
	NrOfMessages = SendMessage();
	UnlockIBase(Lock);
	if (NrOfMessages == 0)
	  PopUpMenu(InputSignals);
	else
	  Flags |= VERIFYOK;
      }
      else
	UnlockIBase(Lock);
    }
  } /* FOREVER */
}

/*******************************************************
 * SendMessage() - Send MENUVERIFY message             *
 *  to all windows on screen with MENUVERIFY flag set. *
 *  IBase must be locked!!			       *
 * Input:					       *
 *   none					       *
 * Output:					       *
 *   return    - Messages sent. 		       *
 *******************************************************/
WORD SendMessage()
{
  IMPORT struct Window	*const ActiveWindow;
  IMPORT struct MsgPort *ReplyPort;
  IMPORT struct Screen	*Screen;

  struct Window  *Window;
  WORD	 NrOfMessages = 0;

  Window = Screen->FirstWindow;
  do {
    if (Window->IDCMPFlags & MENUVERIFY) {
      struct IntuiMessage *const Message =
			  BuildIntuiMsg(ReplyPort, MENUVERIFY,
			     (Window == ActiveWindow) ? MENUHOT : MENUWAITING);
      if (Message) {
	CurrentTime(&Message->Seconds,&Message->Micros);
	Message->IDCMPWindow = Window;

	PutMsg(Window->UserPort,(struct Message *)Message);
	NrOfMessages++;
      }
    }
  }
  while (Window = Window->NextWindow);

  return (NrOfMessages);
}

/***************************************
 * BuildIntuiMsg(ReplyMsg,Class,Code)  *
 *				       *
 * Input:			       *
 *   ReplyPort			       *
 *   Class			       *
 *   Code			       *
 * Output:			       *
 *   return    -  IntuiMessage	       *
 ***************************************/
struct IntuiMessage *BuildIntuiMsg(ReplyPort,Class,Code)
  struct MsgPort  *const ReplyPort;
  ULONG   Class;
  UWORD   Code;
{
  struct IntuiMessage *const Message =
		AllocMem(sizeof(struct IntuiMessage),MEMF_PUBLIC | MEMF_CLEAR);

  if (Message) {
    Message->ExecMessage.mn_Node.ln_Type = NT_MESSAGE;
    Message->ExecMessage.mn_ReplyPort	 = ReplyPort;
    Message->ExecMessage.mn_Length	 = sizeof(struct IntuiMessage) -
					   sizeof(struct Message);
    Message->Class = Class;
    Message->Code  = Code;
  }
  return (Message);
}

/*******************************
 *  MyCreatePort(Name, Pri)    *
 *  MyDeletePort(Port)         *
 *			       *
 *  Replacements for amiga.lib *
 *******************************/
struct MsgPort *MyCreatePort(Name)
  STRPTR Name;
{
  UBYTE  SigBit;

  if ((SigBit = AllocSignal(-1)) != -1) {
    struct MsgPort  *const Port = AllocMem(sizeof(struct MsgPort),
					   MEMF_CLEAR | MEMF_PUBLIC);
    if (Port) {
      Port->mp_Node.ln_Name = Name;
      Port->mp_Node.ln_Pri  = 0;
      Port->mp_Node.ln_Type = NT_MSGPORT;
      Port->mp_Flags	    = PA_SIGNAL;
      Port->mp_SigBit	    = SigBit;
      Port->mp_SigTask	    = (struct Task *)FindTask(0);

      AddPort(Port);
      return(Port);
    }
    else
      FreeSignal((LONG)SigBit);
  }
  return(NULL);
}

VOID MyDeletePort(Port)
  struct MsgPort *const Port;
{
  RemPort(Port);
  FreeSignal((LONG)Port->mp_SigBit);
  FreeMem(Port,sizeof(struct MsgPort));
}

