/*
 *  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:  cDos.c               Handles AmigaDOS calls and BCPL stuff.
 */


#include "Cells.h"
#include "cRequest.h"
#include "cDos.h"
#include "cMemory.h"

UWORD Mounted;

static FIB Fib;                     /* a common FIB structure */
static INFODATA InfoData;           /* a cmmon INFO structure */
static struct Process *theProc;     /* the CELLS process */
static LOCK InitialDir;             /* the initial directory LOCK */
static LOCK SwapLock;               /* temprary swap storage */

static char *FType[] = {"Directory","File"};


/*
 *  The DOS error messages
 */

#define BASE_ERROR      201
#define TOP_ERROR       226
static char *DosErr[] =
{
   "Error 00000",
   "No Default Directory",
   "Object in Use",
   "Object Already Exists",
   "Directory Not Found",
   "Object Not Found",
   "Bad Stream Name",
   "Object Too Large",
   "Error 208",
   "Action Not Known",
   "Invalid Component Name",
   "Invalid Lock",
   "Object of Wrong Type",
   "Disk Not Validated",
   "Disk Write Protected",
   "Rename Across Devices",
   "Directory Not Empty",
   "Too Many Levels",
   "Device Not Mounted",
   "Seek Error",
   "Comment Too Big",
   "Disk Full",
   "Delete Protected",
   "Write Protected",
   "Read Protected",
   "Not a DOS Disk",
   "No Disk in Drive"
};


/*
 *  Free the common FIB and INFO structures if they have been allocated
 */

void FreeBlock()
{
   if (Fib)      FREESTRUCT(FileInfoBlock,Fib);
   if (InfoData) FREESTRUCT(InfoData,InfoData);
}


/*
 *  DosError()
 *
 *  If the error is outside the known error range,
 *    set up the dummy error string
 *  Otherwise use the known error string
 *  If the active requester is a File requester (which has a message box)
 *    put the message in the message box,
 *  Otherwise
 *    put up an error message
 */

void DosError(s1,s2)
char *s1,*s2;
{
   extern struct ExtRequest LSRequest;
   int ErrNo = IoErr();
   char *Error; 
   
   if (ErrNo < BASE_ERROR || ErrNo > TOP_ERROR)
   {
      sprintf(DosErr[0],"Error %d",ErrNo);
      Error = DosErr[0];
   } else {
      Error = DosErr[ErrNo-BASE_ERROR+1];
   }
   if (ActiveRequest == &LSRequest)
      LSMessage("Can't %s %s: %s",s1,s2,Error);
     else
      DoError("Can't %s %s: %s",s1,s2,Error);
}


/*
 *  DosLock()
 *
 *  Attempt to lock the specified file.  If no good, give an error message,
 *  otherwise set the lock pointer to the specified lock, and return TRUE.
 */

int DosLock(LockPtr,Name,Index)
LOCK *LockPtr;
char *Name;
int Index;
{
   if (!(*LockPtr = Lock(Name,ACCESS_READ)))
      DosError("Lock",FType[Index]);
   return((*LockPtr) != NULL);
}


/*
 *  DosExamine()
 *
 *  If the common FIB has not been allocated, allocate it.
 *  If the allocate failed, give a message
 *  Otherwise try to examine the given lock.
 *  If it can't be examined, give an error.
 */

int DosExamine(theLock,FibPtr,Index)
LOCK theLock;
FIB *FibPtr;
int Index;
{
   int status = FALSE;

   if (Fib == NULL) NEWSTRUCT(FileInfoBlock,Fib);
   if ((*FibPtr = Fib) == NULL)
      LSMessage("Can't Allocate FileInfoBlock");
   else if (Examine(theLock,Fib)) status = TRUE;
   else DosError("Examine",FType[Index]);
   return(status);
}


/*
 *  DosInfo()
 *
 *  If the common Info structure has not been allocated, allocate it.
 *  If the allocation failed, give a message,
 *  Otherwise attempt to the the info on the given lock
 *  If the Info call failed, give an error message.
 */

int DosInfo(theLock,InfoPtr,Index)
LOCK theLock;
INFODATA *InfoPtr;
int Index;
{
   int status = FALSE;

   if (InfoData == NULL) NEWSTRUCT(InfoData,InfoData);
   if ((*InfoPtr = InfoData) == NULL)
      LSMessage("Can't Allocate Memory for Info Data");
   else if (Info(theLock,InfoData)) status = TRUE;
   else DosError("Get Info for",FType[Index]);
   return(status);
}


/*
 *  BSTRcpy()
 *
 *  Copy a BCPL string to a C string buffer and return the size of the string.
 */

int BSTRcpy(to,BCPLfrom)
char *to,*BCPLfrom;
{
   char *from = BCPL_TO_CHAR(BCPLfrom);
   
   strncpy(to,from+1,(int)(*from));
   *(to+(*from)) = 0;
   return((int)*from);
}


/*
 *  LockCurrentDir()
 *
 *  Duplicate a lock on the current directory (to guarantee a non-null lock)
 *  If the duplicate failed, unlock the duplicate and lock the root directory.
 */

LOCK LockCurrentDir() 
{
   LOCK CurDir;
   
   CurDir = DupLock(theProc->pr_CurrentDir);
   if (CurDir == NULL)
   {
      UnLock(CurDir);
      CurDir = Lock(":",ACCESS_READ);
   }
   return(CurDir);
}


/*
 *  GetPathFromLock()
 *
 *  Clear the name string
 *  Start with a duplicate of the specified lock (so we can unlock it)
 *  Get the device name of the device associated with the lock, and add
 *  a ":" to the end of the string.
 *
 *  If the common FIB is not allocated, allocate it.
 *  While there is a current directory lock,
 *    Examine the lock
 *    If it could not be examined,
 *      clear the directory name and unlock the lock,
 *    Otherwise
 *      Keep a copy of the old lock,
 *      Get a lock on the parent of the lock, and unlock the old lock
 *      If there is a parent,
 *        get the lenght of the parent's name,
 *        move the contents of the current path to make room for the
 *          new parent directory name.
 *        copy the parent directory name into the string
 *        add a '/' if there were other names already.
 */

char *GetPathFromLock(dir,lock)
char *dir;
LOCK lock;
{
   LOCK CurDir,OldDir;
   char *subdir = dir;
   char *s,*s1;
   char c;
   int len;

   *dir = 0;
   CurDir = DupLock(lock);
   Forbid();
   subdir += BSTRcpy(dir,DEVLIST(FILELOCK(CurDir)->fl_Volume)->dl_Name);
   Permit();
   *subdir++ = ':';
   *subdir = 0;

   if (Fib == NULL) if (NEWSTRUCT(FileInfoBlock,Fib) == NULL)
   {
      LSMessage("Can't Allocate FileInfoBlock");
      return(NULL);
   }
   while (CurDir != NULL)
   {
      if (!Examine(CurDir,Fib))
      {
         *dir = 0;
         UnLock(CurDir);
         CurDir = NULL;
      } else {
         OldDir = CurDir;
         CurDir = ParentDir(OldDir);
         UnLock(OldDir);
         
         if (CurDir)
         {
            len = strlen(Fib->fib_FileName);
            for (s=subdir+strlen(subdir),s1=s+len+1; s>=subdir; *s1-- = *s--);
            c = *subdir;
            strcpy(subdir,Fib->fib_FileName);
            if (c) *s1 = '/';
         }
      }
   }
   RETURN(dir);
}


/*
 *  GetProcessDir()
 *
 *  Lock the current directory and get the path name of that lock.
 *  Unlock the lock and return the name.
 */

char *GetProcessDir(dir)
char *dir;
{
   LOCK lock;
   
   lock = LockCurrentDir();
   GetPathFromLock(dir,lock);
   UnLock(lock);
   RETURN(dir);
}

/*
 *  The names of the floppy drives
 */

#define MAXDRIVE    4
static char DriveName[MAXDRIVE][5] = {"DF0:","DF1:","DF2:","DF3:"};


/*
 *  MountedDrives()
 *
 *  Set the process WindoowPtr so that no system requests will occur
 *  For each drive name in the list
 *    try to lock the give drive
 *    If successful
 *      unlock the drive, and record that the drive is mounted
 *  Put the window pointer back
 *  return the mounted drives flag.
 */

UWORD MountedDrives()
{
   APTR OldWindowPtr = theProc->pr_WindowPtr;
   LOCK lock;
   short i;
   UWORD drive,Mounted = 0;
   
   theProc->pr_WindowPtr = (APTR) -1;
   for (i=0,drive=1; i<MAXDRIVE; i++,drive<<=1)
   {
      lock = Lock(DriveName[i],ACCESS_READ);
      if (lock)
      {
         UnLock(lock);
         Mounted |= drive;
      }
   }
   theProc->pr_WindowPtr = OldWindowPtr;
   return(Mounted);
}


/*
 *  DosInsertedDrive()
 *
 *  For each of the currently mounted drives that didn't used to be mounted
 *    attempt to lock the drive
 *    if successful,
 *      if the the volume is not the same as the current directoty's volume,
 *        get the name of the drive, and stop looking for more drives.
 */

int DosInsertedDrive(OldMounted,Name)
UWORD OldMounted;
char *Name;
{
   int status = FALSE;
   LOCK lock;
   short i = 0;
   
   OldMounted = Mounted & (~OldMounted);
   while (OldMounted)
   {
      if (OldMounted & 1)
      {
         lock = Lock(DriveName[i],ACCESS_READ);
         if (lock)
         {
            if (FILELOCK(lock)->fl_Volume != 
                FILELOCK(theProc->pr_CurrentDir)->fl_Volume)
            {
               strcpy(Name,DriveName[i]);
               OldMounted = 0;
               status = TRUE;
            }
         }
      }
      OldMounted >>= 1;
      i++;
   }
   return(status);
}


/*
 *  GetDevName()
 *
 *  Get the device name of a specified device into a C string
 */

void GetDevName(to,Dev)
char *to;
struct DeviceList *Dev;
{
   char *from = BCPL_TO_CHAR(Dev->dl_Name);
   
   strncpy(to,from+1,(int)(*from));
   strcpy(to+(*from),":");
}


/*
 *  GetNextMounted()
 *
 *  Skip any non-devices (i.e., ASSIGNs and volumes) and any non-mounted
 *    devices in the device list.
 *  If we found a device
 *    get its name, and attempt to lock it
 *    if successful,
 *      get the volume mounted on the device
 *      and record it as the first volume, if there isn't already one recorded
 *      unlock the device
 *    go on to the next device
 */

static void GetNextMounted(Dev,DevVol,FirstVol,Name)
struct DeviceList **Dev,**DevVol,**FirstVol;
char *Name;
{
   LOCK lock;
   
   while (*Dev && ((*Dev)->dl_Type != DLT_DEVICE || (*Dev)->dl_Task == NULL))
      *Dev = DEVLIST((*Dev)->dl_Next);
   if (*Dev)
   {
      GetDevName(Name,*Dev);
      lock = Lock(Name,ACCESS_READ);
      if (lock)
      {
         *DevVol = DEVLIST(FILELOCK(lock)->fl_Volume);
         if (FirstVol && *FirstVol == NULL) *FirstVol = *DevVol;
         UnLock(lock);
      }
      *Dev = DEVLIST((*Dev)->dl_Next);
   }
}


/*
 *  DosNextDisk()
 *
 *  Set the window pointer so that system requests will not appear
 *  Forbid() since we will be using a system list (the device list)
 *  Get the pointer to the volume of the current directory
 *  Starting with the first volume in the device list, look through the
 *    device list until we run out of devices, or find the current device
 *    (note that this will find the FirstVol - the first mounted volume)
 *  Find the colume following the current one (or the end of the list).
 *  If there is no following volume,
 *    if there is no first mounted volume,
 *      return the NO VOLUMES MOUNTED error
 *    otherwise,
 *      if the first volume is also the current one,
 *        return the NO OTHER VOLUMES MOUNTED error
 *      get the name of the first volume in the list.
 *  otherwise
 *    get the name of the volume found following the current one
 *  Permit() again (we're done with the device list)
 *  put back the window pointer so system requesters show up again.
 */

int DosNextDisk(Name)
char *Name;
{
   int status = NOERROR;
   struct DeviceList *Dev = NULL;
   struct DeviceList *DevVol = NULL;
   struct DeviceList *FirstVol = NULL;
   struct DeviceList *CurVol = NULL;
   APTR OldWindowPtr = theProc->pr_WindowPtr;
   
   *Name = 0;
   theProc->pr_WindowPtr = (APTR) -1;
   
   Forbid();
   CurVol = DEVLIST(FILELOCK(theProc->pr_CurrentDir)->fl_Volume);
   for (Dev = FIRSTDEV; Dev && DevVol != CurVol;)
      GetNextMounted(&Dev,&DevVol,&FirstVol,Name);
   
   DevVol = NULL;
   while (Dev && DevVol == NULL)
      GetNextMounted(&Dev,&DevVol,NULL,Name);
   
   *Name = 0;
   if (DevVol == NULL)
   {
      if (FirstVol == NULL)
      {
         status = NOVOLMOUNT;
      } else {
         if (FirstVol == CurVol) status = NOVOLOTHER;
         GetDevName(Name,FirstVol);
      }
   } else {
      GetDevName(Name,DevVol);
   }
   Permit();
   
   theProc->pr_WindowPtr = OldWindowPtr;
   return(status);
}


/*
 *  SaveStartingDir()
 *
 *  Find the current task, and set the current directory to a copy of the
 *  lock on the current directory.  Save the old lock for replacement when
 *  we finish.  Save a copy of the current directory in the Swap Lock for 
 *  later.  Record the currently mounted floppies.
 */

void SaveStartingDir()
{
   theProc = FindTask(NULL);
   InitialDir = CurrentDir(LockCurrentDir());
   SwapLock = LockCurrentDir();
   Mounted = MountedDrives();
}


/*
 *  RestoreStartingDir()
 *
 *  If saved the initial lock, put it back.
 *  If there is a swap lock, unlock it.
 */

void RestoreStartingDir()
{
   if (theProc) UnLock(CurrentDir(InitialDir));
   if (SwapLock) UnLock(SwapLock);
}


/*
 *  SwapLocks()
 *
 *  Switch current directories temporarily (save the old one so we can swap
 *  back later)
 */

void SwapLocks()
{
   SwapLock = CurrentDir(SwapLock);
}


/*
 *  Days to the begining of each month, and names of the months
 */

static int
   DaysIn[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};

static char
   *NameOf[] = {"", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

#define CENTURY             (100 YEARS + 25 LEAPDAYS)
#define FOURCENTURIES       (400 YEARS + 99 LEAPDAYS)
#define FOURYEARS           (4 YEARS + 1 LEAPDAY)
#define LEAPYEAR            (1 YEARS + 1 LEAPDAY)
#define LEAPDAYS
#define LEAPDAY
#define YEARS               * 365
#define YEAR                365
#define FEBDAYS             59          /* days until the end of February */


/*
 *  GetStrTime()
 *
 *  Turn a date stamp into a character string
 */

void GetStrTime(time,date)
char time[16];             /* storage area for the return value */
struct DateStamp *date;    /* the DateStamp to convert */
{
   int year,month,day,hour,min;

   day = date->ds_Days + 78 YEARS + 20 LEAPDAYS;
/* day += (day - FEBDAYS - CENTURY + FOURCENTURIES) / FOURCENTURIES; */
   year = 4 * (day/FOURYEARS) + 1900;
   day %= FOURYEARS;
   day += (day - FEBDAYS - 1 LEAPDAY) / YEAR;
   year += day / LEAPYEAR;
   day %= LEAPYEAR;

   for (month=1; day >= DaysIn[month]; month++);
   day = day - DaysIn[month-1] + 1;

   hour = date->ds_Minute / 60;
   min  = date->ds_Minute - hour*60;

   sprintf(time,"%02d-%3s-%02d %02d:%02d",
     day,NameOf[month],(year % 100),hour,min);
}
