/*
 *  WICONIFY    A utility that allows you to iconify any Intuition window
 *              on any screen, and to open WB windows on any screen.
 *
 *  wFileRead.c     Performs most of the structured file IO.
 *
 *  Copyright 1990 by Davide P. Cervone, all rights reserved.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "wFile.h"
#include "wMenu.h"


extern int ScreenType;
extern UWORD ImageData[MAXWORDS][MAXHEIGHT][MAXDEPTH]; 
extern short MaxWidth,MaxHeight,MaxWords;
extern UBYTE Plane0,Plane1;
extern int ImageType;
extern int IconFileOpen;
static int NoMaskError;             /* FALSE if a mask error was reported */
static short ErrorCount;            /* number of error messages so far */

#define MAXERROR    8               /* maximum number of errors in a line */

/*
 *  Macros for working with HEX and Color values
 */

#define HEXCHAR(c)      (((c)>='0'&&(c)<='9')||((c)>='A'&&(c)<='F'))
#define VALIDCOLOR(w)\
   (HEXCHAR(w[0])&&HEXCHAR(w[1])&&HEXCHAR(w[2])&&w[3]==0)

#define HEX(c)          (((c)>'9')?(c)-'A'+10:(c)-'0')
#define RGB(r,g,b)      ((r<<8)|(g<<4)|b)
#define COLOR(w)        RGB(HEX(w[0]),HEX(w[1]),HEX(w[2]))

/*
 *  Allowable HEX codes
 */

static char *ExtHex = "0123456789abcdef)!@#$%^&*(ABCDEF";


/*
 *  The initialization file command keywords
 */

static char *MainCommand[] =
{
   "",                      /* No command given */
   "Iconify_Key",           /* Sets the key and quals that iconify a window */
   "Change_Refresh",        /* Sets the quals that change refresh type */
   "Activation_Key",        /* Sets the key and quals that activate wIconify */
   "Color_Map",             /* Sets the default NewScreen color map */
   "Default_Flags",         /* Sets the default icon's icon flags */
   "Screen_Flags",          /* Sets the default screen icon's icon flags */
   "Menu_Keys",             /* Sets the menu key equivalents */
   "Ignore_Screen",         /* Adds screen titles to be ignored by wIconify */
   "Priority",              /* Sets the wIconify process priority */
   "HiRes_CLICommand",      /* Sets the HIRES NewCLI command string */
   "LoRes_CLICommand",      /* Sets the LOWRES NewCLI command string */
   "Default_Image",         /* Defines the default icon image */
   "Default_Select",        /* Defines the default icon select image */
   "Default_Mask",          /* Defines the default icon image mask */
   "Default_Icon",          /* Reads the default icon from a file */
   "Screen_Image",          /* Defines the default screen icon image */
   "Screen_Select",         /* Defines the default screen icon select image */
   "Screen_Mask",           /* Defines the default screen icon image mask */
   "Screen_Icon",           /* Reads the default screen icon from a file */
   "Open_On",               /* Sets the OpenOn window submenu entries */
   "Size_To_Fit",           /* Sets or clears the SizeToFit option */
   "Command_Stack",         /* Sets the size of the newCLI command stack */
   "Handler_Priority",      /* Sets the Input Device handler priority */
};
#define MAXMAINCOM  COM_LAST


/*
 *  The commands for within an icon file (for DEFAULT_ICON and SCREEN_ICON)
 */

static char *IconCommand[] =
{
   "Image",                 /* Sets the default icon image */
   "Select",                /* Sets the default icon select image */
   "Mask"                   /* Sets the default icon image mask */
};
#define MAXICONCOM  2

char **ComName = &MainCommand[0];       /* The current command set */
static int ComOffset;                   /* Add this to get "real" command # */
static int ComCount = MAXMAINCOM;       /* Size of command array */


/*
 *  Icon Flags for DEFAULT_FLAGS command
 */

static struct Flag IconFlag[] =
{
   {"NOICONIFY",    WI_NOICONIFY},      /* Window can not be iconified */
   {"NOSAVEPOS",    WI_NOSAVEPOS},      /* Position not saved when openned */
   {"NOCLOSE",      WI_NOCLOSE},        /* CLOSE menu not available */
   {"NOORGANIZE",   WI_NOORGANIZE},     /* Icon not affected by ORGANIZE */
   {"NOMOVE",       WI_NOMOVE},         /* Icon can not be moved */
   {"LOCKED",       WI_LOCKED},         /* Never change icon X,Y */
   {"NOMULTISELECT",WI_NOMULTISELECT},  /* No more than one icon selectable */
   {"CHANGEREFRESH",WI_CHANGEREFRESH},  /* Automatically change refresh type */
};
#define MAXICONFLAG 8


/*
 *  Qualifier key names and flags for ReadKeyDefinition()
 */

static struct Flag KeyFlag[] =
{
   {"LSHIFT",       IEQUALIFIER_LSHIFT},
   {"RSHIFT",       IEQUALIFIER_RSHIFT},
   {"CAPSLOCK",     IEQUALIFIER_CAPSLOCK},
   {"CONTROL",      IEQUALIFIER_CONTROL},
   {"LALT",         IEQUALIFIER_LALT},
   {"RALT",         IEQUALIFIER_RALT},
   {"LCOMMAND",     IEQUALIFIER_LCOMMAND},
   {"RCOMMAND",     IEQUALIFIER_RCOMMAND},
   {"LAMIGA",       IEQUALIFIER_LCOMMAND},
   {"RAMIGA",       IEQUALIFIER_RCOMMAND},
   {"NUMERICPAD",   IEQUALIFIER_NUMERICPAD},
   {"REPEAT",       IEQUALIFIER_REPEAT},
   {"MBUTTON",      IEQUALIFIER_MIDBUTTON},
   {"RBUTTON",      IEQUALIFIER_RBUTTON},
   {"LBUTTON",      IEQUALIFIER_LEFTBUTTON},
};
#define MAXKEYFLAG  15

/*
 *  Key names and numbers for ReadKeyDefinition()
 */

#define KEY(x)      (x|IECODE_UP_PREFIX)
struct Flag KeyName[] =
{
   {"RBUTTONPRESS",  MENUUP},
   {"LBUTTONPRESS",  SELECTUP},
   {"F1",       KEY(0x50)},
   {"F2",       KEY(0x51)},
   {"F3",       KEY(0x52)},
   {"F4",       KEY(0x53)},
   {"F5",       KEY(0x54)},
   {"F6",       KEY(0x55)},
   {"F7",       KEY(0x56)},
   {"F8",       KEY(0x57)},
   {"F9",       KEY(0x58)},
   {"F10",      KEY(0x59)},
   {"ESC",      KEY(0x45)},
   {"ENTER",    KEY(0x43)},
   {"RETURN",   KEY(0x44)},
   {"BACKSPACE",KEY(0x41)},
   {"DELETE",   KEY(0x46)},
   {"HELP",     KEY(0x5F)},
   {"TAB",      KEY(0x42)},
   {"SPACE",    KEY(0x40)}
};
#define MAXKEYNAME  20


/*
 *  Menu names and IDs
 */

struct Flag MenuName[] =
{
   {"OPEN",             MENUID(ICON_MENU,IM_OPEN,NOSUB)},
   {"CLOSE",            MENUID(ICON_MENU,IM_CLOSE,NOSUB)},
   {"LOCK",             MENUID(ICON_MENU,IM_LOCK,NOSUB)},
   {"UNLOCK",           MENUID(ICON_MENU,IM_LOCK,NOSUB)},
   {"CLEAN_UP",         MENUID(ICON_MENU,IM_CLEANUP,NOSUB)},
   {"ORGANIZE",         MENUID(ICON_MENU,IM_ORGANIZE,NOSUB)},
   {"OPEN_ALL",         MENUID(ICON_MENU,IM_OPENALL,NOSUB)},
   {"ABOUT",            MENUID(ICON_MENU,IM_ABOUT,NOSUB)},
   {"END",              MENUID(ICON_MENU,IM_END,NOSUB)},
   {"TO_FRONT",         MENUID(SCREEN_MENU,SM_TOFRONT,NOSUB)},
   {"TO_BACK",          MENUID(SCREEN_MENU,SM_TOBACK,NOSUB)},
   {"WB_TO_FRONT",      MENUID(SCREEN_MENU,SM_WBTOFRONT,NOSUB)},
   {"TOGGLE_TITLE",     MENUID(SCREEN_MENU,SM_TOGGLE,NOSUB)},
   {"ICONIFY",          MENUID(SCREEN_MENU,SM_ICONIFY,NOSUB)},
   {"NEWCLI",           MENUID(SCREEN_MENU,SM_NEWCLI,NOSUB)},
   {"MAKE_WB",          MENUID(SCREEN_MENU,SM_MAKEWB,NOSUB)},
   {"ACTIVE_SCREEN",    MENUID(SCREEN_MENU,SM_OPENWINDOW,OW_ACTIVESCRN)},
   {"CURRENT_WB",       MENUID(SCREEN_MENU,SM_OPENWINDOW,OW_CURRENTWB)},
   {"REAL_WB",          MENUID(SCREEN_MENU,SM_OPENWINDOW,OW_REALWB)},
   {"SIZE_TO_FIT",      MENUID(SCREEN_MENU,SM_OPENWINDOW,OW_AUTORESIZE)},
   {"HIRES",            MENUID(SCREEN_MENU,SM_NEWSCREEN,NS_HIRES)},
   {"LORES",            MENUID(SCREEN_MENU,SM_NEWSCREEN,NS_LORES)},
   {"INTERLACE",        MENUID(SCREEN_MENU,SM_NEWSCREEN,NS_INTERLACE)},
   {"HAM",              MENUID(SCREEN_MENU,SM_NEWSCREEN,NS_HAM)},
   {"CLOSE_SCREEN",     MENUID(SCREEN_MENU,SM_CLOSESCREEN,NOSUB)},
};
#define MAXMENU     25



/*
 *  ReadCommand()
 *
 *  If we're at the end of the line, read the next line.
 *  Get the next word on the line.
 *  If we have readed the end of the file, use COM_EOF
 *  Otherwise, if the character following the next word is not ':'
 *    then we have not found a command, so use COM_NONE,
 *  Otherwise,
 *    Look for the command in the current command table, and skip the ':'
 *  Return the number of the command found.
 */
 
int ReadCommand()
{
   int Command = COM_UNKNOWN;
   short i;

   if (NextChar == '\n' || NextChar == 0) ReadNextLine();
   ReadNextWord();
   if (EndOfFile)       Command = COM_EOF; else
   if (NextChar != ':') Command = COM_NONE;
   else
   {
      for (i=0; i<=ComCount && Command == COM_UNKNOWN; i++)
         if (WORDMATCH(ComName[i])) Command = i + ComOffset;
      SkipChar();
   }
   return(Command);
}


/*
 *  ReadKeyDef()
 *
 *  Parse the next word as a qualifier or key name.
 *  If theKey is NULL, no key name is allowed, if DisQual is NULL, no
 *  disqualifiers are allowed.
 *  
 *  If the next character is '\' and we are accepting key names,
 *    Go back to the start of the word and skip the '\'
 *    If the next char is a HEX digit,
 *      Get the HEX value of the next char and get the following char.
 *      If the following char is a HEX digit,
 *        Shift the old hex digit by 16 (10 HEX), add the next digit,
 *        and get the next character
 *      Skip any blanks.
 *      Mark the code as a key-up event
 *    Otherwise give an error message.
 *    Indicate that this should be the end of the line.
 *  Otherwise (not a '\')
 *    If the word is '+' or '|', ie, a qualifier, then
 *      If there were no qualifiers or disqualifiers given, give a message
 *      Get the next word (the qualifier itself)
 *    Otherwise, if the word is '-', ie, a disqualifier, then
 *      Check to make sure disqualifiers are allowed; error if not.
 *      Get the disqualifier.
 *    Look through the qualifier flag names for a match.
 *      If a match is found,
 *        add the qualifier to the proper list and end the loop
 *    If no qualifier was found,
 *      If we are allowed to have a key name,
 *        Go back to the beginning of the word, and re-read it allowing numbers
 *        Look through the key name list
 *          If a match is found,
 *            Record the key number and end the loop.
 *            Indicate that this should be the end of the line.
 *        If no match found, give an error.
 *      Otherwise (no key name allowed) give an error.
 *  Return the end-of-line status.
 */
 
int ReadKeyDef(theKey,theQual,DisQual)
UBYTE *theKey;
USHORT *theQual,*DisQual;
{
   short i;
   int AddIt = TRUE;
   int EndOfLine = FALSE;
   int OpMissing;
   
   if (Word[0] == '\\' && theKey)
   {
      Reread(); GetNextChar();
      if (HEXCHAR(NextChar))
      {
         *theKey = HEX(NextChar);
         GetNextChar();
         if (HEXCHAR(NextChar))
         {
            *theKey = 16*(*theKey) + HEX(NextChar);
            GetNextChar();
         }
         SkipSpaces();
         *theKey |= IECODE_UP_PREFIX;
      } else Expected("HEX char");
      EndOfLine = TRUE;
   } else {
      if (Word[0] == '+' || Word[0] == '|')
      {
         if (*theQual == 0)
         {
            if (DisQual == NULL) OpMissing = TRUE; else
            if (*DisQual == 0)   OpMissing = TRUE; else OpMissing = FALSE;
            if (OpMissing) ShowError("Missing Qualifier before '%c'",Word[0]);
         }
         ReadNextWord();
      } else if (Word[0] == '-') {
         if (DisQual) AddIt = FALSE; else
            ShowError("Disqualifiers not allowed");
         ReadNextWord();
      }
      for (i=0; i<MAXKEYFLAG; i++)
      {
         if (WORDMATCH(KeyFlag[i].Name))
         {
            if (AddIt) *theQual |= KeyFlag[i].Flag;
               else    *DisQual |= KeyFlag[i].Flag;
            i = MAXKEYFLAG+1;
         }
      }
      if (i == MAXKEYFLAG)
      {
         if (theKey)
         {
            Reread(); ReadExtendedWord();
            for (i=0; i<MAXKEYNAME; i++)
            {
               if (WORDMATCH(KeyName[i].Name))
               {
                  *theKey = KeyName[i].Flag;
                  i = MAXKEYNAME+1;
                  EndOfLine = TRUE;
               }
            }
            if (i == MAXKEYNAME) Expected("a Key-Name or Qualifier");
         } else Expected("a Qualifier");
      }
   }
   return(EndOfLine);
}


/*
 *  ReadColor()
 *
 *  If the color number is a valid one,
 *    Go back to the beginning of the word and read it as a number.
 *    Set it to upper case (for HEX check).
 *    If the word is '=', set the color value to NOCOLOR
 *    If the color is a valid color specification, get the color value
 *    If the color is '*' leave the color alone,
 *    Otherwise, give an error.
 *  Otherwise give an error about how many colors are allowed.
 */
 
void ReadColor(theID)
short theID;
{
   extern UWORD *ScreenColors;

   if (theID < 32)
   {
      Reread(); ReadExtendedWord(); WordToUpper();
      if (WORDMATCH("=")) ScreenColors[theID] = NOCOLOR; else
      if (VALIDCOLOR(Word)) ScreenColors[theID] = COLOR(Word); else
      if (WORDMATCH("*") == FALSE) Expected("an RGB Color");
   } else ShowError("Maximum of 32 colors exceeded");
}


/*
 *  ReadIconFlags()
 *
 *  If the word is '+' or '|', then
 *    If there were no previous flags specified, give an error
 *    The next word is the flag name
 *  Look through the flag names
 *    If one of them matches,
 *      Add the flag to the default flags and end the loop
 *  If no flag found, give an error.
 */
 
void ReadIconFlags(Flags)
long *Flags;
{
    short i;   
    
    if (Word[0] == '|' || Word[0] == '+')
    {
       if (*Flags == 0)
          ShowError("Missing Icon Flag before '%c'",Word[0]);
       ReadNextWord();
    }
    for (i=0; i<MAXICONFLAG; i++)
    {
       if (WORDMATCH(IconFlag[i].Name))
          (*Flags) |= IconFlag[i].Flag,
          i = MAXICONFLAG+1;
    }
    if (i == MAXICONFLAG) Expected("an Icon Flag");
}


/*
 *  ReadMenuKey()
 *
 *  Look through the menu names
 *    If one of them matches,
 *      Get the pointer to the specified item in the menu bar structure;
 *      If there is no command key, clear the COMSEQ flag for the item,
 *      Otherwise,
 *        Get the next word and make it upper case
 *        If it is a control character, report an error;
 *        If it is more than one characterm report an error;
 *        Otherwise, set the command key to the indicated one.
 *      End the loop.
 *  If no match was found, give an error, and skip the rest of the line.
 */
 
void ReadMenuKey()
{
   short i;  
   struct MenuItem *theItem;
   extern struct MenuItem *ItemAddress();
   extern struct Menu *wMenuBar;
   
   for (i=0; i<MAXMENU; i++)
   {
      if (WORDMATCH(MenuName[i].Name))
      {
         theItem = ItemAddress(wMenuBar,MenuName[i].Flag);
         if (NextChar == '\n')
         {
            theItem->Flags &= ~COMMSEQ;
            theItem->Command = 0;
         } else {
            ReadNextWord(); WordToUpper();
            if (Word[0] < ' ') Expected("a Printable Character"); else
            if (Word[1] != 0)  Expected("a Single Character");
            else theItem->Flags |= COMMSEQ, theItem->Command = Word[0];
         }
         i = MAXMENU+1;
      }
   }
   if (i == MAXMENU)
   {
      if (strlen(Word) > 1) ShowError("Unrecognized Menu '%s'",Word);
         else Expected("a Menu Item Name");
      SkipLine();
    }
}


/*
 *  ReadBool()
 *
 *  If the word is "TRUE", return TRUE.
 *  If the word is "FALSE", return FALSE.
 *  Otherwise, give an error.
 */
 
void ReadBool(n)
int *n;
{
   if (WORDMATCH("TRUE"))  *n = TRUE; else
   if (WORDMATCH("FALSE")) *n = FALSE; else
      Expected("TRUE or FALSE");
}


/*
 *  ReadInteger()
 *
 *  Parse the next word as an integer.
 *  If the word is an integer, return it, otherwise give an error.
 */
 
void ReadInteger(i)
int *i;
{
   ReadNextInteger();
   if (sscanf(Word,"%d",i) != 1) Expected("Integer value");
}


/*
 *  ReadString()
 *
 *  Get the whole line as the next word.
 *  If the character string has already been read, free it.
 *  Get enough space for the new string, and copy the value into it.
 *  Of not enough space, give an error.
 */
 
void ReadString(s)
char **s;
{
   ReadFullLine();
   if (*s) FREECHAR(*s);
   if (NEWCHAR(*s,strlen(Word))) strcpy(*s,Word); else
      ShowError("Can't get memory for character string");
}


/*
 *  ReadIgnoreScreen()
 *
 *  Get a new Ignore structure; if allocated OK,
 *    Read the title of the screen.
 *    If a title was found, add the screen to the ignored list,
 *    Otherwise, free the Ignore structure.
 *  Otherwise, give an error and skip the rest of the line.
 */
 
void ReadIgnoreScreen()
{
   struct Ignore *theScreen;

   if (NEWSTRUCT(Ignore,theScreen))
   {
      ReadString(&theScreen->Title);
      if (theScreen->Title)
      {
         theScreen->Next = IgnoreScreen;
         IgnoreScreen = theScreen;
      } else {
         FREESTRUCT(Ignore,theScreen);
      }
   } else {
      ShowError("Can't get memory to Ignore Screen");
      SkipLine();
   }
}


/*
 *  GetPen()
 *
 *  Look for the given HEX digit in the extended HEX list.
 *  If not found,
 *    Give an error message and count the number of errors.
 *    If more than the maximum allowed, skip the rest of the line.
 *  If the image type is a MASK, and the pen is not 0 or 1,
 *    If there were no previous errors of this type, give one.
 *    Mark that the user was warned about mask pen types.
 *    Reduce the pen number to 0 or 1.
 *  Return the pen color.
 */
 
static UBYTE GetPen(c)
char c;
{
   UBYTE Pen = 0;
   short i;

   for (i=0; i<32; i++) if (c == ExtHex[i]) Pen = i, i = 33;
   if (i == 32)
   {
      Expected("HEX Digit"); ErrorCount++;
      if (ErrorCount > MAXERROR)
         printf("Maximum errors exceeded - skipping rest of line\n"),
         SkipLine();
   }
   if (Pen > 1 && (ImageType == COM_DEFAULTMASK || ImageType == COM_SCREENMASK))
   {
      if (NoMaskError) ShowError("Mask pixel values must be 0 or 1");
      NoMaskError = FALSE;
      Pen = Pen & 1;
   }
   return(Pen);
}


/*
 *  ReadImageLine()
 *
 *  If the current line is within the maximum image size,
 *    Clear the error flags, and get back to the beginning of the line.
 *    While we're not at the end of the line,
 *      If the current column is within the maximum image size,
 *        Get the pen number of the current pixel, and increment the column.
 *        Update Plane0 and Plane1 (Plane0 has a zero wherever that plane
 *          has at least one pixel that is zero; Plane1 has a 1 wherever
 *          that plane has at least one pixel that is one).
 *        If the bit mask has wrapped around, go on to the next image word.
 *        For each 1 bit in the Pen color, add a 1 bit into the image data
 *        Shift the image mask to the right.
 *        Get the next pixel's pen color and skip any comments.
 *        Skip trailing blanks (but not interior blanks).
 *      Otherwise, indicate that the width is too large and skip to the
 *        end of the line.
 *    Update the current image size, if necessary.
 *  Otherwise, indicate that the icon is too tall, and skip to the next line.
 */
 
void ReadImageLine(y)
short y;
{
   short x = 0, w = 0;
   short d;
   UWORD mask = BIT(15);
   UBYTE Pen;
   
   if (y < MAXHEIGHT)
   {
      NoMaskError = TRUE; ErrorCount = 0;
      Reread();
      while (NextChar != '\n')
      {
         if (x < MAXWIDTH)
         {
            Pen = GetPen(NextChar); x++;
            Plane0 &= Pen; Plane1 |= Pen; d = 0;
            if (mask == 0) w++, mask = BIT(15);
            while (Pen)
            {
               if (Pen & 1) ImageData[w][y][d] |= mask;
               Pen >>= 1; d++;
            }
            mask >>= 1;
            GetNextChar(); SkipComments();
            if (NextChar == ' ')
            {
               SkipSpaces();
               if (NextChar != '\n') Reread();
            }
         } else {
            ReadFullLine();
            ShowError("Maximum Image Width of %d exceeded",MAXWIDTH);
         }
      }
      if (x   > MaxWidth)  MaxWidth = x, MaxWords = w+1;
      if (y+1 > MaxHeight) MaxHeight = y+1;
      if (MaxWidth > MAXWIDTH) MaxWidth = MAXWIDTH;
   } else {
      ReadFullLine();
      ShowError("Maxmimum Image Height of %d exceeded",MAXHEIGHT);
   }
}


/*
 *  ReadIconFile()
 *
 *  Get the rest of the line (it will be the file name to open)
 *  Save the current file information.
 *  Attempt to open the specified file.
 *  If openned OK, then
 *    Set the icon file flag,
 *    Set the command list to the icon file command list and size.
 *  Otherwise,
 *    Put back the old file information,
 *    Give an error about the icon file.
 */
 
void ReadIconFile(Offset)
int Offset;
{
   ReadFullLine();
   SaveOpenFile();
   if (OpenFile(Word))
   {
      IconFileOpen = TRUE;
      ComName = &IconCommand[0];
      ComOffset = Offset;
      ComCount = MAXICONCOM;
   } else {
      RestoreFile();
      ShowError("Can't open Icon file '%s'",Word);
   }
}


/*
 *  EndIconFile()
 *
 *  Put back the old file information
 *  Mark the file as closed, and restore the command list to the main list.
 */
 
void EndIconFile()
{
   RestoreFile();
   IconFileOpen = FALSE;
   ComName = &MainCommand[0];
   ComOffset = 0;
   ComCount = MAXMAINCOM;
}


/*
 *  ReadOpenOn()
 *
 *  Check if the word is one of "ACTIVE_SCREEN", "CURRENT_WB", or "REAL_WB",
 *  and return the correct type, otherwise give an error.
 */
 
void ReadOpenOn()
{
   if (WORDMATCH("ACTIVE_SCREEN"))  ScreenType = OW_ACTIVESCRN; else
   if (WORDMATCH("CURRENT_WB"))     ScreenType = OW_CURRENTWB; else
   if (WORDMATCH("REAL_WB"))        ScreenType = OW_REALWB; else
      Expected("ACTIVE_SCREEN, CURRENT_WB, or REAL_WB");
}
