/*
 *  CELLS       An Implementation of the WireWorld cellular automata
 *              as described in Scientific American, Jan 1990.
 *
 *              Copyright 1990 by Davide P. Cervone.
 *  You may use this code, provided this copyright notice is kept intact.
 *  See the CELLS.HELP file for complete information on distribution conditions.
 */

/*
 *  File:  cRead.c          Handles reading Ciruit and Library files.
 */


#include "cGadget.h"
#include "cRequest.h"
#include "cParts.h"
#include <stdio.h>

char FileName[MAXFNAME];                /* current circuit's name */

static FILE *theFile;                   /* the file being read */
static int noError,EndOfFile;           /* error flags */
static char Line[256];                  /* input buffer */
static char *NextWord,*Parameters;      /* pointers to parts of commands */
static short LinePos;                   /* current character being read */
static int TooWide;                     /* TRUE if circuit is too wide */
static LIBRARY *LibList;                /* list of libraries to load */

#define MAXERROR        8

#define CELL_UNKNOWN    0xFF

#define COM_UNKNOWN     0
#define COM_CIRCUIT     1
#define COM_LIBRARY     2
#define COM_PART        3
#define COM_CELLSIZE    4
#define COM_ORIGIN      5
#define COM_VIEW        6
#define COM_INCLUDE     7
#define COM_BLANKLINE   8
#define COM_CELLS       9
#define COM_EOF         10

/*
 *  Command character strings
 */

static char *Command[] =
   {"Circuit","Library","Part","CellSize","Origin","View","Include"};


static char LoadMessage[] = "Continuing with Load...";
#define LOADMESSAGE  LoadMessage


/*
 *  ReadError()
 *
 *  Put up the error message and increment the error count
 *  If too many errors occurred, ask if the user wants to quit loading
 *  If not, put up the info message again
 */

void ReadError(s,x1,x2,x3)
char *s,*x1,*x2,*x3;
{
   static int ErrorCount = 0;

   DoError(s,x1,x2,x3);
   ErrorCount++;
   if (ErrorCount == MAXERROR)
   {
      noError = DoQuestion("%d Errors:  Continue Loading?",ErrorCount);
      ErrorCount = 0;
   }
   if (noError) DoInfoMessage(LOADMESSAGE);
}


/*
 *  ReadNextLine()
 *
 *  If no errors have occurred
 *    If we can read another line from the file
 *      set the line position to the start of the line
 *    otherwise
 *      if we're at the end of the file, indicate that
 *      otherwise put up an error message
 *  Otherwise consider ourselves at the end of the file (don't read any more)
 */

static void ReadNextLine()
{
   if (noError)
   {
      if (fgets(Line,256,theFile))
      {
         LinePos = 0;
      } else {
         if (feof(theFile))
         {
            EndOfFile = TRUE;
         } else {
            DosError("Read","Line");
            noError = FALSE;
         }
      }
   } else {
      EndOfFile = TRUE;
   }
}


/*
 *  GetNextWord()
 *
 *  While still looking and not at the end of the file
 *    if the the rest of the line is a comment, skip it
 *    if the next character is a space or tab, go to the next character
 *    otherwise we've found the first letter of the next word
 *  Mark the start of the word
 *  Find the end of the word and mark it as the beginning of the parameters
 *  Find the trailing '\n' and convert it to a NULL
 */

static void GetNextWord()
{
   int NotDone = TRUE;
   char *s;

   while (NotDone && !EndOfFile)
   {
      if (Line[LinePos] == '*' || Line[LinePos] == '!') ReadNextLine();
      else if (Line[LinePos] == ' ' || Line[LinePos] == '\t') LinePos++;
      else NotDone = FALSE;
   }
   NextWord = &Line[LinePos];
   while (Line[LinePos] != ' ' && Line[LinePos] != '\n' && Line[LinePos])
      LinePos++;
   s = Parameters = &Line[LinePos+1];
   while (*s && *s != '\n') s++;
   if (*s == '\n') *s = 0;
}


/*
 *  GetNextCommand()
 *
 *  Get the next word and its parameters
 *  If we're not at the end of the file
 *    check if the first character of the command is a state symbol; if not
 *      if we actually have some characters
 *        temporarily mark the end of the word with a NULL
 *        and look through the command list for a match
 *        then put back the original separating character
 *        (if no match is made, i will be decremented to COM_UNKNOWN)
 */

static int GetNextCommand()
{
   short i = COM_EOF;
   int NotDone = TRUE;
   char c;

   GetNextWord();
   if (!EndOfFile)
   {
      i--; c = *NextWord;
      if (c != '[' && c != '=' && c != '#' && c != '.')
      {
         i--;
         if (*NextWord && *NextWord != '\n')
         {
            c = Line[LinePos]; Line[LinePos] = 0;
            while (i>0 && NotDone)
               if (stricmp(NextWord,Command[i-1]) == 0)
                   NotDone = FALSE; else i--;
            Line[LinePos] = c;
         }
      }
   }
   return(i);
}


/*
 *  AddLineToGrid()
 *
 *  Start at the beginning of the line
 *  If the current Y position is past the bottom of the grid, give an error
 *  otherwise
 *    while there is more to do on the line
 *      find the cell type of the next character on the line:
 *        if we're at the end of the line, we're done
 *        '[]' = WIRE  '##' = HEAD  '==' = TAIL  '. ' and '  ' = BLANK
 *      If we're not at the end of the line
 *        if the cell is not identified, give a message
 *        otherwise, id the cell is within the array, set the cell state
 *        move on the the next position in the array
 *    If the circuit is too wide for the array, give a warning
 *  return the new Y position
 */

static short AddLineToGrid(Grid,Y,Ox,Oy,blank)
UBYTE Grid[];
short Ox,Oy;
short Y;
int blank;
{
   short X = Ox;
   short i = X + (Y + Oy) * MAXGRIDW;
   int NotDone = TRUE;
   UBYTE CellType;

   LinePos = 0;
   if (Y + Oy >= MAXGRIDH)
   {
      if (Y + Oy == MAXGRIDH)
         ReadError("Circuit Exceeds Maximum Height of %d",MAXGRIDH);
   } else {
      while (NotDone)
      {
         CellType = CELL_UNKNOWN;
         switch(Line[LinePos++])
         {
            case '\n':
            case '\0':
               NotDone = FALSE;
               break;

            case '[':
               if (Line[LinePos++] == ']') CellType = WIRE;
               break;

            case '#':
               if (Line[LinePos++] == '#') CellType = HEAD;
               break;

            case '=':
               if (Line[LinePos++] == '=') CellType = TAIL;
               break;

            case ' ':
               if (Line[LinePos++] == ' ') CellType = blank;
               break;

            case '.':
               if (Line[LinePos++] == ' ') CellType = BLANK;

            default:
               LinePos++;
               break;
         }
         if (NotDone)
         {
            if (CellType == CELL_UNKNOWN)
               ReadError("Unknown Cell Symbol '%c%c'",
                  Line[LinePos-2],Line[LinePos-1]);
              else if (i < ARRAYSIZE)
               Grid[i] = CellType;
            i++; X++;
         }
      }
      if (X >= MAXGRIDW && TooWide == FALSE)
      {
         ReadError("Circuit Exceeds Maximum Width of %d",MAXGRIDW);
         TooWide = TRUE;
      }
   }
   return((short)(Y+1));
}


/*
 *  LoadLibrary()
 *
 *  If the library is being loaded as the Parts List
 *    put up the proper message and use the proper library strucutre
 *  Otherwise put up the standard load library message
 *  Clear the flags
 *  While there is more to the library
 *    Read the next line from the file and the next command
 *    If the command is other than another row of cells or a blank line
 *      Finish any part in progress and clear the part flags
 *    Do the correct command:
 *
 *      LIBRARY:
 *        give an error
 *
 *      PART:
 *        if the part has a name
 *          clear the scratch grid and set the flags for starting a part
 *          copy the name of the part into the part name string
 *        otherwise warn the user that the part needs a name
 *
 *      CIRCUIT, CELLSIZE, ORIGIN, VIEW, INCLUDE:
 *        these are legal only in Circuit files, so give an error
 *
 *      CELLS:
 *        if a part is being made, add the line to the parts' grid
 *        otherwise, if we haven't already warned the user, do so
 *
 *      BLANKLINE:
 *        increment the position in the grid
 *
 *      UNKNOWN
 *        warn the user about the bad command in the file
 */

static void LoadLibraryParts(theLibrary)
LIBRARY *theLibrary;
{
   int Command;
   short Y = 0;
   UBYTE *Grid = NULL;
   char PartName[MAXPNAME];
   int MakingPart;
   int NoMessage = TRUE;

   if (theLibrary->Flags & PL_ASPARTS)
   {
      DoInfoMessage("Loading Library '%s' as Parts",theLibrary->Name);
      theLibrary = &PartsList;
   } else {
      DoInfoMessage("Loading Library '%s'",theLibrary->Name);
   }
   MakingPart = FALSE; TooWide = FALSE;
   while (!EndOfFile)
   {
      ReadNextLine(); Command = GetNextCommand();
      if (Command != COM_CELLS && Command != COM_BLANKLINE)
      {
         if (MakingPart) FinishPart(PartName,theLibrary);
         if (Grid && Y) Grid = NULL; Y = 0;
         MakingPart = 0;
      }
      switch(Command)
      {
         case COM_LIBRARY:
            ReadError("LIBRARY Command must be First in the File");
            break;

         case COM_PART:
            if (Parameters[0])
            {
               ZeroGrid(NewGen); Y = 0; Grid = NewGen;
               NoMessage = TRUE; MakingPart = TRUE; TooWide = FALSE; 
               strcpy(PartName,Parameters);
            } else {
               ReadError("Missing Name in PART command");
            }
            break;
               
         case COM_CIRCUIT:
         case COM_CELLSIZE:
         case COM_ORIGIN:
         case COM_VIEW:
         case COM_INCLUDE:
            ReadError("Command '%s' Invalid in LIBRARY",NextWord);
            break;

         case COM_CELLS:
            if (Grid)
            {
               Y = AddLineToGrid(Grid,Y,0,0,0);
            } else if (NoMessage) {
               ReadError("Missing PART Command?");
               NoMessage = FALSE;
            }
            break;

         case COM_BLANKLINE:
            if (Y) Y++;
            break;

         case COM_UNKNOWN:
            *(Parameters-1) = 0;
            ReadError("Unknown Command '%s'",NextWord);
            break;
      }
   }
}


/*
 *  IncludeLibrary()
 *
 *  Get a new Library structure, if possible, and link it into the
 *  list of libraries to load
 */

static void IncludeLibrary(Name)
char *Name;
{
   LIBRARY *theLibrary;
   extern LIBRARY *NewLibrary();
   
   theLibrary = NewLibrary(Name);
   if (theLibrary)
   {
      theLibrary->Next = LibList;
      LibList = theLibrary;
   }
}


/*
 *  LoadLibList()
 *
 *  For each library in the list
 *    Find the location of the library (sorted) into the library list
 *    If it does not already appear
 *      if we can't open the specified library
 *        inform the user of the problem
 *        free the library structure
 *      otherwise
 *        clear the error flags and read the first line of the library
 *        find the first command in the library
 *        if the first command is a LIBRARY command
 *          load the library's parts and close the file
 *          warn the user if any errors occur
 *        otherwise
 *          close the file and warn the user it is not a library
 *        If the library has parts in it
 *          link the library into the list
 *        otherwise
 *          free the library structure
 *    otherwise (already loaded) warn the user about it and free the duplicate
 *  restore the error status
 */

static void LoadLibList()
{
   LIBRARY *theLibrary;
   LIBRARY *PrevLib;
   LIBRARY **LibPtr;
   int OldNoError = noError;
   int Command;
   char ErrorName[MAXFNAME];
   
   while (theLibrary = LibList)
   {
      LibList = LibList->Next;
      FindListPos(theLibrary->Name,&(FirstLibrary),&PrevLib,&LibPtr);
      if (LibPtr)
      {
         if ((theFile = fopen(theLibrary->Name,"r")) == NULL)
         {
            sprintf(ErrorName,"Library '%s'",theLibrary->Name);
            DosError("Open",ErrorName);
            DoInfoMessage(LOADMESSAGE);
            FreeLibrary(theLibrary);
         } else {
            noError = TRUE; EndOfFile = FALSE; ReadNextLine();
            while ((Command = GetNextCommand()) == COM_BLANKLINE)
               ReadNextLine();
            if (Command == COM_LIBRARY)
            {
               LoadLibraryParts(theLibrary);
               fclose(theFile);
               if (noError == FALSE)
                 ReadError("Library '%s' Not Completely Read",theLibrary->Name);
            } else {
               fclose(theFile);
               ReadError("File '%s' is not a Library",theLibrary->Name);
            }
            if (theLibrary->FirstPart)
            {
               theLibrary->Prev = PrevLib;
               theLibrary->Next = *LibPtr;
               if (theLibrary->Next) theLibrary->Next->Prev = theLibrary;
               *LibPtr = theLibrary;
               Changed = TRUE;
            } else {
               FreeLibrary(theLibrary);
            }
         }
      } else {
         ReadError("Library '%s' Already Loaded",theLibrary->Name);
         FreeLibrary(theLibrary);
      }
   }
   noError = OldNoError;
}


/*
 *  LoadCircuit()
 *
 *  Clear the flags
 *  While there is more data in the file
 *    read the next line and get the command on that line
 *    if the command is not more cells or blank lines
 *      fininsh making a part, if one is being read
 *      clear the part flags
 *    Do the proper command:
 *
 *      LIBRARY, CIRCUIT:
 *        warn the user about bad LIBRARY and CIRCUIT commands
 *
 *      PART:
 *        if the part has a name
 *          clear the scratch grid and set the flags for making a part
 *          copy the part name into the name string
 *        otherwise warn about the missing name
 *
 *      CELLSIZE:
 *        get the specified cell size and check it for errors
 *
 *      ORIGIN:
 *        get the position of the origin and check for errors
 *
 *      VIEW:
 *        get the position of the visible grid area and check for errors
 *
 *      INCLUDE:
 *        include the specified library in the libraries to be loaded
 *
 *      CELLS:
 *        if there is a part in progress, add the line to it,
 *        otherwise warn about the missing command
 *
 *      BLANK LINE:
 *        move on to the next row in the grid
 *
 *      UNKNOWN:
 *        warn the user about bad commands
 *  close the circuit file
 *  load any libraries that were indicated in the file
 *  if there were errors in the circuit file, let the user know
 *  clear the info message
 */

static void LoadCircuit(Name)
char *Name;
{
   int Command;
   short Y = 0;
   UBYTE *Grid = CurGen;
   char PartName[MAXPNAME];
   int tmp1,tmp2;
   short Ox = 0, Oy = 0;
   int blank = BLANK;
   int NoMessage = TRUE;
   int MakingPart;
   
   MakingPart = FALSE; TooWide = FALSE;
   GridX = GridY = 0;
   while (!EndOfFile)
   {
      ReadNextLine();
      Command = GetNextCommand();
      if (Command != COM_CELLS && Command != COM_BLANKLINE)
      {
         if (MakingPart) FinishPart(PartName,&PartsList);
         if (Grid && Y) Grid = NULL; Y = 0;
         MakingPart = 0;
      }
      switch(Command)
      {
         case COM_LIBRARY:
            ReadError("Command 'LIBRARY' Invalid in CIRCUIT");
            break;

         case COM_CIRCUIT:
            ReadError("CIRCUIT Command must be First in the File");
            break;

         case COM_PART:
            if (Parameters[0])
            {
               ZeroGrid(NewGen);
               Ox = 0; Oy = 0; blank = 0; NoMessage = TRUE;
               Grid = NewGen; Y = 0; MakingPart = TRUE;
               strcpy(PartName,Parameters);
            } else {
               ReadError("Missing Name in PART command");
            }
            break;
               
         case COM_CELLSIZE:
            if (sscanf(Parameters,"%ld",&tmp1) != 1)
               ReadError("Invalid CELLSIZE Value");
            else if (tmp1 < MINCELLSIZE || tmp1 > MAXCELLSIZE)
               ReadError("Invalid CELLSIZE:  %d",tmp1);
            else CellSize = tmp1;
            break;

         case COM_ORIGIN:
            if (sscanf(Parameters,"%d %d",&tmp1,&tmp2) != 2)
            {
               ReadError("Invalid ORIGIN Values");
            } else if (tmp1 < 0 || tmp1 >= MAXGRIDW ||
                       tmp2 < 0 || tmp2 >= MAXGRIDH) {
               ReadError("ORIGIN Value Out of Range: (%d,%d)",tmp1,tmp2);
            } else {
               Ox = tmp1; Oy = tmp2;
            }
            break;

         case COM_VIEW:
            if (sscanf(Parameters,"%d %d",&tmp1,&tmp2) != 2)
            {
               ReadError("Invalid VIEW Values");
            } else if (tmp1 < 0 || tmp2 >= MAXGRIDW ||
                       tmp2 < 0 || tmp2 >= MAXGRIDH) {
               ReadError("VIEW Value Out of Range: (%d,%d)",tmp1,tmp2);
            } else {
               GridX = tmp1; GridY = tmp2;
            }
            break;

         case COM_INCLUDE:
            IncludeLibrary(Parameters);
            break;

         case COM_CELLS:
            if (Grid)
            {
               Y = AddLineToGrid(Grid,Y,Ox,Oy,blank);
            } else if (NoMessage) {
               ReadError("Missing PART Command?");
               NoMessage = FALSE;
            }
            break;

         case COM_BLANKLINE:
            if (Y) Y++;
            break;

         case COM_UNKNOWN:
            *(Parameters-1) = 0;
            ReadError("Unknown Command '%s'",NextWord);
            break;
      }
   }
   fclose(theFile);
   LoadLibList();
   if (noError == FALSE) DoError("Circuit '%s' Not Completely Read",Name);
   ClearInfoMessage();
}


/*
 *  ReadCircuit()
 *
 *  If the previous circuit has been changed, ask if the user wants
 *    to save the changes
 *
 *  If not (or no changes were made)
 *   put up the message saying that a file is being read
 *   clear the grid and the library lists
 *   load the new circuit
 *   copy the circuit's grid to the UNDO array and the RESET array
 *   set the cell size to the new cell size and move to the specified 
 *     viewing position
 *   clear and refresh the screen and set the sliders
 */

static void ReadCircuit(Name)
char *Name;
{
   short tmp1,tmp2;

   if (Changed) noError = 
      DoQuestion(" Changes have not been Saved. Discard Changes?      ");

   if (noError)
   {
      DoInfoMessage("Loading Circuit '%s'",Name);
      ClearGrid(CurGen);
      ClearLibs(); LibList = NULL;
      LoadCircuit(Name);
      CopyGrid(CurGen,UndoArray);
      CopyGrid(CurGen,ResetArray);
      tmp1 = GridX; tmp2 = GridY; GridW = GridH = 0;
      SetBoardSize(CellSize);
      GridX = tmp1; GridY = tmp2;
      SetBoardSize(CellSize);
      ClearScreen(FALSE);
      RefreshScreen(CurGen);
      UpdateSliders();
   }
}


/*
 *  ReadLibrary()
 *
 *  close the current file
 *  if loading a library as parts and changes have been made,
 *    ask if the user wants to save the changes
 *  if not
 *    if loading as parts clear the library list
 *    set the list of libraries to read to the single specified library
 *    if loading as parts, indicate this in the library
 *    load the list
 *    clear the info message
 */

static void ReadLibrary(Name,Check)
char *Name;
int Check;
{
   int OKtoLoad = TRUE;

   fclose(theFile);
   if (Check && Changed) OKtoLoad = 
      DoQuestion(" Changes have not been Saved. Discard Changes?     ");
   if (OKtoLoad)
   {
      if (Check) ClearLibs();
      LibList = NULL;
      IncludeLibrary(Name);
      if (LibList && Check) LibList->Flags |= PL_ASPARTS;
      LoadLibList();
      ClearInfoMessage();
   }
}


/*
 *  ReadFile()
 *
 *  If we can open the specified file
 *    clear the error flags and read the first line
 *    get the first command in the file
 *    if the command is CIRCUIT:
 *      read the circuit
 *      if it read in OK, set the circuit's name and mark it as unchanged
 *    If the command is LIBRARY:
 *      read the library
 *      if no error and loading as parts, clear the circuit name
 *    If any other command:
 *      indicate that the file must have a CIRCUIT or LIBRARY command
 *      close the file
 *  otherwise indicate the problem with openning the file
 *  clear the info message and put the LOAD button back to normal
 */

void ReadFile(Name,Check)
char *Name;
int Check;
{
   int Command;

   if (theFile = fopen(Name,"r"))
   {
      noError = TRUE; EndOfFile = FALSE; ReadNextLine();
      while ((Command = GetNextCommand()) == COM_BLANKLINE) ReadNextLine();
      switch(Command)
      {
         case COM_CIRCUIT:
            ReadCircuit(Name);
            if (noError)
            {
               strcpy(FileName,Name);
               Changed = FALSE;
            }
            break;

         case COM_LIBRARY:
            ReadLibrary(Name,Check);
            if (Check && noError) FileName[0] = 0;
            break;

         default:
            DoError("File must begin with a LIBRARY or CIRCUIT command");
            fclose(theFile);
            break;
      }
   } else {
      DosError("Read","File");
   }
   ClearInfoMessage();
   InvertGadget(&cGadget[ID_LOAD]);
}
