/***************************************************************************
 *                                                                         *
 *   WGSMED.C                                                              *
 *                                                                         *
 *   Copyright (c) 1996-1997 Galacticomm, Inc.  All rights reserved.       *
 *                                                                         *
 *   This is the Galacticomm Client/Server Menu Editor agent.              *
 *                                                                         *
 *                                              - M. Donnelly              *
 *                                                D. Pitchford 8/2/96      *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "gcspsrv.h"
#include "wgsmm.h"

#define FILREV "$Revision: 15 $"

#define MEDAID "WGSMED"            /* app-id of Menu Editor                */

// Handy definitions for scratch menu pages, misc functions:

#define PSCRATCH ((struct mnupag *)mnubb->data)
#define PTEMPPAGE ((struct mnupag *)vdatmp)
#define CLIPPY(x)  (SmartClip(x,sizeof(x)))

struct SPAWNPAGE {                 /* struct for handling spawnpage request*/
     CHAR selchr;
     CHAR Mode;
     CHAR PageName[PNMSIZ];
     CHAR ParentName[PNMSIZ];
     SHORT Selection;
     SHORT Type;
};

#define pTempSpawn ((struct SPAWNPAGE *)value)
#define pTempSel   ((struct pglink *)value)

#define SIZEDIFF ((sizeof(struct pglink)*MAXSEL))

INT UseChan=-1;                    /* usrnum currently using editor        */
struct mnupag Work;                /* temporary menu page for scratch work */
struct ffblk IconScan;             /* Scratch ffblk for building icon list */

IMPORT_VARIABLE(DFAFILE*) mnubb;   /* menu tree data file                  */

struct flddef spawnpageFDA[]={
     { CVTFLD_CHAR  ,1        ,fldoff(SPAWNPAGE,selchr)     ,NULL },
     { CVTFLD_CHAR  ,1        ,fldoff(SPAWNPAGE,Mode)       ,NULL },
     { CVTFLD_CHAR  ,PNMSIZ   ,fldoff(SPAWNPAGE,PageName)   ,NULL },
     { CVTFLD_CHAR  ,PNMSIZ   ,fldoff(SPAWNPAGE,ParentName) ,NULL },
     { CVTFLD_SHORT ,1        ,fldoff(SPAWNPAGE,Selection)  ,NULL },
     { CVTFLD_SHORT ,1        ,fldoff(SPAWNPAGE,Type)       ,NULL },
     { CVTFLD_END   ,0        ,0                            ,NULL }
};

struct flddef mnupagFDA[]={
     { CVTFLD_CHAR  ,1        ,fldoff(mnupag,menutype)      ,NULL },
     { CVTFLD_CHAR  ,PNMSIZ   ,fldoff(mnupag,pagnam)        ,NULL },
     { CVTFLD_CHAR  ,PNMSIZ   ,fldoff(mnupag,parpag)        ,NULL },
     { CVTFLD_CHAR  ,GCSTRPTH ,fldoff(mnupag,fname)         ,NULL },
     { CVTFLD_CHAR  ,MNMSIZ   ,fldoff(mnupag,modnam)        ,NULL },
     { CVTFLD_CHAR  ,CMDSIZ   ,fldoff(mnupag,cmdstg)        ,NULL },
     { CVTFLD_CHAR  ,KEYSIZ   ,fldoff(mnupag,golock)        ,NULL },
     { CVTFLD_SHORT ,1        ,fldoff(mnupag,flags)         ,NULL },
     { CVTFLD_CHAR  ,TITLSZ   ,fldoff(mnupag,mnuttl)        ,NULL },
     { CVTFLD_SHORT ,1        ,fldoff(mnupag,npages)        ,NULL },
     { CVTFLD_LONG  ,1        ,fldoff(mnupag,ichange)       ,NULL },
     { CVTFLD_END   ,0        ,0                            ,NULL }
};

struct flddef pagelinkFDA[]={
     { CVTFLD_SHORT ,1        ,fldoff(pglink,position)      ,NULL },
     { CVTFLD_CHAR  ,1        ,fldoff(pglink,selchr)        ,NULL },
     { CVTFLD_CHAR  ,FNSIZE   ,fldoff(pglink,iconame)       ,NULL },
     { CVTFLD_CHAR  ,PNMSIZ   ,fldoff(pglink,destpage)      ,NULL },
     { CVTFLD_CHAR  ,1        ,fldoff(pglink,optdsp)        ,NULL },
     { CVTFLD_CHAR  ,KEYSIZ   ,fldoff(pglink,keyreq)        ,NULL },
     { CVTFLD_CHAR  ,SHDSIZ   ,fldoff(pglink,shortd)        ,NULL },
     { CVTFLD_CHAR  ,LNDSIZ   ,fldoff(pglink,longd)         ,NULL },
     { CVTFLD_END   ,0        ,0                            ,NULL }
};

struct flddef pagelinkarrayFDA[]={
     { CVTFLD_STRUCT,MAXSEL   ,0                            ,pagelinkFDA },
     { CVTFLD_END   ,0        ,0                            ,NULL }
};

                                   /* variables to manage the list of      */
                                   /* available modules & c/s apps         */
CHAR **ppTerminal=NULL;            /*   list of ptrs to names of a/a mods  */
CHAR **ppClient=NULL;              /*   list of ptrs to names of c/s mods  */
INT nTerminal=0;                   /*   number of a/a mods available       */
INT nClient=0;                     /*   number of c/s mods available       */
INT nDesc=0;                       /*   number of MDFs available           */

// Prototypes for miscellaneous functions:

static VOID ProcessMDF(CHAR *pName);
static VOID PrepareAppList(VOID);
static GBOOL LoadPage(struct mnupag *pOutput,CHAR *pName);
static INT FindPage(struct mnupag *pPage,CHAR *pName);
static VOID SavePage(struct mnupag *pInput);
static GBOOL Goodpgnam(CHAR *pProposed);
static GBOOL ValidatePage(CHAR *pProposed,GBOOL require);
static VOID SmartClip(CHAR *pThing,size_t Length);
static GBOOL IsLastChoice(INT Index);
static GBOOL IsMenu(struct mnupag *pCheck);
static VOID EndTree(VOID);
static INT VBPageType(struct mnupag *pPage);
static VOID BuildTree(VOID);
static VOID FillIcon(VOID);
static VOID StartTree(VOID);
static VOID MenuRead(INT direction,struct saunam *dpknam);
static VOID MenuWrite(struct saunam *dpknam,USHORT length,VOID *value);
static VOID MenuDone(VOID);
static VOID MenuDisconnect(VOID);
static VOID CSKillPage(VOID);
static VOID CSRenamePage(CHAR *pNewName);
static VOID SmartUpdate(struct mnupag *pPage);

// Variables for the non-recursive tree building routines.

#define MAX_DEPTH   32

LONG Position[MAX_DEPTH];
INT MenuIndex[MAX_DEPTH];
INT Depth;
FILE *pTree;

#define ICONS_PER_GULP   8

struct agent MenuAgent={           /* agent information structure          */
     MEDAID,                       /*   appid                              */
     MenuRead,                     /*   read-dynapak function pointer      */
     MenuWrite,                    /*   write-dynapak function pointer     */
     MenuDone,                     /*   file xfer-done function pointer    */
     NULL                          /*   abort-request function pointer     */
};

VOID EXPORT
init__wgsmed(VOID)                 /* module initialization routine        */
{
     hook_disconnect(MenuDisconnect);
     register_agent(&MenuAgent);
     PrepareAppList();
     register_dpkfda(MEDAID,"sa:setpage ",mnupagFDA);
     register_dpkfda(MEDAID,"sa:setselection ",pagelinkFDA);
     register_dpkfda(MEDAID,"sa:spawnpage ",spawnpageFDA);
}

VOID EXPORT
initwc__wgsmed(VOID)
{
     init__wgsmed();
}

static VOID
ProcessMDF(                        /* process an MDF for agents/modules    */
CHAR *pName)
{
     CHAR Scratch[129],Name[MNMSIZ];
     FILE *fp;
     CHAR *pTemp;
     GBOOL HitName,CSOnly,HitClient,Internal;

     if ((fp=fopen(pName,FOPRA)) == NULL) {
          return;
     }
     HitName=CSOnly=HitClient=Internal=FALSE;
     while (fgets(Scratch,128,fp) != NULL) {
          if (sameto("Module Name:",Scratch)) {
               HitName=TRUE;
               pTemp=skpwht(strchr(Scratch,':')+1);
               depad(pTemp);
               stzcpy(Name,pTemp,MNMSIZ);
               pTemp=strchr(Name,'\r');
               if (pTemp == NULL) {
                    pTemp=strchr(Name,'\n');
               }
               if (pTemp != NULL) {
                    *pTemp='\0';
               }
          }
          if (sameto("Client app",Scratch)) {
               HitClient=TRUE;
          }
          if (sameto("C/S Only",Scratch)) {
               CSOnly=TRUE;
          }
          if (sameto("Internal",Scratch)) {
               Internal=TRUE;
          }
     }
     fclose(fp);
     if (HitName && !Internal) {
          if (!CSOnly) {
               ppTerminal[nTerminal++]=alcdup(Name);
          }
          if (HitClient) {
               ppClient[nClient++]=alcdup(Name);
          }
     }
}

static VOID
PrepareAppList(VOID)
{
     INT Index;
     UINT nDesc;
     struct ffblk fInfo;

     if (ppTerminal != NULL) {
          for (Index=0 ; Index < nTerminal ; Index++) {
               free(ppTerminal[Index]);
          }
          free(ppTerminal);
     }
     nTerminal=0;
     ppTerminal=NULL;
     if (ppClient != NULL) {
          for (Index=0 ; Index < nClient ; Index++) {
               free(ppClient[Index]);
          }
          free(ppClient);
     }
     nClient=0;
     ppClient=NULL;
     if (fnd1st(&fInfo,"*.MDF",0)) {
          nDesc=0;
          do {
               nDesc++;
          } while (fndnxt(&fInfo));
          ppTerminal=(CHAR **)alczer(nDesc*sizeof(CHAR *));
          ppClient=(CHAR **)alczer(nDesc*sizeof(CHAR *));
          if (fnd1st(&fInfo,"*.MDF",0)) {
               do {
                    ProcessMDF(fInfo.ff_name);
               } while (fndnxt(&fInfo));
          }
     }
}

static GBOOL                       /*   FALSE if page doesn't exist        */
LoadPage(                          /* load and unpack menu page            */
struct mnupag *pOutput,            /*   page to load (modifies)            */
CHAR *pName)                       /*   page name                          */
{
     if (dfaAcqEQ(pOutput,pName,0)) {
          unpack(pOutput);
          return(TRUE);
     }
     return(FALSE);
}

static INT                         /*   returns page index or -1 if unfound*/
FindPage(                          /* find the matching destination page   */
struct mnupag *pPage,              /*   menu page to search                */
CHAR *pName)                       /*   name to find                       */
{
     INT Index;

     for (Index=0 ; Index < MAXSEL ; Index++) {
          if (sameas(pPage->page[Index].destpage,pName)) {
               return(Index);
          }
     }
     return(-1);
}

static VOID
SavePage(                          /* save passed page                     */
struct mnupag *pInput)
{
     USHORT Length;
     INT Index;

     if (pInput->menutype[0] == 'C') {
          for (Index=0 ; Index < MAXSEL ; Index++) {
               if (pInput->page[Index].destpage[0] != '\0') {
                    pInput->page[Index].selchr='!';
               }
          }
     }
     repack(pInput);
     Length=sizeof(struct mnupag)-
            (MAXSEL-pInput->npages)*sizeof(struct pglink);
     dfaUpdateV(pInput,Length);
     unpack(pInput);
}

static GBOOL                       /*   if FALSE, returns reason in vdatmp */
Goodpgnam(                         /* is the page name a good one?         */
CHAR *pProposed)                   /*   proposed page name (uppercases)    */
{
     USHORT Length;
     CHAR *pTemp;

     strupr(pProposed);
     for (Length=0,pTemp=pProposed ;
       *pTemp != '\0' && Length < PNMSIZ ; pTemp++,Length++) {
          if (!istxvc(*pTemp)) {
               strcpy(vdatmp,"Invalid character in page name");
               return(FALSE);
          }
     }
     if (Length < 3) {
          strcpy(vdatmp,"Page name too short");
          return(FALSE);
     }
     if (Length >= PNMSIZ) {
          strcpy(vdatmp,"Page name too long");
          return(FALSE);
     }
     return(TRUE);
}

static GBOOL                       /*   if FALSE, returns reason in vdatmp */
ValidatePage(                      /* OKs a menu page name                 */
CHAR *pProposed,                   /*   proposed page name (uppercases)    */
GBOOL require)                     /*   TRUE= must exist already,          */
{                                  /*   FALSE=must NOT exist already       */
     GBOOL exists;

     strupr(pProposed);
     exists=dfaAcqEQ(NULL,pProposed,0);
     if (require) {
          if (!exists) {
               sprintf(vdatmp,"No such page: %s",1+pProposed);
               return(FALSE);
          }
     }
     else if (exists) {
          sprintf(vdatmp,"Page already exists: %s",1+pProposed);
          return(FALSE);
     }
     return(Goodpgnam(pProposed));
}

static VOID
SmartClip(CHAR *pThing,            /* remove space padding                 */
size_t Length)
{
     if (Length > 0) {
          pThing[Length-1]='\0';
     }
     depad(pThing);
}

static GBOOL
IsLastChoice(
INT Index)
{
     INT Temp;

     for (Temp=MAXSEL-1 ; Temp >= 0 ; Temp--) {
          if (PSCRATCH->page[Temp].destpage[0] != '\0') {
               break;
          }
     }
     return(Temp == Index);
}

static GBOOL
IsMenu(                            /* is passed page a menu page?          */
struct mnupag *pCheck)
{
     if ((pCheck->flags&FILPAG) != 0) {
          return(FALSE);
     }
     if ((pCheck->flags&MDLPAG) != 0) {
          return(FALSE);
     }
     return(TRUE);
}

static VOID
EndTree(VOID)
{
     fclose(pTree);
     cycleme(NULL);
     rsp2read(NULL,STGLEN,
              spr("wgsmmtre.tmp;wgsmm%c.txt",Work.menutype[0]),NULL);
}

static INT
VBPageType(                             /* return page type                */
struct mnupag *pPage)
{
     if ((pPage->flags&FILPAG) != 0) {
          return(1);
     }
     if ((pPage->flags&MDLPAG) != 0) {
          return(2);
     }
     if ((pPage->flags&AUTPAG) != 0) {
          return(3);
     }
     return(4);
}

static VOID
BuildTree(VOID)
{
     struct pglink *pLink;
     struct pgllist *mt;

     dfaSetBlk(mnubb);
     dfaGetAbs(PSCRATCH,Position[Depth],0);
     if (Depth == 0) { // Skimming key pages.
          // Next, make sure the corresponding real page
          // exists (and is orphan):
          if (!dfaAcqEQ(PSCRATCH,PSCRATCH->menutype,0)
           || !(PSCRATCH->flags&TPOTRE)) {
               // Key page references a bogus page.  We'll erase that key page
               // here as a matter of cleanup, then move along.
               if (gofind == 1 && PSCRATCH->menutype[0] == 'C') {
                    pglset(&csfgop);
                    pglopdel(PSCRATCH->pagnam);
               }
               pglset(PSCRATCH->menutype[0] == 'A' ? &aaftop : &csftop);
               pglopdel(PSCRATCH->pagnam);
               mt=pglopggt(PSCRATCH->pagnam);
               if (mt == NULL) {
                    EndTree();
                    return;
               }
               stzcpy(PSCRATCH->pagnam,firstwd(mt->pagedata),PNMSIZ);
               if (!dfaAcqEQ(PSCRATCH,PSCRATCH->menutype,0)) {
                    EndTree();
                    return;
               }
               Position[0]=dfaAbs();
               return;
          }
          // Ok, scratch buffer has a good page in it.  If it's a menu page,
          // we'll need to store the position and dig in.  If not, just
          // write it to the tree and move on.
          fwrite(PSCRATCH->pagnam,strlen(PSCRATCH->pagnam),1,pTree);
          fwrite("\n",1,1,pTree);
          fwrite(spr("%02d %d",Depth,VBPageType(PSCRATCH)),1,4,pTree);
          fwrite("\n",1,1,pTree);
          if (IsMenu(PSCRATCH)) {
               Depth++;
               MenuIndex[Depth]=0;
               Position[Depth]=dfaAbs();
               return;
          }
          // Since it's not a menu, go back to the key page and query next.
          pglset(PSCRATCH->menutype[0] == 'A' ? &aaftop : &csftop);
          mt=pglopggt(PSCRATCH->pagnam);
          if (mt == NULL) {
               EndTree();
               return;
          }
          stzcpy(PSCRATCH->pagnam,firstwd(mt->pagedata),PNMSIZ);
          if (!dfaAcqEQ(PSCRATCH,PSCRATCH->menutype,0)) {
               EndTree();
               return;
          }
          Position[0]=dfaAbs();
          return;
     }
     else {
          unpack(PSCRATCH);
          while (TRUE) {
               pLink=PSCRATCH->page+MenuIndex[Depth];
               if (MenuIndex[Depth] == MAXSEL) { // Back it up a level.
                    Depth--;
                    if (Depth == 0) { // Back out to hunting orphans.
                         dfaGetAbs(PSCRATCH,Position[Depth],0);
                         pglset(PSCRATCH->menutype[0] == 'A' ? &aaftop
                                                             : &csftop);
                         mt=pglopggt(PSCRATCH->pagnam);
                         if (mt == NULL) {
                              EndTree();
                              return;
                         }
                         stzcpy(PSCRATCH->pagnam,firstwd(mt->pagedata),PNMSIZ);
                         if (!dfaAcqEQ(PSCRATCH,PSCRATCH->menutype,0)) {
                              EndTree();
                              return;
                         }
                         Position[Depth]=dfaAbs();
                    }
                    break;
               }
               MenuIndex[Depth]++;
               if (pLink->destpage[0] != '\0') {
                    if (IsLastChoice(MenuIndex[Depth]-1)) {
                         fwrite("*",1,1,pTree);
                    }
                    fwrite(pLink->destpage,strlen(pLink->destpage),1,pTree);
                    sprintf(vdatmp,"%c%s",
                              PSCRATCH->menutype[0],pLink->destpage);
                    dfaAcqEQ(vdatmp,vdatmp,0);
                    fwrite("\n",1,1,pTree);
                    fwrite(spr("%02d %d",
                               Depth,VBPageType(PTEMPPAGE)),1,4,pTree);
                    fwrite("\n",1,1,pTree);
                    if (IsMenu(PTEMPPAGE)) { // Dig in another recursion level.
                         Depth++;
                         MenuIndex[Depth]=0;
                         Position[Depth]=dfaAbs();
                         break;
                    }
               }
          }
     }
}

static VOID
FillIcon(VOID)                     /* stuff icons into vdatmp              */
{
     CHAR *pTemp;
     INT Added=0;

     pTemp=vdatmp;
     *pTemp='\0';
     while (TRUE) {
          strcpy(pTemp,IconScan.ff_name);
          pTemp+=strlen(pTemp);
          *pTemp='\t';
          pTemp++;
          Added++;
          if (Added == ICONS_PER_GULP) {
               break;
          }
          if (!fndnxt(&IconScan)) {
               *pTemp='!';
               pTemp++;
               break;
          }
     }
     *pTemp='\0';
}

static VOID
StartTree(VOID)
{
     struct pgllist *mt;

     Depth=0;
     dfaSetBlk(mnubb);
     pglset(Work.menutype[0] == 'A' ? &aaftop : &csftop);
     mt=pglopggt("");
     if (mt == NULL) {
          shocst("WGSMED - NO KEY PAGES!!!","Type:%c",Work.menutype[0]);
          rejectreq();
          return;
     }
     stzcpy(Work.pagnam,firstwd(mt->pagedata),PNMSIZ);
     if (!dfaAcqEQ(PSCRATCH,Work.menutype,0)) {
          shocst("WGSMED - KEY PAGES OUT OF SYNC!","Type:%c",Work.menutype[0]);
          rejectreq();
          return;
     }
     Position[Depth]=dfaAbs();
     pTree=fopen("wgsmmtre.tmp",FOPWA);
     cycleme(BuildTree);
}

static VOID
MenuRead(
INT direction,
struct saunam *dpknam)
{
     CHAR *dpkstg;

     if (!stdchk(medkey) || usrnum != UseChan) {
          rejectreq();
          return;
     }
     dpkstg=cnvs2d(dpknam);
     dfaSetBlk(mnubb);
     if (sameas("sa:treeupdate",dpkstg)) {
          rsp2read(NULL,STGLEN,"1",NULL);
          return;
     }
     if (sameas("sa:starticon",dpkstg)) {
          if (fnd1st(&IconScan,spr("WGSICONS\\*.ICO"),0)) {
               FillIcon();
               rsp2read(NULL,STGLEN,vdatmp,NULL);
          }
          else {
               rejectreq();
          }
          return;
     }
     if (sameas("sa:nexticon",dpkstg)) {
          if (fndnxt(&IconScan)) {
               FillIcon();
               rsp2read(NULL,STGLEN,vdatmp,NULL);
          }
          else {
               rejectreq();
          }
          return;
     }
     if (sameto("sa:icontime ",dpkstg)) {
          struct ffblk Temp;
          DOUBLE TimeDate;

          dpkstg=strchr(dpkstg,' ')+1;
          if (*dpkstg == '\0') {
               rejectreq();
               return;
          }
          if (!fnd1st(&Temp,spr("WGSICONS\\%s",dpkstg),0)) {
               rejectreq();
               return;
          }
          TimeDate=d2vdat(Temp.ff_fdate,Temp.ff_ftime);
          rsp2read(NULL,sizeof(DOUBLE),&TimeDate,NULL);
          return;
     }
     if (sameto("sa:module ",dpkstg)) {
          INT State;
          struct saunam Temp;

          dpkstg=skpwht(skpwrd(dpkstg));
          State=atoi(dpkstg);
          if (direction < 0) {
               State--;
          }
          else if (direction > 0) {
               State++;
          }
          if (State < 1 || State > nTerminal) {
               rejectreq();
               return;
          }
          sprintf(vdatmp,"sa:module %03d",State);
          cnvd2s(vdatmp,&Temp);
          rsp2read(&Temp,STGLEN,ppTerminal[State-1],NULL);
          return;
     }
     if (sameto("sa:agent ",dpkstg)) {
          INT State;
          struct saunam Temp;

          dpkstg=skpwht(skpwrd(dpkstg));
          State=atoi(dpkstg);
          if (direction < 0) {
               State--;
          }
          else if (direction > 0) {
               State++;
          }
          if (State < 1 || State > nClient) {
               rejectreq();
               return;
          }
          sprintf(vdatmp,"sa:agent %03d",State);
          cnvd2s(vdatmp,&Temp);
          rsp2read(&Temp,STGLEN,ppClient[State-1],NULL);
          return;
     }
     if (sameto("sa:checkpage ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          strcpy(PSCRATCH->menutype,dpkstg);
          if (!dfaAcqEQ(NULL,PSCRATCH->menutype,0)) {
               rsp2read(NULL,STGLEN,"No such page",NULL);
               return;
          }
          if ((PSCRATCH->flags&TPOTRE) != 0) {
               rsp2read(NULL,STGLEN,"Orphan page",NULL);
          }
          else {
               rsp2read(NULL,STGLEN,"Child page",NULL);
          }
          return;
     }
     if (sameto("saf:geticon ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          sprintf(vdatmp,"WGSICONS\\%s",dpkstg);
          if (!fnd1st(&IconScan,vdatmp,0)) {
               rejectreq();
          }
          else {
               rsp2read(NULL,STGLEN,vdatmp,NULL);
          }
          return;
     }
     if (sameto("saf:buildtree ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          if (toupper(*dpkstg) == 'A' || toupper(*dpkstg) == 'C') {
               Work.menutype[0]=toupper(*dpkstg);
               StartTree();
          }
          else {
               rejectreq();
          }
          return;
     }
     if (sameto("sa:getpage ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          strcpy(PSCRATCH->menutype,dpkstg);
          if (!dfaAcqEQ(NULL,PSCRATCH->menutype,0)) {
               rejectreq();
               return;
          }
          rsp2read(NULL,sizeof(struct mnupag)-SIZEDIFF,PSCRATCH,mnupagFDA);
          return;
     }
     if (sameto("sa:getlinks ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          strcpy(PSCRATCH->menutype,dpkstg);
          if (!dfaAcqEQ(NULL,PSCRATCH->menutype,0)) {
               rejectreq();
               return;
          }
          if (PSCRATCH->npages == 0) {
               // Special case: pass back an empty link so that the rsp2read
               // doesn't have zero length, which appears as a rejection.
               PSCRATCH->npages=1;
               memset(PSCRATCH->page,0,sizeof(struct pglink));
               PSCRATCH->page[0].selchr=0xFF;
          }
          rsp2read(NULL,sizeof(struct pglink)*PSCRATCH->npages,PSCRATCH->page,
               pagelinkarrayFDA);
          return;
     }
     rejectreq();
}

static VOID
MenuWrite(
struct saunam *dpknam,
USHORT length,
VOID *value)
{
     CHAR *dpkstg;

     (VOID)length;
     if (!stdchk(medkey)) {
          rejectreq();
          return;
     }
     dpkstg=cnvs2d(dpknam);
     dfaSetBlk(mnubb);
     if (sameas(dpkstg,"sa:lock")) {
          if (UseChan == -1) {
               UseChan=usrnum;
               rsp2write(TRUE,STGLEN,"Access granted",NULL);
               return;
          }
          rsp2write(FALSE,STGLEN,"Someone else using Menu Editor now!",NULL);
          return;
     }
     if (usrnum != UseChan) {
          rsp2write(FALSE,STGLEN,"Someone else using Menu Editor now!",NULL);
          return;
     }
     if (sameas(dpkstg,"sa:unlock")) {
          UseChan=-1;
          rsp2write(TRUE,STGLEN,"Unlocked successfully",NULL);
          return;
     }
     if (sameto("saf:uploadicon",dpkstg)) {
          sprintf(vdatmp,"WGSICONS\\%s",FINFPTR->name);
          ok2write(vdatmp);
          return;
     }
     if (sameto("sa:swapsel ",dpkstg)) {
          INT OneSwap;
          INT TwoSwap;
          INT Index;

          dpkstg=strchr(dpkstg,' ')+1;
          for (Index=0 ;
             dpkstg[Index] != '\0' && dpkstg[Index] != ' ' ; Index++) {
               vdatmp[Index]=dpkstg[Index];
          }
          vdatmp[Index]='\0';
          dpkstg=strchr(dpkstg,' ');
          if (dpkstg == NULL) {
               rsp2write(FALSE,STGLEN,"Invalid swap format",NULL);
               return;
          }
          dpkstg=dpkstg+1; // Skip over the space.
          OneSwap=atoi(dpkstg);
          TwoSwap=*(SHORT *)value;
          if (OneSwap < 0 || TwoSwap < 0
            || OneSwap >= MAXSEL || TwoSwap >= MAXSEL) {
               rsp2write(FALSE,STGLEN,"Invalid selection range",NULL);
               return;
          }
          if (!LoadPage(PSCRATCH,vdatmp)) {
               rsp2write(FALSE,STGLEN,"No such page",NULL);
               return;
          }
          if ((PSCRATCH->flags&(MDLPAG|FILPAG)) != 0) {
               rsp2write(FALSE,STGLEN,"Not a menu page!",NULL);
               return;
          }
          memcpy(vdatmp,
                 PSCRATCH->page+OneSwap,
                 sizeof(struct pglink));
          memcpy(PSCRATCH->page+OneSwap,
                 PSCRATCH->page+TwoSwap,
                 sizeof(struct pglink));
          memcpy(PSCRATCH->page+TwoSwap,vdatmp,
                 sizeof(struct pglink));
          SavePage(PSCRATCH);
          rsp2write(TRUE,STGLEN,"Swapped ok",NULL);
          return;
     }
     if (sameto("sa:setselection ",dpkstg)) {
          INT Choice;
          LONG TempTime;

          dpkstg=strchr(dpkstg,' ')+1;
          if (!dfaAcqEQ(NULL,dpkstg,0)) {
               rsp2write(FALSE,STGLEN,"No such page",NULL);
               return;
          }
          if ((PSCRATCH->flags&(MDLPAG|FILPAG)) != 0) {
               rsp2write(FALSE,STGLEN,"Not a menu page",NULL);
               return;
          }
          Choice=pTempSel->position;
          CLIPPY(pTempSel->iconame);
          CLIPPY(pTempSel->destpage);
          CLIPPY(pTempSel->keyreq);
          pTempSel->shortd[SHDSIZ-1]='\0';
          pTempSel->longd[LNDSIZ-1]='\0';
          depad(pTempSel->shortd);
          depad(pTempSel->longd);
          strupr(pTempSel->iconame);
          strupr(pTempSel->destpage);
          strupr(pTempSel->keyreq);
          if (sameas(pTempSel->destpage,"TOP")) {
               rsp2write(FALSE,STGLEN,"Can't link to TOP page",NULL);
               return;
          }
          unpack(PSCRATCH);
          if (!sameas(pTempSel->iconame,PSCRATCH->page[Choice].iconame)) {
               PSCRATCH->ichange=time(&TempTime);
          }
          if (sameas(pTempSel->destpage,PSCRATCH->page[Choice].destpage)) {
               memcpy(PSCRATCH->page+Choice,pTempSel,sizeof(struct pglink));
               repack(PSCRATCH);
               SmartUpdate(NULL);
               rsp2write(FALSE,STGLEN,"Updated ok",NULL);
               return;
          }
          // If it's a new pagnam, it must exist already as an orphan page.
          // Spawn page creates such orphan pages.  Or, we could just be
          // linking to an orphan page straight up (graft).
          if (!dfaAcqEQ(&Work,spr("%c%s",PSCRATCH->menutype[0],
                                     pTempSel->destpage),0)) {
               rsp2write(FALSE,STGLEN,"Not a valid destination page",NULL);
               return;
          }
          if (!(Work.flags&TPOTRE)) {
               rsp2write(FALSE,STGLEN,"Does not reference orphan page!",NULL);
               return;
          }
          Work.flags&=~TPOTRE;
          strcpy(Work.parpag,PSCRATCH->pagnam);
          SmartUpdate(&Work);
          dfaAcqEQ(NULL,PSCRATCH->menutype,0);
          unpack(PSCRATCH);
          memcpy(PSCRATCH->page+Choice,pTempSel,sizeof(struct pglink));
          repack(PSCRATCH);
          SmartUpdate(NULL);
          rsp2write(TRUE,STGLEN,"Updated ok",NULL);
          return;
     }
     if (sameas(dpkstg,"sa:spawnpage")) {
          CLIPPY(pTempSpawn->PageName);
          CLIPPY(pTempSpawn->ParentName);
          strupr(pTempSpawn->PageName);
          strupr(pTempSpawn->ParentName);
          if (!ValidatePage(spr("%c%s",
                                pTempSpawn->Mode,
                                pTempSpawn->PageName),FALSE)) {
               rsp2write(FALSE,STGLEN,vdatmp,NULL);
               return;
          }
          if (!dfaAcqEQ(NULL,spr("%c%s",
                                 pTempSpawn->Mode,
                                 pTempSpawn->ParentName),0)) {
               rsp2write(FALSE,STGLEN,"No such parent",NULL);
               return;
          }
          unpack(PSCRATCH);
          if (PSCRATCH->page[pTempSpawn->Selection].destpage[0] != '\0') {
               if (dfaAcqEQ(&Work,
                       spr("%c%s",
                          pTempSpawn->Mode,
                          PSCRATCH->page[pTempSpawn->Selection].destpage),0)) {
                    Work.flags|=TPOTRE;
                    strcpy(Work.parpag,"TOP");
                    SmartUpdate(&Work);
                    pglset(Work.menutype[0] == 'A' ? &aaftop : &csftop);
                    pglopins(Work.pagnam);
                    if (gofind == 1 && Work.menutype[0] == 'C') {
                         pglset(&csfgop);
                         pglopins(spr("%s %s %s",Work.pagnam,
                                                 Work.golock,
                                                 gofdesc(&Work)));
                    }
                    dfaAcqEQ(NULL,PSCRATCH->menutype,0);
               }
          }
          if (PSCRATCH->menutype[0] == 'C') {
               PSCRATCH->page[pTempSpawn->Selection].selchr='!';
          }
          else {
               PSCRATCH->page[pTempSpawn->Selection].selchr=pTempSpawn->selchr;
          }
          strcpy(PSCRATCH->page[pTempSpawn->Selection].destpage,
                 pTempSpawn->PageName);
          PSCRATCH->npages++;
          repack(PSCRATCH);
          SmartUpdate(NULL);
          setmem(PSCRATCH,sizeof(struct mnupag),0);
          PSCRATCH->menutype[0]=pTempSpawn->Mode;
          strcpy(PSCRATCH->pagnam,pTempSpawn->PageName);
          strcpy(PSCRATCH->parpag,pTempSpawn->ParentName);
          switch (pTempSpawn->Type) {
          case 1:
               PSCRATCH->flags=CNGOTO;
               break;
          case 2:
               PSCRATCH->flags=CNGOTO|MDLPAG;
               if (PSCRATCH->menutype[0] == 'C') {
                    PSCRATCH->flags|=CSMPAG;
               }
               break;
          case 3:
               PSCRATCH->flags=CNGOTO|FILPAG;
               break;
          }
          PSCRATCH->flags|=CNGOTO;
          dfaInsert(NULL);
          rsp2write(TRUE,STGLEN,"Spawned page",NULL);
          return;
     }
     if (sameto("sa:createpage ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          if (*dpkstg != 'A' && *dpkstg != 'C') {
               rsp2write(FALSE,STGLEN,"Invalid page type",NULL);
               return;
          }
          if (!ValidatePage(dpkstg,FALSE)) {
               rsp2write(FALSE,STGLEN,vdatmp,NULL);
               return;
          }
          setmem(PSCRATCH,sizeof(struct mnupag),0);
          PSCRATCH->menutype[0]=*dpkstg;
          dpkstg++;
          stzcpy(PSCRATCH->pagnam,dpkstg,PNMSIZ);
          CLIPPY(PSCRATCH->pagnam);
          strupr(PSCRATCH->pagnam);
          PSCRATCH->flags=TPOTRE|CNGOTO;
          strcpy(PSCRATCH->parpag,"TOP");
          switch (*(SHORT *)value) {
          case 0: // File
               PSCRATCH->flags|=FILPAG;
               break;
          case 1: // Menu.
               break;
          case 2: // Module.
               PSCRATCH->flags|=MDLPAG;
               if (PSCRATCH->menutype[0] == 'C') {
                    PSCRATCH->flags|=CSMPAG;
               }
               break;
          default:
               rsp2write(FALSE,STGLEN,"Invalid page type",NULL);
               return;
          }
          dfaInsert(NULL);
          if ((PSCRATCH->flags&TPOTRE) != 0) {
               pglset(PSCRATCH->menutype[0] == 'A' ? &aaftop : &csftop);
               pglopins(PSCRATCH->pagnam);
               if (gofind == 1 && PSCRATCH->menutype[0] == 'C') {
                    pglset(&csfgop);
                    pglopins(spr("%s %s %s",PSCRATCH->pagnam,
                                            PSCRATCH->golock,
                                            gofdesc(PSCRATCH)));
               }
          }
          rsp2write(TRUE,STGLEN,"Created page",NULL);
          return;
     }
     if (sameto("sa:setpage ",dpkstg)) {
          INT Temp;

          dpkstg=strchr(dpkstg,' ')+1;
          strcpy(PSCRATCH->menutype,dpkstg);
          if (!dfaAcqEQ(NULL,PSCRATCH->menutype,0)) {
               rsp2write(FALSE,STGLEN,"No such page to save (?!)",NULL);
               return;
          }
          Temp=PSCRATCH->npages;
          memcpy(PSCRATCH,value,sizeof(struct mnupag)-SIZEDIFF);
          CLIPPY(PSCRATCH->pagnam);
          CLIPPY(PSCRATCH->parpag);
          CLIPPY(PSCRATCH->fname);
          CLIPPY(PSCRATCH->modnam);
          CLIPPY(PSCRATCH->cmdstg);
          CLIPPY(PSCRATCH->golock);
          CLIPPY(PSCRATCH->mnuttl);
          PSCRATCH->npages=Temp;
          SmartUpdate(PSCRATCH);
          rsp2write(TRUE,STGLEN,"Update successful",NULL);
          return;
     }
     if (sameto("sa:deletepage ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          if (!dfaAcqEQ(&Work,dpkstg,0)) {
               rsp2write(FALSE,STGLEN,"No such page",NULL);
               return;
          }
          if (sameas(Work.pagnam,"TOP")) {
               rsp2write(FALSE,STGLEN,"Can't delete TOP page",NULL);
               return;
          }
          CSKillPage();
          rsp2write(TRUE,STGLEN,"Deleted page",NULL);
          return;
     }
     if (sameto("sa:changetype ",dpkstg)) {
          INT NewFlags;

          dpkstg=strchr(dpkstg,' ')+1;
          if (!dfaAcqEQ(&Work,dpkstg,0)) {
               rsp2write(FALSE,STGLEN,"No such page",NULL);
               return;
          }
          if (sameas(Work.pagnam,"TOP")) {
               rsp2write(FALSE,STGLEN,"Can't change type of TOP page",NULL);
               return;
          }
          switch (*(SHORT *)value) {
          case 1:
               NewFlags=DFTDSP;
               break;
          case 2:
               NewFlags=MDLPAG;
               if (Work.menutype[0] == 'C') {
                    NewFlags|=CSMPAG;
               }
               break;
          case 3:
               NewFlags=FILPAG;
               break;
          default:
               rsp2write(FALSE,STGLEN,"Bogus page type",NULL);
               return;
          }
          if (!(Work.flags&(FILPAG|MDLPAG))) {
               INT Index;

               for (Index=0 ; Index < MAXSEL ; Index++) {
                    if (Work.page[Index].destpage[0] != '\0') {
                         if (LoadPage(PSCRATCH,spr("%c%s",Work.menutype[0],
                                               Work.page[Index].destpage))) {
                              strcpy(PSCRATCH->parpag,"TOP");
                              PSCRATCH->flags|=TPOTRE;
                              SavePage(PSCRATCH);
                              pglset(PSCRATCH->menutype[0] == 'A' ? &aaftop
                                                                  : &csftop);
                              pglopins(PSCRATCH->pagnam);
                              if (gofind == 1
                                && PSCRATCH->menutype[0] == 'C') {
                                   pglset(&csfgop);
                                   pglopins(spr("%s %s %s",
                                                PSCRATCH->pagnam,
                                                PSCRATCH->golock,
                                                gofdesc(PSCRATCH)));
                              }
                         }
                    }
               }
          }
          memset(Work.page,0,sizeof(Work.page));
          memset(Work.fname,0,sizeof(Work.fname));
          memset(Work.modnam,0,sizeof(Work.modnam));
          memset(Work.cmdstg,0,sizeof(Work.cmdstg));
          memset(Work.golock,0,sizeof(Work.golock));
          Work.npages=0;
          Work.flags&=~(MDLPAG|FILPAG);     // Zap old type flags.
          Work.flags|=NewFlags;             // Set new type flags.
          dfaGetEQ(NULL,dpkstg,0);
          SavePage(&Work);
          rsp2write(TRUE,STGLEN,"Changed type",NULL);
          return;
    }
    if (sameto("sa:renamepage ",dpkstg)) {
          dpkstg=strchr(dpkstg,' ')+1;
          if (!ValidatePage(dpkstg,TRUE)) {
               rsp2write(FALSE,STGLEN,vdatmp,NULL);
               return;
          }
          if (!ValidatePage(spr("%c%s",*dpkstg,value),FALSE)) {
               rsp2write(FALSE,STGLEN,vdatmp,NULL);
               return;
          }
          ((CHAR *)value)[PNMSIZ-1]='\0';
          if (!dfaAcqEQ(&Work,dpkstg,0)) {
               rsp2write(FALSE,STGLEN,"No such page",NULL);
               return;
          }
          if (sameas(Work.pagnam,"TOP")) {
               rsp2write(FALSE,STGLEN,"Can't rename TOP page",NULL);
               return;
          }
          if (dfaAcqEQ(NULL,spr("%c%s",Work.menutype[0],value),0)) {
               rsp2write(FALSE,STGLEN,spr("%s already exists",value),NULL);
               return;
          }
          CSRenamePage((CHAR *)value);
          rsp2write(TRUE,STGLEN,"Renamed page",NULL);
          return;
     }
     rejectreq();
}

static VOID
MenuDone(VOID)
{
     if (iswrite()) {
          rsp2write(TRUE,STGLEN,"Icon uploaded",NULL);
     }
}

static VOID
MenuDisconnect(VOID)
{
     if (UseChan == usrnum) {
          UseChan=-1;
     }
}

static VOID
CSKillPage(VOID)                   /* Kill current page */
/* If it's a child of a menu page, go back to that menu page and zap the   */
/* selection info.  If its page is a menu page, orphan all of its children */
{
     if (!(Work.flags&TPOTRE)) {
          if (LoadPage(PSCRATCH,spr("%c%s",Work.menutype[0],Work.parpag))) {
               INT Index;

               Index=FindPage(PSCRATCH,Work.pagnam);
               if (Index != -1) {
                    memset(PSCRATCH->page+Index,0,sizeof(struct pglink));
                    PSCRATCH->ichange=time(&PSCRATCH->ichange);
                    SavePage(PSCRATCH);
               }
          }
     }
     if (LoadPage(PSCRATCH,Work.menutype)) {
          dfaDelete();
          if ((PSCRATCH->flags&TPOTRE) != 0) {
               pglset(PSCRATCH->menutype[0] == 'A' ? &aaftop : &csftop);
               pglopdel(PSCRATCH->pagnam);
               if (gofind == 1 && PSCRATCH->menutype[0] == 'C') {
                    pglset(&csfgop);
                    pglopdel(PSCRATCH->pagnam);
               }
          }
          if (!(Work.flags&(FILPAG|MDLPAG))) {
               INT Index;

               for (Index=0 ; Index < MAXSEL ; Index++) {
                    if (Work.page[Index].selchr != '\0') {
                         if (LoadPage(PSCRATCH,
                                      spr("%c%s",
                                          Work.menutype[0],
                                          Work.page[Index].destpage))) {
                              strcpy(PSCRATCH->parpag,"TOP");
                              PSCRATCH->flags|=TPOTRE;
                              SavePage(PSCRATCH);
                              pglset(PSCRATCH->menutype[0] == 'A' ? &aaftop
                                                                  : &csftop);
                              pglopins(PSCRATCH->pagnam);
                              if (gofind == 1
                               && PSCRATCH->menutype[0] == 'C') {
                                   pglset(&csfgop);
                                   pglopins(spr("%s %s %s",
                                            PSCRATCH->pagnam,
                                            PSCRATCH->golock,
                                            gofdesc(PSCRATCH)));
                              }
                         }
                    }
               }
          }
     }
}

static VOID
CSRenamePage(                      /* Rename the current page              */
CHAR *pNewName)                    /*   (uppercases)                       */
{
     strupr(pNewName);
     if (!(Work.flags&TPOTRE)) {
          if (LoadPage(PSCRATCH,spr("%c%s",Work.menutype[0],Work.parpag))) {
               INT Index;

               Index=FindPage(PSCRATCH,Work.pagnam);
               if (Index != -1) {
                    strcpy(PSCRATCH->page[Index].destpage,pNewName);
                    SavePage(PSCRATCH);
               }
          }
     }
     if (LoadPage(PSCRATCH,spr("%c%s",Work.menutype[0],Work.pagnam))) {
          pglset(Work.menutype[0] == 'A' ? &aaftop : &csftop);
          pglopdel(Work.pagnam);
          if (gofind == 1 && Work.menutype[0] == 'C') {
               pglset(&csfgop);
               pglopdel(Work.pagnam);
          }
          strcpy(Work.pagnam,pNewName);
          pglset(Work.menutype[0] == 'A' ? &aaftop : &csftop);
          pglopins(Work.pagnam);
          if (gofind == 1 && Work.menutype[0] == 'C') {
               pglset(&csfgop);
               pglopins(spr("%s %s %s",Work.pagnam,
                                       Work.golock,
                                       gofdesc(&Work)));
          }
          SavePage(&Work);
          unpack(&Work);
          if (!(Work.flags&(FILPAG|MDLPAG))) {
               INT Index;

               for (Index=0 ; Index < MAXSEL ; Index++) {
                    if (Work.page[Index].selchr != '\0') {
                         if (LoadPage(PSCRATCH,spr("%c%s",
                                                Work.menutype[0],
                                                Work.page[Index].destpage))) {
                              stzcpy(PSCRATCH->parpag,Work.pagnam,PNMSIZ);
                              SavePage(PSCRATCH);
                         }
                    }
               }
          }
     }
}

static VOID
SmartUpdate(
struct mnupag *pPage)
{
     USHORT Length;

     if (pPage == NULL) {
          pPage=PSCRATCH;
     }
     Length=sizeof(struct mnupag)-(MAXSEL-pPage->npages)*sizeof(struct pglink);
     if (pPage->menutype[0] == 'A') {
          INT Index;
          CHAR c;

          for (Index=0 ; Index < pPage->npages ; Index++) {
               c=pPage->page[Index].selchr;
               if (c >= 'a' && c <= 'z') {
                    pPage->page[Index].selchr+='A'-'a';
               }
          }
     }
     dfaUpdateV(pPage,Length);
}

