/*
 *  WICONSETTER     A companion utility to wIconify.  wIconSetter allows
 *                  you to specify custom icons for windows ans screens
 *                  that normally use the default icons.
 *
 *  wIconFileIO.c   Handles low-level file activity.
 *
 *  Copyright 1990 by Davide P. Cervone, all rights reserved.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include <exec/types.h>
#include <stdio.h>
#include "wIconFile.h"

FILE *InFile;                   /* Main file pointer */
int EndOfFile;                  /* TRUE if at end-of-file */

static int LineCount;           /* Number of lines read */
static char Line[MAXLINE+2];    /* Line buffer */
static short LinePos;           /* current position in line */
static short WrdStart,WrdEnd;   /* Where the current word starts and ends */

char NextChar;                  /* the next character of the file */
char Word[MAXLINE+1];           /* the word buffer */

static FILE *OldFile;           /* Saved file for DEFAULT_ICON command */
static int OldLineCount;        /* Saved line count for old file */


#define INVERSE     "\033[32;41m"
#define NORMAL      "\033[m"
#define COLORED     "\033[32;40m"


/*
 *  ShowError()
 *
 *  If the word starts and ends at the same character, move ahead so something
 *    will be displayed.
 *  Get the first character of the word, and end the line there.
 *  Set the text color and print the first part of the line.
 *  Put back the first character of the word, and save the last one.
 *  Print the word in inverted lettering and go back to colored letters.
 *  Put back the last letter of the word and print the rest of the line.
 *  Go back to normal lettering and print the error message with a line number.
 *  If the icon file is open, indicate that.
 */

void ShowError(s,x1,x2,x3)
char *s,*x1,*x2,*x3;
{
   char c;
   
   if (WrdStart == WrdEnd) WrdEnd++;
   c = Line[WrdStart]; Line[WrdStart] = 0;
   printf(COLORED);
   printf("%s",Line);
   Line[WrdStart] = c; c = Line[WrdEnd]; Line[WrdEnd] = 0;
   printf(INVERSE); 
   if (EndOfFile || Line[WrdStart] < ' ') printf(" ");
      else printf("%s",&Line[WrdStart]);
   printf(COLORED);
   Line[WrdEnd] = c; if (c) printf("%s",&Line[WrdEnd]); else printf("\n");
   printf(NORMAL);
   printf(s,x1,x2,x3);
   printf(" at line %d",LineCount);
   if (OldFile) printf(" of Icon File");
   printf("\n");
}


/*
 *  Expected()
 *
 *  If we are at the end of file, indicate this.
 *  If the word is a single character (or no characters)
 *    If the character is printable, use it,
 *    Otherwise, convert it to a word it control character notation.
 *  Print the message saying what was seen and what was expected.
 */

void Expected(s)
char *s;
{
   char *Seen = &Word[0];
   char c = Line[WrdStart];

   if (EndOfFile) Seen = "End-of-File"; else
   if (WrdEnd < WrdStart+2)
   {
      if ((c >= ' ' && c < 0x7F) || c > 0xA0) Seen = " ", Seen[0] = c;
      else
      {
         switch(c)
         {
            case '\n':
               Seen = "End-of-Line";
               break;

            case ' ':
               Seen = "Space";
               break;

            case '\t':
               Seen = "TAB";
               break;

            case '\033':
               Seen = "ESC";
               break;

            case '\0':
               Seen = "NULL";
               break;

            case 0x7F:
               Seen = "DEL";
               break;

            default:
               Seen = "^ ";
               Seen[1] = (c & 0x1F) + '@';
               break;
         }
      }
   }
   ShowError("'%s' seen where %s was expected",Seen,s);
}


/*
 *  GetNextLine()
 *
 *  If we can read a line from the file,
 *    Mark the end of line (in case the maximum of the buffer was used)
 *    Clear the position markers, and count the line.
 *    Get the next character on the line.
 *  Otherwise,
 *    Clear the line and set the positions to the end of the line.
 *    Mark the end of file (any error is treated as EOF).
 */

static void GetNextLine()
{
   if (fgets(Line,MAXLINE,InFile))
   {
      Line[MAXLINE] = '\n';
      Line[MAXLINE+1] = 0;
      LinePos = WrdStart = WrdEnd = 0;
      LineCount++;
      NextChar = Line[LinePos];
   } else {
      NextChar = '\n'; LinePos = WrdStart = WrdEnd = MAXLINE;
      EndOfFile = TRUE;
   }
}


/*
 *  GetNextChar()
 *
 *  Get the next character on the line and increment the position.
 *  Set the word positions to the current position.
 */

void GetNextChar()
{
   NextChar = Line[++LinePos];
   WrdStart = WrdEnd = LinePos;
}


/*
 *  SkipComments()
 *
 *  While we are at the beginning of a comment (indicated by '/*')
 *    Move past the comment characters and increment the comment counter
 *    Get the next character.
 *    While we are still within a coment,
 *      and until we reach a comment terminator or end-of-file,
 *        If we are at the end of a line, read the next line,
 *        If we are at the beginning of a nested comment, 
 *          Increment the nexted count and move past the comment characters.
 *        Otherwise, go on to the next character.
 *      If we are at the end of the file, show an error (unbalanced comments).
 *      Otherwise, skip the end-comment characters.
 *      Decrement the comment nesting count.
 *  If the next character is the rest-of-line comment character,
 *    or if we are at the end of the file, move to the end of the line.
 */

void SkipComments()
{
   int Nested = 0;

   while (NextChar == '/' && Line[LinePos+1] == '*')
   {
      LinePos += 2; Nested++;
      NextChar = Line[LinePos++];
      while (Nested)
      {
         while ((NextChar != '*' || Line[LinePos] != '/') && !EndOfFile)
         {
            if (NextChar == '\n' || NextChar == '\0') GetNextLine(); else
            if (NextChar == '/' && Line[LinePos] == '*')
               Nested++, NextChar = Line[++LinePos];
            else NextChar = Line[LinePos++];
         }
         if (EndOfFile)
         {
            WrdStart = WrdEnd = MAXLINE;
            Expected("End-of-Comment");
         } else NextChar = Line[++LinePos];
         Nested--;
      }
   }
   if (EndOfFile || NextChar == ';') NextChar = Line[LinePos=MAXLINE];
}


/*
 *  SkipSpaces()
 *
 *  First skip any comments.
 *  While the next character is white-space,
 *    Skip characters until it is not white space,
 *    Then skip comments and do it again.
 */

void SkipSpaces()
{
   SkipComments();
   while (NextChar == ' ' || NextChar == '\t')
   {
      do NextChar = Line[++LinePos];
         while (NextChar == ' ' || NextChar == '\t');
      SkipComments();
   }
}


/*
 *  ReadNextLine()
 *
 *  While there is more in the file, and the current line is blank
 *    Get the next line of the file and skip any comments.
 *    Do this until there is actually some real data to look at.
 *  Set the word positions to the current location.
 */

void ReadNextLine()
{
   while (!EndOfFile && (NextChar == 0 || NextChar == '\n'))
   {
      GetNextLine();
      SkipSpaces();
   }
   WrdStart = WrdEnd = LinePos;
}


/*
 *  SkipChar()
 *
 *  If we are at the end of the line, read the next one,
 *  Otherwise, move to the next non-blank character.
 */

void SkipChar()
{
   if (NextChar == '\n') ReadNextLine();
   else NextChar = Line[++LinePos], SkipSpaces();
}


/*
 *  SkipLine()
 *
 *  No matter where we are on the current line, go on to the next line
 *  and skip spaces.  Mark the current word positions.
 */

void SkipLine()
{
   GetNextLine(); SkipSpaces();
   WrdStart = WrdEnd = LinePos;
}


/*
 *  ReadNextChar()
 *
 *  Get the next character and mark the beginning of a word.
 *  Skip any spaces after the current character.
 */

void ReadNextChar()
{
   NextChar = Line[++LinePos];
   WrdStart = WrdEnd = LinePos;
   SkipSpaces();
}


/*
 *  ReadAWord()
 *
 *  Begin the word at the current location.
 *  While the next character is a letter (or a number if they are allowed)
 *    Add it to the word buffer and go to the next character
 *  If the word has at least one character, mark the end of the word,
 *  Otherwise, the word is a single special character.
 *  Mark the end of the word in the line buffer and skip trailing spaces.
 */

void ReadAWord(Numbers)
int Numbers;
{
   short i=0;
   
   WrdStart = LinePos;
   while ((NextChar >= 'A' && NextChar <= 'Z') ||
          (NextChar >= 'a' && NextChar <= 'z') ||
          (NextChar >= '0' && NextChar <= '9' && Numbers) ||
           NextChar == '_')
   {
      Word[i++] = NextChar;
      NextChar = Line[++LinePos];
   }
   if (i)
   {
      Word[i] = 0;
   } else {
      Word[0] = NextChar; Word[1] = 0;
      NextChar = Line[++LinePos];
   }
   WrdEnd = LinePos;
   SkipSpaces();
}


/*
 *  ReadNextWord()
 *
 *  Read a word without allowing numbers as part of the word.
 */

void ReadNextWord()
{
   ReadAWord(FALSE);
}


/*
 *  ReadExtendedWord()
 *
 *  Read a word and allow numbers as part of the word.
 */

void ReadExtendedWord()
{
   ReadAWord(TRUE);
}


/*
 *  ReadNextInteger()
 *
 *  Get the first digit of the number.
 *  If it is a minus sign, add it to the number and move on.
 *  While the next character is a digit, add it to the number and move on.
 *  Mark the end of the word, and skip blanks.
 */

void ReadNextInteger()
{
   short i=0;
   
   NextChar = Line[LinePos=WrdStart];
   if (NextChar == '-')
   {
      Word[i++] = NextChar;
      NextChar = Line[++LinePos];
   }
   while (NextChar >= '0' && NextChar <= '9')
   {
      Word[i++] = NextChar;
      NextChar = Line[++LinePos];
   }
   Word[i] = 0;
   WrdEnd = LinePos;
   SkipSpaces();
}


/*
 *  ReadFullLine()
 *
 *  Get the first letter of the word.
 *  If the first character is a quote, flag it and move past it.
 *  While we are not at the end of the line (or at an end quote)
 *    Add the character into the word buffer and move on.
 *    If we are looking for a close quote
 *      and the next character is a quoted quote,
 *        add a single quote into the buffer and skip over the quotes.
 *  Mark the end of the word (ie, line).
 *  If we were looking for quotes, then
 *     If we didn't find a closing quote, show an error, otherwise skip
 *       over the close-quote.
 *     Skip blanks after the string.
 *  Otherwise, strip off trailing blanks from the string.
 */

void ReadFullLine()
{
   short i = 0;
   int Quoted = FALSE;

   NextChar = Line[LinePos=WrdStart];
   if (NextChar == '\'')
   {
      Quoted = TRUE;
      NextChar = Line[WrdStart=++LinePos];
   }
   while (NextChar != '\n' && (NextChar != '\'' || Quoted == FALSE))
   {
      Word[i++] = NextChar;
      NextChar = Line[++LinePos];
      if (Quoted)
      {
         if (NextChar == '\'' && Line[LinePos+1] == '\'')
         {
             Word[i++] = NextChar;
             LinePos += 2;
             NextChar = Line[LinePos];
         }
      }
   }
   WrdEnd = LinePos;
   Word[i--] = 0;
   if (Quoted)
   {
      if (NextChar == '\n') ShowError("Quoted string not terminated");
         else NextChar = Line[++LinePos];
      SkipSpaces();
   } else {
      while (i >= 0 && Word[i] == ' ') Word[i--] = 0;
   }
}


/*
 *  Reread()
 *
 *  Go back to be beginning of the word and start reading again.
 */

void Reread()
{
   LinePos = WrdStart;
   NextChar = Line[LinePos];
}


/*
 *  WordToUpper()
 *
 *  Look through the word buffer and convert lower case letters to uppper
 *  case ones.
 */

void WordToUpper()
{
   char *s = &Word[0];
   
   while (*s)
   {
      if (*s >= 'a' && *s <= 'z') *s = *s - 'a' + 'A';
      s++;
   }
}


/*
 *  OpenFile()
 *
 *  Attempt to open the specified file.
 *  If successful,
 *    Set the error status and line counters and read the first line
 *  Return TRUE if file openned OK.
 */

int OpenFile(Name)
char *Name;
{
   int status = FALSE;
   
   InFile = fopen(Name,"r");
   if (InFile)
   {
      EndOfFile = FALSE; LineCount = 0;
      ReadNextLine();
      status = TRUE;
   }
   return(status);
}


/*
 *  CloseFile()
 *
 *  Close the file and clear the file pointer.
 */

void CloseFile(theFile)
FILE *theFile;
{
   fclose(theFile);
   if (theFile == InFile) InFile = NULL;
}


/*
 *  SaveOpenFile()
 *
 *  Save the old file pointer and old line count.
 *  Clear the file pointer for future use.
 */

void SaveOpenFile()
{
   OldFile = InFile;
   OldLineCount = LineCount;
   InFile = NULL;
}


/*
 *  RestoreFile()
 *
 *  If there was an old file open,
 *    Get back the old pointer and line count,
 *    Reset the status, and move to the end of the line.
 *    Clear the save pointer for future use.
 */

void RestoreFile()
{
   if (OldFile)
   {
      InFile = OldFile;
      LineCount = OldLineCount;
      EndOfFile = FALSE;
      NextChar = Line[LinePos=MAXLINE];
      OldFile = NULL;
   }
}
