/*
 *  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:  cDoFile.c        Handles the file requester
 */


#include "cGadget.h"
#include "cRequest.h"
#include "cReqLS.h"
#include "cDos.h"
#include "cParts.h"

extern char FileName[];         /* The current circuit file name */
extern char LSNameBuf[];        /* The name gadget buffer area */
extern int RequestID;           /* The type of file requester in use */
extern FILEINFO FirstFile;      /* The list of names in the current directory */

extern FILEINFO FindFile();

char DirName[MAXFNAME];         /* The current directory name */

#define LSGADGET    LSRequest.er_Request.ReqGadget


/*
 *  NewDirectory()
 *
 *  If the directory name is not empty, then get a lock on the current
 *  directory; if successful, change directories to that directory, otherwise
 *  set the lookup flag (change the name of the current directory instead
 *  of changing the current directory to the named one).
 *
 *  If we are looking up the name (e.g., an error occured or DirName only
 *  contains a relative path), the get the complete path name.  Set the 
 *  gadgets in the requester to reflect the new name and contents.
 *
 *  If no directory name is present, look up the device list instead.
 *
 *  Since directory look-ups can take long, record the mounted drives so
 *  that disk insert events will not cause an update before the user has 
 *  had a chance to look at the current directory.
 */

void NewDirectory(Flag)
int Flag;
{
   LOCK lock;
   int Last = strlen(DirName) - 1;
   
   if (Last >= 0)
   {
      if (DosLock(&lock,DirName,TYPE_DIR))
         UnLock(CurrentDir(lock)); else Flag = LOOKUPDIR;
      if (Flag == LOOKUPDIR) GetProcessDir(DirName);
      SetLSDirectory(DirName);
   } else {
      SetLSDevices();
   }
   Mounted = MountedDrives();
}


/*
 *  AttemptOpen()
 *
 *  If a file name was given, then
 *    If the file can not be locked for reading, then
 *      If the operation is SAVE and the file does not exist, then were done,
 *      Otherwise, report the failure to open the file;
 *    Otherwise, (the file exists and can be read)
 *      Get the FIB of the file.
 *      If it is not a directory, then
 *        If the operation is SAVE warn about re-writing,
 *        Otherwise we've found the file to open.
 *      Otherwise, (it IS a directory)
 *        re-activate the name gadget (bug in Intuition makes this fail)
 *        CD to the selected directory and get its name
 *        look up the directory contents, etc.
 *    Unlock the lock.
 */

static int AttemptOpen(Name,ID)
char *Name;
int ID;
{
   FIB theFib;
   LOCK lock;
   int status = FALSE;

   if (Name && Name[0])
   {
      if ((lock = Lock(Name,ACCESS_READ)) == 0)
      {
         if (RequestID == LS_SAVE && IoErr() == ERROR_OBJECT_NOT_FOUND)
            status = TRUE;
           else
            DosError("Open","File");
      } else {
         if (DosExamine(lock,&theFib,TYPE_FILE))
         {
            if (theFib->fib_DirEntryType < 0)
            {
               if (RequestID == LS_SAVE && ID != LS_REWRITE)
                  SetReWrite(); else status = TRUE;
            } else {
               ActivateLSName();
               lock = CurrentDir(lock);
               GetProcessDir(DirName);
               NewDirectory(NOLOOKUP);
            }
         }
         UnLock(lock);
      }
   } else {
      LSMessage("Can't Open File: No File Specified");
   }
   return(status);
}


/*
 *  DoOpen()
 *
 *  Try to open the file specified in the name gadget.
 *  If successful,
 *    If the operation will save a new file to the current directory, 
 *      disable the directory list (so that it will be looked up fresh 
 *      the next time - we don't actually know what directory the file
 *      will go into, since the name could include a path)
 *    Remove the requester and clean up
 *    Read or write the curcuit as necessary (include PARTS gadget status)
 */

static void DoOpen(theGadget)
struct Gadget *theGadget;
{
   if (AttemptOpen(LSNameBuf,theGadget->GadgetID))
   {
      if (RequestID == LS_SAVE && theGadget->GadgetID != LS_REWRITE)
          DisableLSList(&LSGADGET[LS_NLIST]);
      RemoveRequest(&LSRequest);
      SetPointerColor(PenInUse);
      if (RequestID == LS_LOAD)
         ReadFile(LSNameBuf,LSGADGET[LS_CHECK].Flags & SELECTED);
        else
         WriteFile(LSNameBuf,LSGADGET[LS_CHECK].Flags & SELECTED);
   }
}


/*
 *  DoDirUpPressed()
 *
 *  If SHIFT is held down (meaning go to the root directory)
 *    get the directory name if necessary
 *    find the ':' in the name
 *    If found and it is not the last character in the name,
 *      remove the rest of the name and look up the new directory
 *  Otherwiese (no SHIFT)
 *    If there is a directory named, get its length
 *      look backward for the first '/' or ':'
 *      truncate the directory name and look up the new directory
 */

static void DoDirUpPressed(Shifted)
int Shifted;
{
   int len;
   short i;

   if (Shifted)
   {
      if ((len = strlen(DirName)) == 0) GetProcessDir(DirName);
      for (i=0; DirName[i] && DirName[i] != ':'; i++);
      if ((DirName[i] == ':' && DirName[i+1]) || len == 0)
      {
         DirName[i+1] = 0;
         NewDirectory(NOLOOKUP);
      }
   } else {
      if ((i = strlen(DirName)) > 0)
      {
         for (i-=2; i>=0 && DirName[i] != '/' && DirName[i] != ':'; i--);
         if (i < 0 || DirName[i] == ':') i++;
         DirName[MAX(i,0)] = 0;
         NewDirectory(NOLOOKUP);
      }
   }
}


/*
 *  DoDirPressed()
 *
 *  Look up the current directory.
 *  If SHIFT pressed, then disable the directory listing
 *  otherwise refresh the directory listing.
 */

static void DoDirPressed(Shifted)
int Shifted;
{
   GetProcessDir(DirName);
   if (Shifted)
   {
      DisableLSList(&LSGADGET[LS_NLIST]);
      ClearDirectoryList();
   } else {
      EnableLSList(&LSGADGET[LS_NLIST]);
      SetLSDirectory(DirName);
   }
}


/*
 *  DoDrive()
 *
 *  Get the name of the next mounted disk drive
 *  If no error, then look up the directory contents
 *  otherwise give an appropriate message.
 */

static void DoDrive()
{
   switch(DosNextDisk(DirName))
   {
      case NOERROR:
         NewDirectory(NOLOOKUP);
         break;

      case NOVOLMOUNT:
         LSMessage("No Volumes Mounted");
         DirName[0] = 0;
         NewDirectory(NOLOOKUP);
         break;

      case NOVOLOTHER:
         LSMessage("No Other Volumes Mounted");
         GetProcessDir(DirName);
         break;
   }
}


/*
 *  DoInfo()
 *
 *  If the specified file name can be locked, then
 *    If the ALT key is pressed
 *      If we can get an Info Block for the current disk,
 *        display its information
 *    Otherwise
 *      If we can get an FIB for the specified file
 *        If SHIFT is pressed
 *          display its comment field
 *        Otherwise
 *          get the file's creation date and its protection
 *          display the appropriate message for a file or directory
 *    Unlock the lock
 */

static void DoInfo(Shifted,ALTed)
int Shifted,ALTed;
{
   LOCK lock;
   FIB Fib;
   INFODATA theInfo;
   char time[16],prot[9];
   int i;

   if (DosLock(&lock,LSNameBuf,TYPE_FILE))
   {
      if (ALTed)
      {
         if (DosInfo(lock,&theInfo,TYPE_FILE))
            LSMessage("Disk: %ld Free %ld Used (%ld%%) %s",
               theInfo->id_NumBlocks - theInfo->id_NumBlocksUsed,
               theInfo->id_NumBlocksUsed,
               theInfo->id_NumBlocksUsed * 100 / theInfo->id_NumBlocks,
               (theInfo->id_DiskState == ID_WRITE_PROTECTED)? "Locked": "");  
      } else {
         if (DosExamine(lock,&Fib,TYPE_FILE))
         {
            if (Shifted)
            {
               if (Fib->fib_Comment[0])
                  LSMessage(Fib->fib_Comment);
                 else
                  LSMessage("No FileNote for this File");
            } else {
               GetStrTime(&time,&(Fib->fib_Date));
               strcpy(prot,"hsparwed");
               for (i=3; i>=0; i--)
                  if (Fib->fib_Protection & (1<<i)) prot[7-i] = '-';
               for (i=7; i>3; i--)
                  if ((Fib->fib_Protection & (1<<i)) == 0) prot[7-i] = '-';

               if (Fib->fib_DirEntryType < 0)
                  LSMessage("%ld Byte%s (%ld Block%s) [%s] %s",
                     Fib->fib_Size, (Fib->fib_Size != 1)? "s": "",
                     Fib->fib_NumBlocks, (Fib->fib_NumBlocks != 1)? "s": "",
                     prot, time);
                 else
                  LSMessage("Dir: [%s] %s",prot,time);
            }
         }
      }
      UnLock(lock);
   }
}


/*
 *  DoDelete()
 *
 *  If no file is named, give an error message,
 *  Otherwise if the file can be deleted,
 *    display the deletion message
 *    find the file in the directory listing (if any)
 *    remove the file entry from the list
 *    clear the name string
 *  Otherwise, give an error message
 */

static void DoDelete()
{
   FILEINFO theFile;

   if (LSNameBuf[0] == 0)
   {
      LSMessage("Can't Delete File: None Specified");
   } else if (DeleteFile(LSNameBuf)) {
      LSMessage("'%s' Deleted",LSNameBuf);
      theFile = FindFile(LSNameBuf);
      if (theFile) RemoveFile(theFile);
      SetStringInfo(&LSGADGET[LS_NAME],"");
      RefreshRequest(&LSRequest,LS_NAME,NULL);
   } else DosError("Delete","File");
}


/*
 *  DoMakeDir()
 *
 *  If no name was given, display an error
 *  Otherwise,
 *    If the new directory can not be created, display a message
 *    Otherwise,
 *      CD to the new directory
 *      get its complete path name,
 *      clear the name gadget
 *      look up the new directory contents
 */

static void DoMakeDir()
{
   LOCK lock;

   if (LSNameBuf[0] == 0)
   {
      LSMessage("Can't Create Directory: No Name Specified");
   } else {
      if ((lock = CreateDir(LSNameBuf)) == 0)
      {
         DosError("Create","Directory");
      } else {
         UnLock(lock);
         if (DosLock(&lock,LSNameBuf,TYPE_DIR))
         {
            UnLock(CurrentDir(lock));
            GetProcessDir(DirName);
            SetStringInfo(&LSGADGET[LS_NAME],"");
            RefreshRequest(&LSRequest,LS_NAME,NULL);
            NewDirectory(LOOKUPDIR);
         }
      }
   }
}


/*
 *  LSGadgetUp()
 *
 *  Handles GadgetUp events for the LoadSave file requester:
 *  Clear any message in the message box
 *  Clear the RE-WRITE button name if appropriate
 *  For each gadget, do the right thing (try to make sure the name gadget
 *    remains active):
 *
 *    LOAD, SAVE, REWRITE and NAME:  Open the specified file
 *    DRIVE:    Get the next drive's information
 *    INFO:     Get the file's information
 *    DELETE:   Delete the file
 *    MAKEDIR:  Make the new directory
 *    CANCEL:   Cancel the requester and put things back in order
 *
 *    The Directory Name:  lookup directory list
 *    Left Arrow:  Go to parent directory
 *    The Slider:  update the list position
 */

static void LSGadgetUp(theGadget,theMessage)
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   LSMessage(NULL);
   if (theGadget->GadgetID != LS_REWRITE) UnsetReWrite();
   switch(theGadget->GadgetID)
   {
      case LS_LOAD:
      case LS_SAVE:
      case LS_REWRITE:
      case LS_NAME:
         DoOpen(theGadget);
         break;

      case LS_DRIVE:
         DoDrive();
         ActivateLSName();
         break;

      case LS_INFO:
         DoInfo(theMessage->Qualifier & SHIFTKEYS,
                theMessage->Qualifier & ALTKEYS);
         ActivateLSName();
         break;

      case LS_DELETE:
         DoDelete();
         ActivateLSName();
         break;

      case LS_MAKEDIR:
         DoMakeDir();
         ActivateLSName();
         break;

      case LS_DIR:
         DoDirPressed(theMessage->Qualifier & SHIFTKEYS);
         ActivateLSName();
         break;

      case LS_DLARROW:
         DoDirUpPressed(theMessage->Qualifier & SHIFTKEYS);
         ActivateLSName();
         break;

      case LS_CANCEL:
         QuitInProgress = FALSE;
         RemoveRequest(&LSRequest);
         if (RequestID == LS_LOAD)
            InvertGadget(&cGadget[ID_LOAD]);
           else
            InvertGadget(&cGadget[ID_SAVE]);
         SetPointerColor(PenInUse);
         break;

      case LS_SLIDE:
         EndListSlider(theGadget->UserData);
         break;
   }
}


/*
 *  LSGadgetDown()
 *
 *  Handles GadgetDown message for the LoadSave File Requester
 *  Clear the message box if appropriate
 *  Clear the RE-WRITE gadget name
 *  For each gadget, do the right thing (try to make the name gadget active
 *    as often as possible):
 *
 *    A File Name in the List:  if double clicked, try to open it
 *    Up Arrow:    Scroll the list Up
 *    Down Arrow:  Scroll the List Down
 *    Slider:      Let the list manager know that the slider is active
 *    Name Gadget: Unselect any selected names in the list
 *    Message:     Get back the last message displayed
 */

static void LSGadgetDown(theGadget,theMessage)
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   if (theGadget->GadgetID != LS_MESSAGE &&
       theGadget->GadgetID != LS_NAME) LSMessage(NULL);
   UnsetReWrite();
   switch(theGadget->GadgetID)
   {
      case LS_NLIST:
         if (DoListHit(theGadget->UserData,theMessage)) DoOpen(theGadget);
         break;

      case LS_UARROW:
         DoListScrollUp(theGadget->UserData,theGadget,theMessage);
         break;

      case LS_DARROW:
         DoListScrollDown(theGadget->UserData,theGadget,theMessage);
         break;

      case LS_SLIDE:
         StartListSlider(theGadget->UserData);
         break;

      case LS_NAME:
         if ((LSGADGET[LS_NLIST].Flags & GADGDISABLED) == 0)
            UnsetListSelect(theGadget->UserData,TRUE);
         break;

      case LS_CHECK:
         ActivateLSName();
         break;

      case LS_MESSAGE:
         RecallLSMessage(theGadget);
         ActivateLSName();
         break;
   }
}


/*
 *  DoLoad()
 *
 *  If the requester is not already up
 *    Clear the name buffer
 *    Invert the LOAD gadget and set the pointer to its normal colors
 *    Set up the requester title and gadget names
 *    Open the requester
 *    If unsuccessful, put the pointer and LOAD gadgets back the way they were
 */

void DoLoad()
{
   extern void DoListMouse();

   if (ActiveRequest != &LSRequest)
   {
      LSNameBuf[0] = 0;
      InvertGadget(&cGadget[ID_LOAD]);
      RestorePointerColor();
      SetLSTexts("Load Circuit from File:","Load",
         "Load Library as Parts",LS_LOAD);
      LSListInfo.Image->NextImage = NULL;
      LSGADGET[LS_CHECK].Flags &= ~GADGDISABLED;
      AddRequest(&LSRequest,LSGadgetDown,LSGadgetUp,DoListMouse);
      if (ActiveRequest == NULL)
      {
         InvertGadget(&cGadget[ID_LOAD]);
         SetPointerColor(PenInUse);
      }
   }
}


/*
 *  DoSaveAs()
 *
 *  If the requester is not already active
 *    Copy the current circuit file name to the name gadget
 *    Invert the SAVE gadget and set the pointer to its normal colors
 *    Set the requester title and gadget names
 *    Disable PARTS gadget if necessary
 *    Open the requester
 *    If unsuccessful, put things back as they were
 */

void DoSaveAs()
{
   extern void DoListMouse();

   if (ActiveRequest != &LSRequest)
   {
      strcpy(LSNameBuf,FileName);
      InvertGadget(&cGadget[ID_SAVE]);
      RestorePointerColor();
      SetLSTexts((SelectType)? "Save Selection to File:":
         "Save Circuit to File:","Save","Save as Library (Parts Only)",LS_SAVE);
      if (SelectType || PartsList.FirstPart == NULL)
         LSGADGET[LS_CHECK].Flags |= GADGDISABLED;
        else
         LSGADGET[LS_CHECK].Flags &= ~GADGDISABLED;
      LSListInfo.Image->NextImage = NULL;
      AddRequest(&LSRequest,LSGadgetDown,LSGadgetUp,DoListMouse);
      if (ActiveRequest == NULL)
      {
         InvertGadget(&cGadget[ID_SAVE]);
         SetPointerColor(PenInUse);
      }
   }
}


/*
 *  DoSave()
 *
 *  If cells are selected, request a file for them to be saved into
 *  Otherwise,
 *    If changes have been made to the current circuit
 *      If the circuit has no name yet
 *        get a file name for the circuit
 *      Otherwise
 *        Invert the SAVE button during the save operation
 *        and put the pointer colors back to normal
 *        save the circuit to its file
 *    Otherwise give a message that no saving is required
 */

void DoSave()
{
   if (SelectType)
   {
      DoSaveAs();
   } else {
      if (Changed)
      {
         if (FileName[0] == 0)
         {
            DoSaveAs();
         } else {
            InvertGadget(&cGadget[ID_SAVE]);
            RestorePointerColor();
            WriteFile(FileName,0);
         }
      } else {
         DoError("Circuit has not been Modified");
      }
   }
}


/*
 *  DoNew()
 *
 *  Cancel any selection
 *  If changes have been made
 *    Ask whether changes should be discarded
 *  If still going on with the NEW command,
 *    Clear the library list
 *    Clear the grid
 *    Clear the UNDO and RESET grids
 *    Clear the file name and the Changed flag
 */

void DoNew()
{
   int OKtoDoNew = TRUE;

   if (SelectType) CancelSelect();
   if (Changed)
   {
      InvertGadget(&cGadget[ID_LOAD]);
      RestorePointerColor();
      OKtoDoNew =
         DoQuestion(" Changes have not been Saved. Discard Changes?      ");
      SetPointerColor(PenInUse);
      InvertGadget(&cGadget[ID_LOAD]);
   }
   if (OKtoDoNew)
   {
      ClearLibs();
      DoClear();
      ClearGrid(UndoArray);
      ClearGrid(ResetArray);
      Changed = FALSE;
      FileName[0] = 0;
   }
}


/*
 *  DoDiskInseted()
 *
 *  If the file requester is active
 *    Look up the currently mounted floppy drives
 *    If a new disk was inserted, go to the root of that disk
 */

void DoDiskInserted(theMessage)
struct IntuiMessage *theMessage;
{
   int OldMounted = Mounted;

   if (ActiveRequest == &LSRequest)
   {
      Mounted = MountedDrives();
      if (DosInsertedDrive(OldMounted,DirName))
         NewDirectory(LOOKUPDIR);
   }
}


/*
 *  DoDiskRemoved()
 *
 *  Get the currently mounted disks, so we can tell what new disks
 *  are inserted later.
 */

void DoDiskRemoved(theMessage)
struct IntuiMessage *theMessage;
{
   Mounted = MountedDrives();
}
