#ifndef XPKMASTER_PASSWORD_C
#define XPKMASTER_PASSWORD_C

/* Routinesheader

	Name:		password.c
	Main:		xpkmaster
	Versionstring:	$VER: password.c 1.13 (27.03.1998)
	Author:		SDI
	Distribution:	Freeware
	Description:	password requester related things

 1.0   27.12.96 : first version
 1.1   28.12.96 : starting to code the stuff
 1.2   29.12.96 : optimized and removed bugs
 1.3   02.01.97 : corrected return handling
 1.4   01.03.97 : fixed tag parsing error
 1.5   10.04.97 : now old screen comes to front after Request
 1.6   15.10.97 : TimeOut now uses timer.device
 1.7   07.11.97 : timout of 0 again means no timeout
 1.8   27.12.97 : added title line to passrequest
 1.9   09.01.98 : gadget uses gadtools now
 1.10  18.01.98 : added 5 position tags
 1.11  21.01.98 : added verification mode
 1.12  21.02.98 : uses new style register definition
 1.13  27.03.98 : some optimizations
*/

#include <proto/intuition.h>
#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/gadtools.h>
#include <exec/memory.h>
#include <exec/errors.h>
#include <intuition/sghooks.h>
#include <intuition/intuitionbase.h>
#include <intuition/gadgetclass.h>
#include <xpk/xpk.h>
#include <xpk/xpkprefs.h>
#include "texts.h"
#include "xpkmaster.h"

#define TESTSIZE	13

#define VERIFY_OFF	0
#define VERIFY_ON	1
#define VERIFY_ACTIVE	2
#define VERIFY_DONE	3

static const struct PassCharData {
  ULONG Flag;
  UBYTE Lower;
  UBYTE Upper;
} TestField[TESTSIZE] = {
{XPKPASSFF_30x39,0x30,0x39}, {XPKPASSFF_41x46,0x41,0x46},
{XPKPASSFF_61x66,0x61,0x66}, {XPKPASSFF_47x5A,0x47,0x5A},
{XPKPASSFF_67x7A,0x67,0x7A}, {XPKPASSFF_20,   0x20,0x20},
{XPKPASSFF_SPECIAL7BIT,0x21,0x2F},
{XPKPASSFF_SPECIAL7BIT,0x3A,0x40},
{XPKPASSFF_SPECIAL7BIT,0x5B,0x60},
{XPKPASSFF_SPECIAL7BIT,0x7B,0x7E},
{XPKPASSFF_C0xDE,0xC0,0xDE}, {XPKPASSFF_DFxFF,0xDF,0xFF},
{XPKPASSFF_SPECIAL8BIT,0xA0,0xBF},
};

/* this is no public structure !! */
struct RequestData {
  ULONG			rd_Time;
  ULONG			rd_GadgFlags;
  ULONG			rd_BufSize;
  WORD			rd_WinLeft;
  WORD			rd_WinTop;
  WORD			rd_WinWidth;
  WORD			rd_WinHeight;
  UWORD			rd_WinCenter;
  UWORD			rd_Verify;
  STRPTR		rd_Title;
  STRPTR		rd_GivenBuffer;
  struct Window *	rd_Window;
  struct Screen *	rd_GivenScreen;
  struct Screen *	rd_FirstScreen;
  struct timerequest *  rd_TimeRequest;
  STRPTR		rd_PassBuffer;
  UBYTE			rd_ScreenTitle[80];
  UBYTE			rd_KeyBuffer[9];
};

static void SendTimerReq(struct RequestData *rd)
{
  rd->rd_TimeRequest->tr_node.io_Command = TR_ADDREQUEST;
  rd->rd_TimeRequest->tr_time.tv_secs = rd->rd_Time;
  SendIO((struct IORequest *) rd->rd_TimeRequest);
}

static ASM(ULONG) PassHookFunc(REG(a0, struct Hook *hook),
	REG(a2, struct SGWork *sgw), REG(a1, ULONG *msg))
{
  struct RequestData *rd;

  rd = (struct RequestData *) sgw->Gadget->UserData;

  if(rd->rd_Time)
    AbortIO((struct IORequest *) rd->rd_TimeRequest);

  if(*msg == SGH_KEY)
  {
    STRPTR passbuf = rd->rd_PassBuffer;
    ULONG bufpos = sgw->BufferPos-1;
    STRPTR wbuf = sgw->WorkBuffer;

    switch(sgw->EditOp)
    {
    case EO_INSERTCHAR:
    {
      register UBYTE c = sgw->Code;

      if(rd->rd_Verify == VERIFY_ACTIVE)
      {
        if(c != passbuf[bufpos])
        {
          sgw->Actions |= SGA_BEEP; /* let the screen beep */
          sgw->Actions &= ~SGA_USE; /* do not use char */
        }
        else
	  wbuf[bufpos] = '*';
      }
      else
      {
        register ULONG i;
        for(i = 0; i < TESTSIZE; ++i)
        {
          if((rd->rd_GadgFlags & TestField[i].Flag) &&
          (c >= TestField[i].Lower) && (c <= TestField[i].Upper))
            break;
        }
        if(i == TESTSIZE)
        {
          sgw->Actions |= SGA_BEEP; /* let the screen beep */
          sgw->Actions &= ~SGA_USE; /* do not use char */
        }
        else
        {
          passbuf[bufpos] = c;
          passbuf[sgw->NumChars] = 0;
	  wbuf[bufpos] = '*';
        }
      }
    }
    break;
    case EO_CLEAR:
      if(rd->rd_Verify != VERIFY_ACTIVE)
        *passbuf = 0;
      *wbuf = 0;
      sgw->NumChars = sgw->BufferPos = 0;
      sgw->EditOp = EO_BIGCHANGE;
      break;
    case EO_DELBACKWARD:
      if(rd->rd_Verify != VERIFY_ACTIVE)
        passbuf[sgw->NumChars] = 0;
      break;
    case EO_ENTER:
      if(rd->rd_Verify == VERIFY_ON && sgw->NumChars)
      {
	SetWindowTitles(rd->rd_Window, strings[TXT_VERIFY_PASS], (STRPTR) ~0);
        rd->rd_Verify = VERIFY_ACTIVE;
        *wbuf = 0;
        sgw->NumChars = sgw->BufferPos = 0;
        sgw->EditOp = EO_BIGCHANGE;
        sgw->Actions &= ~SGA_END;
      }
      else if(rd->rd_Verify == VERIFY_ACTIVE && !passbuf[sgw->NumChars])
        rd->rd_Verify = VERIFY_DONE;
    case EO_NOOP:
      break;
    /* prevent to complicated edit operations, like cursor move */
    default:
      sgw->Actions &= ~SGA_USE;
      sgw->Actions |= SGA_BEEP;
      break;
    } /* switch */
    return ~0;
  }
  else if(*msg == SGH_CLICK) /* on click set last character */
  {
    sgw->BufferPos = sgw->NumChars;
    sgw->EditOp = EO_BIGCHANGE;
  }
  else
    return 0;
}

static struct Hook PassHook = { {0}, (ULONG (*) ()) PassHookFunc, 0, 0};

/* returns XPKERR codes */
static LONG DoRequest(struct RequestData *rd)
{
  struct Screen *scr;
  struct MsgPort *MsgPort = 0;
  LONG err = XPKERR_UNKNOWN;

  if(rd->rd_GivenScreen)
    scr = rd->rd_GivenScreen;
  else if(!(scr = LockPubScreen(0)))
    return XPKERR_UNKNOWN;

  *rd->rd_PassBuffer = 0;

  if(!rd->rd_Time || (MsgPort = CreateMsgPort()))
  {
    ULONG tflag = rd->rd_Time ? 1 << MsgPort->mp_SigBit : 0;

    if(!tflag || (rd->rd_TimeRequest = (struct timerequest *)
    CreateIORequest(MsgPort, sizeof(struct timerequest))))
    {
      if(!tflag || !OpenDevice("timer.device",UNIT_VBLANK,
      (struct IORequest *) rd->rd_TimeRequest,0))
      {
	APTR vi;

        if((vi = GetVisualInfoA(scr, 0)))
        {
          struct Gadget *gadg, *gl = 0;
	  struct NewGadget ng;
          ULONG i;

	  i = 4 + scr->WBorLeft + scr->WBorRight;

	  if(rd->rd_WinWidth < i + 50)
	    rd->rd_WinWidth = scr->Width >> 1;
	  if(rd->rd_WinWidth > scr->Width)
	    rd->rd_WinWidth = scr->Width;
	  ng.ng_Width = rd->rd_WinWidth - i;

	  i = 4 + scr->WBorTop + scr->Font->ta_YSize + 1 + scr->WBorBottom;

	  if(rd->rd_WinHeight < i + scr->Font->ta_YSize + 6)
	    rd->rd_WinHeight = i + scr->Font->ta_YSize + 6;
	  if(rd->rd_WinHeight > scr->Height)
	    rd->rd_WinHeight = scr->Height;
	  ng.ng_Height = rd->rd_WinHeight - i;

	  ng.ng_TopEdge = scr->WBorTop + scr->Font->ta_YSize + 1 + 2;
	  ng.ng_LeftEdge = scr->WBorLeft + 2;
	  ng.ng_GadgetText = 0;
	  ng.ng_TextAttr = scr->Font;
/*	  ng.ng_GadgetID is not used --> not initialized */
	  ng.ng_Flags = 0;
	  ng.ng_VisualInfo = vi;
	  ng.ng_UserData = rd;

	  if(rd->rd_WinLeft < 0)
	    rd->rd_WinLeft = (scr->Width - rd->rd_WinWidth) >> 1;
	  else if(rd->rd_WinCenter)
	    rd->rd_WinLeft -= (rd->rd_WinWidth >> 1);

	  if(rd->rd_WinTop < 0)
	    rd->rd_WinTop = (scr->Height - rd->rd_WinHeight) >> 1;
	  else if(rd->rd_WinCenter)
	    rd->rd_WinTop -= (rd->rd_WinHeight >> 1);

	  if(rd->rd_WinTop < 0)
	    rd->rd_WinTop = 0;
	  if(rd->rd_WinLeft < 0)
	    rd->rd_WinLeft = 0;

	  if((gadg = CreateContext(&gl)) && (gadg = CreateGadget(
	  STRING_KIND, gadg, &ng,
	  GTST_EditHook, &PassHook,
	  STRINGA_Justification, GACT_STRINGCENTER,
	  GTST_MaxChars, rd->rd_BufSize-1,
	  TAG_DONE)))
          {
	    struct Window *window;

            if((rd->rd_Window = window = OpenWindowTags(0,
	      WA_Left, rd->rd_WinLeft,
	      WA_Top, rd->rd_WinTop,
	      WA_Height, rd->rd_WinHeight,
	      WA_Width, rd->rd_WinWidth,
	      WA_Gadgets, gl,
	      WA_IDCMP, IDCMP_GADGETUP|IDCMP_ACTIVEWINDOW|IDCMP_CLOSEWINDOW,
	      WA_Flags, WFLG_DRAGBAR|WFLG_DEPTHGADGET|WFLG_ACTIVATE|
	        WFLG_RMBTRAP|WFLG_CLOSEGADGET,
	      WA_Title, rd->rd_Title,
	      WA_PubScreen, scr,
	      WA_AutoAdjust, TRUE,
	      WA_PubScreenFallBack, TRUE, TAG_DONE)))
            {
              struct IntuiMessage *msg;
              BOOL stop = 0;

              rd->rd_FirstScreen = IntuitionBase->FirstScreen;
              ScreenToFront(scr);
	      if(tflag)
	        SendTimerReq(rd);

              while(!stop)
              {
	        if((i = Wait((1<<window->UserPort->mp_SigBit)|tflag)) & tflag)
	        { /* when tflag is zero, this is never reached */
	          if(rd->rd_TimeRequest->tr_node.io_Error == IOERR_ABORTED)
	            SendTimerReq(rd); /* got aborted, resend */
	          else
	            stop = 20;
	        }

	        if(i & (1 << window->UserPort->mp_SigBit))
	        {
	          while(!stop && (msg = (struct IntuiMessage *)
	          GetMsg(window->UserPort)))
	          {
	            if(msg->Class == IDCMP_ACTIVEWINDOW)
	              ActivateGadget(gadg, window, 0);
	            else if(msg->Class == IDCMP_CLOSEWINDOW)
	              stop = 10;
	            else /* if(msg->Class == IDCMP_GADGETUP) */
	              stop = 1;
	            ReplyMsg((struct Message *) msg);
	          }
	        }
	      }
	      if(stop >= 20)
	        err = XPKERR_REQTIMEOUT;
	      else if(rd->rd_Verify == VERIFY_ACTIVE)
		err = XPKERR_WRONGPW;
	      else if(stop >= 10 || !*rd->rd_PassBuffer)
	        err = XPKERR_ABORTED;
	      else
	        err = XPKERR_OK;

	      if(tflag)
	      {
	        AbortIO((struct IORequest *) rd->rd_TimeRequest);
	        WaitIO((struct IORequest *) rd->rd_TimeRequest);
	      }
            }
            CloseWindow(window);
            if(rd->rd_FirstScreen != scr) /* possibly dangerous routine */
            {
              struct Screen *sc;
              Forbid();
              sc = IntuitionBase->FirstScreen;
              while((sc = sc->NextScreen) && sc != rd->rd_FirstScreen)
                ;
              if(sc)
                ScreenToFront(sc);
              Permit();
            }
          }
          if(gl)
            FreeGadgets(gl);
          FreeVisualInfo(vi);
        }

	if(tflag)
          CloseDevice((struct IORequest *) rd->rd_TimeRequest);
      }
      if(rd->rd_TimeRequest)
        DeleteIORequest(rd->rd_TimeRequest);
    }
    if(MsgPort)
      DeleteMsgPort(MsgPort);
  }

  if(!rd->rd_GivenScreen)
    UnlockPubScreen(0, scr);

  return err;
}

ASM(LONG) LIBXpkPassRequest(REG(a0, struct TagItem *ti))
{
  register struct RequestData *rd;
  LONG mode = 0, useprefs = 1;
  struct TagItem *tags = ti;

#ifdef DEBUG
  DebugTagList("XpkPassRequest", tags);
#endif

  if(!(rd = (struct RequestData *) AllocMem(sizeof(struct RequestData),
  MEMF_PUBLIC|MEMF_CLEAR)))
    return XPKERR_NOMEM;

  /* set defaults */
  rd->rd_Time = 120;
  rd->rd_GadgFlags = XPKPASSFLG_PRINTABLE;
  rd->rd_WinLeft = rd->rd_WinTop = -1;

  while((ti = NextTagItem(&tags)))
  {
    switch(ti->ti_Tag)
    {
    case XPK_PassChars: rd->rd_GadgFlags = ti->ti_Data; break;
    case XPK_PasswordBuf:
      rd->rd_GivenBuffer = (STRPTR) ti->ti_Data; mode += 10; break;
    case XPK_PassBufSize: rd->rd_BufSize = ti->ti_Data; break;
    case XPK_Key16BitPtr:
      rd->rd_GivenBuffer = (STRPTR) ti->ti_Data; mode += 11; break;
    case XPK_Key32BitPtr:
      rd->rd_GivenBuffer = (STRPTR) ti->ti_Data; mode += 12; break;
    case XPK_PubScreen: rd->rd_GivenScreen = (struct Screen *) ti->ti_Data; break;
    case XPK_PassTitle: rd->rd_Title = ti->ti_Data ? (STRPTR) ti->ti_Data : ""; break;
    case XPK_TimeOut: useprefs = 0; rd->rd_Time = ti->ti_Data; break;
    case XPK_Preferences: if(!ti->ti_Data) useprefs = 0; break;
    case XPK_PassWinLeft: rd->rd_WinLeft = ti->ti_Data; break;
    case XPK_PassWinTop: rd->rd_WinTop = ti->ti_Data; break;
    case XPK_PassWinWidth: rd->rd_WinWidth = ti->ti_Data; break;
    case XPK_PassWinHeight: rd->rd_WinHeight = ti->ti_Data; break;
    case XPK_PassCenter: rd->rd_WinCenter = ti->ti_Data; break;
    case XPK_PassVerify:
      rd->rd_Verify = (ti->ti_Data ? VERIFY_ON : VERIFY_OFF); break;
    };
  }

  if(!mode || (mode > 12) || (mode == 10 && !rd->rd_BufSize) ||
  !rd->rd_GivenBuffer)
  {
    FreeMem(rd, sizeof(struct RequestData));
    return XPKERR_BADPARAMS;
  }

  if(useprefs) /* call the preferences */
  {
    struct XpkPrefsSemaphore *sem;

    if((sem = GetPrefsSem()))
    {
      if(sem->xps_MainPrefs)
        rd->rd_Time = sem->xps_MainPrefs->xmp_Timeout;
      ReleaseSemaphore((struct SignalSemaphore *) sem);
    }
  }

  if(!rd->rd_Title) /* create title text */
  {
    if(mode == 10)
      rd->rd_Title = strings[TXT_REQ_PASSWORD];
    else
    {
      rd->rd_Title = rd->rd_ScreenTitle;
      sprintf(rd->rd_ScreenTitle, strings[TXT_REQ_KEY], (mode == 11 ? 16 : 32));
    }
  }

  if(mode > 10)
  {
    rd->rd_BufSize = (mode == 11 ? 5 : 9);
    rd->rd_PassBuffer = rd->rd_KeyBuffer;
    rd->rd_GadgFlags = XPKPASSFLG_HEXADECIMAL;
  }
  else
    rd->rd_PassBuffer = rd->rd_GivenBuffer;
  
  if(!(useprefs = DoRequest(rd)))
  {
    if(mode == 11)
      *((UWORD *) rd->rd_GivenBuffer) = strtoul(rd->rd_PassBuffer, 0, 16);
    else if(mode == 12)
      *((ULONG *) rd->rd_GivenBuffer) = strtoul(rd->rd_PassBuffer, 0, 16);
  }

  FreeMem(rd, sizeof(struct RequestData));

  return useprefs;
}

#endif	/* XPKMASTER_PASSWORD_C */
