/* Copyright (C) 1996,1997,1998,1999 by Salvador E. Tropea (SET),
   see copyrigh file for details */
/**[txh]********************************************************************

 Module: Menu Loader
 Comments:
 This module is used to load the editor's menu from a text file.

***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define Uses_TMenu
#define Uses_TMenuItem
#define Uses_TSubMenu
#define Uses_TMenuBar
#define Uses_TKeys
#define Uses_TRect
#define Uses_MsgBox
#define Uses_TCEditor
#define Uses_TCEditor_Commands
#define Uses_TStringCollection
#include <ceditor.h>
#define Uses_SETAppConst
#define Uses_SETAppVarious
#include <setapp.h>

#include <ctype.h>
#include <keytrans.h>

const int maxLineLen=255;
const int errMLExpectedStr=1,
          errMLUnclosedStr=2,
          errMLNoKeyInSubMenu=3,
          errMLWrongKeyName=4,
          errMLExtraCharsInLine=5,
          errMLNoNumberForContext=6,
          errMLSyntax=7,
          errMLEmptySubMenu=8,
          errMLUnclosedSubMenu=9,
          errMLNoCommandInMenuItem=10,
          errMLWrongCommandName=11,
          errMLNoMenuDefinition=12,
          errMLWrongCtxName=13,
          errMLUnfinishedMacro=14,
          errMLCantRegisterMacro=15;

static const char *ErrorNames[] =
{
 __("No error"),
 __("String expected"),
 __("Unclosed string"),
 __("No key for SubMenu"),
 __("Wrong key name"),
 __("Extra characters in line"),
 __("No number for a context"),
 __("Syntax error"),
 __("Empty submenu"),
 __("Unclosed SubMenu"),
 __("No command for MenuItem"),
 __("Wrong command name"),
 __("No menu definition"),
 __("Wrong context name"),
 __("Unfinished macro name"),
 __("Unable to register macro")
};

static int Error=0;
static int Line;
static char *FileName=0;

typedef struct
{
 char start;
 char escape;
 char *sIf,*sElse,*sEnd;
 char *sDefined,*sIfDef,*sIfNDef;
} stPreproInfo;

static stPreproInfo PreproInfo={'$',0,"if","else","endif","defined","ifdef","ifndef"};

const unsigned prliASCIIZ=1,prliNoASCIIZ=0;
const unsigned prliEatSpaces=2,prliNoEatSpaces=0;
const int prlisNoPL=0,prlisPLContinue=1,prlisPLOK=2;
const int prlieNoErr=0,prlieSyntax=-1,prlieElse=2,prlieEnd=3;

static int ReadPreprocessor(char *s, stPreproInfo *p, DynStrCatStruct *str,
                            int PreproValue, TStringCollection *defs, char *buf,
                            FILE *f);

#define GetLine() { fgets(buf,maxLineLen,f); Line++; }

char *SkipBlanks(char *buf)
{
 char *s;
 for (s=buf; *s!='\n' && *s && ucisspace(*s); s++);
 return s;
}

static
char *GetString(char *s)
{
 if (*s!='\"')
   {
    Error=errMLExpectedStr;
    return 0;
   }
 for (s++; *s && *s!='\n' && *s!='\"'; s++);
 if (*s!='\"')
   {
    Error=errMLUnclosedStr;
    return 0;
   }
 *s=0;
 return s;
}

static
char *GetNumber(char *s, int &key, int error1, int error2, int Optional=0)
{
 char *ret;

 s=SkipBlanks(s);
 if (Optional && (*s=='\n' || *s==0))
    return s;
 if (*s!=',')
   {
    Error=error1;
    return 0;
   }
 s=SkipBlanks(s+1);
 if (!ucisdigit(*s))
   {
    Error=error2;
    return 0;
   }
 key=strtol(s,&ret,0);
 return ret;
}

static
char *GetKey(char *s, int &key, int error1, int Optional=0)
{
 char *ret;

 s=SkipBlanks(s);
 if (Optional && (*s=='\n' || *s==0))
    return s;
 if (*s!=',')
   {
    Error=error1;
    return 0;
   }
 s=SkipBlanks(s+1);

 if (*s!='k' || *(s+1)!='b')
   {
    Error=error1;
    return 0;
   }
 s+=2;
 for (ret=s; *ret && ucisalnum(*ret); ret++);
 char v=*ret; *ret=0;
 ushort code;
 if (InterpretKeyName(s,code))
   {
    *ret=v;
    Error=errMLWrongKeyName;
    return 0;
   }
 *ret=v;
 key=code;
 return ret;
}

static
char *GetCommand(char *s, int &command)
{
 char *ret;
 int isAMacro=0;

 s=SkipBlanks(s);
 if (*s!=',')
   {
    Error=errMLNoCommandInMenuItem;
    return 0;
   }
 s=SkipBlanks(s+1);

 if (*s!='c' || *(s+1)!='m')
   {
    Error=errMLNoCommandInMenuItem;
    return 0;
   }
 s+=2;
 if (*s!='c' && *s!='e' && *s!='(')
   {
    Error=errMLWrongCommandName;
    return 0;
   }
 isAMacro= *s=='(';
 s++;

 if (isAMacro)
   {
    for (ret=s; *ret && *ret!=')'; ret++);
    if (*ret!=')')
      {
       Error=errMLUnfinishedMacro;
       return 0;
      }
    char v=*ret; *ret=0;
    command=RegisterMacroCommand(s);
    *ret=v;
    ret++; // Skip the last parethesis
    if (command==-1)
      {
       Error=errMLCantRegisterMacro;
       return 0;
      }
   }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
 else
   {
    for (ret=s; *ret && ucisalnum(*ret); ret++);
    char v=*ret; *ret=0;
    if (*(s-1)=='c')
      {
       command=SearchEdCommand(s);
       if (command>=0)
          command+=cmbBaseNumber;
      }
    else
      {
       command=SearchEditCommand(s);
       if (command>=0)
          command+=cmeBase;
      }
    *ret=v;
    if (command==-1)
      {
       Error=errMLWrongCommandName;
       return 0;
      }
   }
 return ret;
}

// MenuItem: "Name", Command[, Key [, Context [,"KeyName"]]]
// MenuItemC: "Name", Command[, Key [,"KeyName"]] -> copyContext
static
TMenuItem *GetMenuItem(char *s,int copyContext)
{
 char *sEnd,*name,*extra=0;
 int key=kbNoKey,context=hcNoContext,command;

 // Now we'll parse the MenuItem definition.

 // Get the name
 s=SkipBlanks(s);
 sEnd=GetString(s);
 if (Error) return 0;
 name=s+1;

 // Get the command
 //s=GetNumber(sEnd+1,command,errMLNoCommandInMenuItem,errMLNoNumberForCommand);
 s=GetCommand(sEnd+1,command);
 if (Error) return 0;
 // Get the key
 s=GetKey(s,key,errMLExtraCharsInLine,1);
 if (Error) return 0;
 // Get the context
 if (copyContext)
    context=command;
 else
   {
    s=GetNumber(s,context,errMLExtraCharsInLine,errMLWrongCtxName,1);
    if (Error) return 0;
   }
 // Get the extra string
 s=SkipBlanks(s);
 if (*s!='\n' && *s!=0)
   {
    if (*s!=',')
      {
       Error=errMLExtraCharsInLine;
       return 0;
      }
    s=SkipBlanks(s+1);
    sEnd=GetString(s);
    if (Error) return 0;
    extra=s+1;
   }

 return new TMenuItem(_(name),command,key,context,extra);
}

static
TSubMenu *GetSubMenu(FILE *f, char *buf, char *s, stPreproInfo *PreproInfo,
                     DynStrCatStruct *Cat, TStringCollection *defs, int &PreproValue)
{
 char *sEnd,*name;
 int key,context=hcNoContext;

 s=SkipBlanks(s);

 // Now we'll parse the SubMenu definition.
 // SubMenu: "Name", Key [,Context]

 // Get the name
 sEnd=GetString(s);
 if (Error) return 0;
 name=s+1;
 // Get the key
 s=GetKey(sEnd+1,key,errMLNoKeyInSubMenu);
 if (Error) return 0;
 // Get context
 s=GetNumber(s,context,errMLExtraCharsInLine,errMLNoNumberForContext,1);
 if (Error) return 0;

 // We had luck ;-)
 TSubMenu *sub=new TSubMenu(_(name),key,context);
 TMenuItem *firstMenu=0;
 TMenuItem *lastMenu=0;
 TMenuItem *newMenu;

 // Now we'll get the items:
 GetLine();
 while (!feof(f) && !Error)
   {
    s=SkipBlanks(buf);
    if (*s=='$')
      {
       PreproValue=ReadPreprocessor(s,PreproInfo,Cat,PreproValue,defs,buf,f);
      }
    else
    if (*s!='#' && *s!='\n' && PreproValue) // Skip comment lines
      {
       newMenu=0;
       if (strncasecmp(s,"SubMenu:",8)==0)
         { // Recursive menues
          newMenu=(TMenuItem *)GetSubMenu(f,buf,s+8,PreproInfo,Cat,defs,PreproValue);
          if (!newMenu)
             return 0; // Fail
         }
       else
       if (strncasecmp(s,"MenuItem:",9)==0)
         { // Just an item
          newMenu=GetMenuItem(s+9,0);
          if (!newMenu)
             return 0; // Fail
         }
       else
       if (strncasecmp(s,"MenuItemC:",10)==0)
         { // Just an item
          newMenu=GetMenuItem(s+10,1);
          if (!newMenu)
             return 0; // Fail
         }
       else
       if (strncasecmp(s,"MenuSeparator",13)==0)
         { // A separator
          newMenu=&newLine();
         }
       else
       if (strncasecmp(s,"EndSubMenu",10)==0)
         { // The enmd of the submenu
          if (firstMenu)
            {
             sub->subMenu=new TMenu(*firstMenu);
             return sub;
            }
          else
            {
             Error=errMLEmptySubMenu;
             return 0;
            }
         }
       else
         {
          Error=errMLSyntax;
          return 0; // Fail
         }
       // Link it
       if (newMenu)
         {
          if (firstMenu)
            {
             lastMenu->next=newMenu;
             lastMenu=newMenu;
            }
          else
             lastMenu=firstMenu=newMenu;
         }
      }
    if (!Error)
       GetLine();
   }
 Error=errMLUnclosedSubMenu;
 return 0;
}

static
int PreproLine_Start(char *line, unsigned len, unsigned flags, stPreproInfo *p,
                     DynStrCatStruct *str)
{
 char *s;

 if (flags & prliASCIIZ)
    len=strlen(line);
 if (flags & prliEatSpaces)
   {
    s=SkipBlanks(line);
    len-=s-line;
   }
 else
    s=line;
 // Eliminate the \n
 if (s[len-1]=='\n')
   {
    len--;
    s[len]=0;
   }
 // Is a valid preprocessor line
 if (*s!=p->start)
    return prlisNoPL;
 // Skip de preprocessor character
 s++;
 len--;
 // Start concatenating escaped lines
 DynStrCatInit(str,s,len);
 // Is multiline?
 if (s[len-1]==p->escape)
   { // Yes, ask for more and eliminate the escape char
    str->str[len-1]=' ';
    return prlisPLContinue;
   }
 return prlisPLOK;
}

static
int PreproLine_Continue(char *line, unsigned len, unsigned flags, stPreproInfo *p,
                        DynStrCatStruct *str)
{
 if (flags & prliASCIIZ)
    len=strlen(line);
 // Eliminate the \n
 if (line[len-1]=='\n')
   {
    len--;
    line[len]=0;
   }
 // Concatenate
 DynStrCat(str,line,len);
 // Is multiline?
 if (line[len-1]==p->escape)
   { // Yes, ask for more and eliminate the escape char
    str->str[len-1]=' ';
    return prlisPLContinue;
   }
 return prlisPLOK;
}

static
int GetLenOfToken(char *&str)
{
 char *s=str;
 while (*s && ucisspace(*s)) s++;
 str=s;
 while (*s && !ucisspace(*s)) s++;
 int r=s-str;
 //str=s;
 return r;
}

static
int GetLenOfWord(char *&str)
{
 char *s=str;
 while (*s && ucisspace(*s)) s++;
 str=s;
 while (*s && (ucisalnum(*s) || *s=='_')) s++;
 int r=s-str;
 //str=s;
 return r;
}

static
int PreproLine_InterpretIfDef(char *s, stPreproInfo *, TStringCollection *defs,
                              int yes)
{
 int l=GetLenOfWord(s),Value;
 ccIndex pos;
 char val;

 val=s[l];
 s[l]=0;
 Value=defs->search(s,pos)==True ? 1 : 0;
 s[l]=val;

 if (!yes)
    Value=Value ? 0 : 1;

 return Value;
}

static
int PreproLine_InterpretIf(char *, stPreproInfo *, TStringCollection *)
{
 return prlieSyntax;
}


static
int PreproLine_Interpret(stPreproInfo *p, DynStrCatStruct *str,
                         TStringCollection *defs)
{
 char *s=str->str;
 int l,ret=prlieSyntax;

 l=GetLenOfToken(s);
 if (strncasecmp(s,p->sIf,l)==0)
    ret=PreproLine_InterpretIf(s+l,p,defs);
 else
 if (strncasecmp(s,p->sIfDef,l)==0)
    ret=PreproLine_InterpretIfDef(s+l,p,defs,1);
 else
 if (strncasecmp(s,p->sIfNDef,l)==0)
    ret=PreproLine_InterpretIfDef(s+l,p,defs,0);
 else
 if (strncasecmp(s,p->sElse,l)==0)
    ret=prlieElse;
 else
 if (strncasecmp(s,p->sEnd,l)==0)
    ret=prlieEnd;
 free(str->str);
 return ret;
}


static
int ReadPreprocessor(char *s, stPreproInfo *p, DynStrCatStruct *str, int PreproValue,
                     TStringCollection *defs, char *buf, FILE *f)
{
 int ret;
 ret=PreproLine_Start(s,0,prliNoEatSpaces | prliASCIIZ,p,str);
 if (ret==prlieSyntax)
    Error=errMLSyntax;
 else
   {
    while (ret==prlisPLContinue)
      {
       GetLine();
       ret=PreproLine_Continue(buf,0,prliNoEatSpaces | prliASCIIZ,p,str);
      }
    if (ret==prlieSyntax)
       Error=errMLSyntax;
    else
      {
       ret=PreproLine_Interpret(p,str,defs);
       switch (ret)
         {
          case 0:
               PreproValue=0;
               break;
          case 1:
               PreproValue=1;
               break;
          case prlieElse:
               PreproValue=PreproValue ? 0 : 1;
               break;
          case prlieEnd:
               PreproValue=1;
               break;
          case prlieSyntax:
               Error=errMLSyntax;
               break;
         }
      }
   }
 return PreproValue;
}

/**[txh]********************************************************************

 Description:
 Reads the menu from the file fileName and creates a TMenuBar with the
provided rectangle as size.

 Return:
 0 if something fails. To display the error call @x{ShowMenuLoadError}.

***************************************************************************/

TMenuBar *LoadTVMenu(char *fileName, TRect &rect)
{
 FILE *f;
 char buf[maxLineLen+1];
 char *s;
 TSubMenu *subMenu=0,*lastSubMenu=0;
 DynStrCatStruct Cat;
 int PreproValue=1;

 Error=0; Line=0;
 f=fopen(fileName,"rt");
 if (!f)
    return 0;
 FileName=strdup(fileName);
 TStringCollection *defs=new TStringCollection(1,1);
 #ifdef __DJGPP__
 defs->insert(strdup("DOS"));
 #else
 defs->insert(strdup("Linux"));
 #endif

 GetLine();
 while (!feof(f) && !Error)
   {
    s=SkipBlanks(buf);
    /*if (*s=='#') No longer needed
      {
       char *e,*p;
       Line=strtol(s+1,&s,0);
       s=SkipBlanks(s)+1;
       for (e=s; *e!='\"' && *e && *e!='\n'; e++);
       if (*e=='\"')
          Line-=strtol(e+1,&p,0);
       *e=0;
       delete FileName;
       FileName=strdup(s);
      }
    else*/
    if (*s=='$') // Preprocessor lines
      {
       PreproValue=ReadPreprocessor(s,&PreproInfo,&Cat,PreproValue,defs,buf,f);
      }
    else
    if (*s!='#' && *s!='\n' && PreproValue) // Skip comment lines
      {
       if (strncasecmp(s,"SubMenu:",8)==0)
         {
          TSubMenu *m=GetSubMenu(f,buf,s+8,&PreproInfo,&Cat,defs,PreproValue);
          if (!Error)
            {
             if (strcasecmp(m->name,"Editor Right Click")==0)
                TCEditor::RightClickMenu=m;
             else
               {
                if (subMenu)
                  {
                   lastSubMenu->next=m;
                   lastSubMenu=m;
                  }
                else
                   subMenu=lastSubMenu=m;
               }
            }
         }
       else
          Error=errMLSyntax;
      }
    if (!Error)
       GetLine();
   }

 fclose(f);
 destroy(defs);
 if (!Error)
   {
    if (!subMenu)
       Error=errMLNoMenuDefinition;
    else
       return new TMenuBar(rect,*subMenu);
   }
 return 0;
}

/**[txh]********************************************************************

  Description:
  It just deletes anything allocated by this module.

***************************************************************************/

void UnLoadTVMenu(void)
{
 delete FileName;
 FileName=0;
 UnRegisterMacroCommands();
}


/**[txh]********************************************************************

 Description:
 Shows the error using a messageBox. If no error just returns.

***************************************************************************/

void ShowMenuLoadError(void)
{
 char buf[PATH_MAX+100];

 if (Error && FileName)
   {
    sprintf(buf,_("Error loading menu: (%d) %s in line %d of %s."),
            Error,_(ErrorNames[Error]),Line,FileName);
    messageBox(buf,mfError | mfOKButton);
    Error=0;
    delete FileName;
    FileName=0;
   }
}
