/* FitDisk V2.0
   Christian Stieber, Konradstraße 41, D-85055 Ingolstadt (Germany)
   stieber@informatik.tu-muenchen.de */

/* This program is PUBLIC DOMAIN */

/* Note: this program assumes that SAS/C does not access local
   variables via SP, but instead loads SP into an adress register
   at function entry, and uses that register to access the local
   variables (link / unlink) !!!!
   This is required to make the stack-switching work
*/

/* define V39 if you want to compile for V39 and above ONLY */
/* #define V39 */

#define STACK_SIZE 4096

#ifndef EXEC_EXECBASE_H
#include <exec/execbase.h>
#endif

#ifndef DOS_DOSEXTENS_H
#include <dos/dosextens.h>
#endif

#ifndef PROTO_EXEC_H
#include <proto/exec.h>
#endif

#ifndef PROTO_DOS_H
#include <proto/dos.h>
#endif

#ifndef V39
#ifndef CLIB_ALIB_PROTOS_H
APTR LibAllocPooled( APTR poolHeader, unsigned long memSize );
APTR LibCreatePool( unsigned long memFlags, unsigned long puddleSize,
	unsigned long threshSize );
void LibDeletePool( APTR poolHeader );
void LibFreePooled( APTR poolHeader, APTR memory, unsigned long memSize );
#endif   /* CLIB_ALIB_PROTOS_H */
#else /* V39 only */
#define LibAllocPooled AllocPooled
#define LibCreatePool CreatePool
#define LibDeletePool DeletePool
#define LibFreePooled FreePooled
#endif   /* V39 */

#include <string.h>

/***********************************************/

struct FileInfo   /* This structure holds information about a file */
   {
      struct FileInfo *Next;
      long Blocks;
      char Name[1];
   };

struct Arguments   /* Filled in by ReadArgs() */
   {
      long *BlocksFree;
      char *Directory;
      long OFS;
      long MSDOS;
   };

/***********************************************/

struct ExecBase *SysBase;
struct DosLibrary *DOSBase;

static struct Arguments Arguments=
   {
      NULL,
      NULL,
      FALSE,
      FALSE
   };

static long BlocksFree=1756;   /* the default number of free blocks */

static void *MemoryPool;       /* heap */

static long Files=0;                     /* number of files in list */
static struct FileInfo *FileList=NULL;   /* the directory */

static struct FileInfo ***Selected;      /* complicated... which files are selected at each level of the recursion */

static long Break=RETURN_OK;             /* set if we have to break */

static struct StackSwapStruct *Stacks;   /* an array of stacks for the recursion */

static char Version[]="$VER: FitDisk 2.1 (26.01.94)"
#ifdef V39
" (V39)"
#endif
;

/***********************************************/

static void MakeFileList(void);
static int ReadDir(void);

/***********************************************/

int __saveds main(void)

{
   int RC;
   struct RDArgs *RDArgs;

   RC=RETURN_OK;
   SysBase=*(struct ExecBase **)4;
   if (DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",37))
      {
         if (MemoryPool=LibCreatePool(0,64*1024,64*1024))
            {
               if (RDArgs=ReadArgs("BLOCKS/N,DIRECTORY=DIR,OFS/S,MSDOS/S",(long *)&Arguments,NULL))
                  {
                     if (Arguments.BlocksFree)
                        {
                           BlocksFree=*Arguments.BlocksFree;
                        }
                     else if (Arguments.MSDOS)
                        {
                           BlocksFree=1426;
                        }
                     if (!(RC=ReadDir()) && Files)
                        {
                           if (Selected=LibAllocPooled(MemoryPool,(Files+1)*sizeof(struct FileInfo **)))
                              {
                                 memset(Selected,0,(Files+1)*sizeof(struct FileInfo **));
                                 if (Stacks=LibAllocPooled(MemoryPool,Files*sizeof(struct StackSwapStruct)))
                                    {
                                       memset(Stacks,0,Files*sizeof(struct StackSwapStruct));
                                       MakeFileList();
                                       RC=Break;
                                    }
                                 else
                                    {
                                       PrintFault(IoErr(),NULL);
                                       RC=RETURN_ERROR;
                                    }
                              }
                           else
                              {
                                 PrintFault(IoErr(),NULL);
                                 RC=RETURN_ERROR;
                              }
                        }
                     FreeArgs(RDArgs);
                  }
               else
                  {
                     PrintFault(IoErr(),NULL);
                     RC=RETURN_ERROR;
                  }
               LibDeletePool(MemoryPool);
            }
         else
            {
               PrintFault(IoErr(),NULL);
               RC=RETURN_ERROR;
            }
         CloseLibrary((struct Library *)DOSBase);
      }
   else
      {
         RC=100;
      }
   return RC;
}

/***********************************************/

static BOOL MakeSelected(int k)

{
   if ((Selected[k]) ||
       (Selected[k]=LibAllocPooled(MemoryPool,(k+2)*sizeof(struct FileInfo *))))
      {
         memset(Selected[k],0,(k+2)*sizeof(struct FileInfo *));
         return TRUE;
      }
   else
      {
         PrintFault(IoErr(),NULL);
         Break=RETURN_ERROR;
         return FALSE;
      }
}

/***********************************************/

static BOOL MakeStack(int k)

{
   if ((Stacks[k].stk_Lower) ||
       (Stacks[k].stk_Lower=LibAllocPooled(MemoryPool,STACK_SIZE)))
      {
         Stacks[k].stk_Upper=((ULONG)(Stacks[k].stk_Lower))+STACK_SIZE;
         Stacks[k].stk_Pointer=(APTR)(Stacks[k].stk_Upper);
      }
   else
      {
         PrintFault(IoErr(),NULL);
         Break=RETURN_ERROR;
         return FALSE;
      }
}

/***********************************************/

#define MySelected     Selected[k]
#define ParentSelected Selected[k+1]

static long Select_k_Files(int k, struct FileInfo *StartFile, long BlocksFree, int *FileCount)

{
   long MinBlocksFree;
   struct FileInfo *FirstFile;
   long t;
   int MyFileCount;

   MinBlocksFree=BlocksFree;
   *FileCount=0;

   ParentSelected[0]=(struct FileInfo *)NULL;
   if (k>=0)
      {
         FirstFile=StartFile;
         while (FirstFile && !Break)
            {
               t=BlocksFree-FirstFile->Blocks;
               if (t>=0)
                  {
                     MySelected[0]=FirstFile;
                     MyFileCount=0;
                     if (t)
                        {
                           MySelected++;
                           StackSwap(&Stacks[k]);
                           t=Select_k_Files(k-1,FirstFile->Next,t,&MyFileCount);
                           StackSwap(&Stacks[k]);
                           MySelected--;
                        }
                     if (t<MinBlocksFree)
                        {
                           MinBlocksFree=t;
                           memcpy(ParentSelected,MySelected,(k+1)*sizeof(struct FileInfo *));
                           *FileCount=MyFileCount+1;
                           if (*FileCount==k+1) t=0;
                        }
                  }
               if (t)
                  {
                     FirstFile=FirstFile->Next;
                  }
               else
                  {
                     FirstFile=NULL;
                  }
               if (CheckSignal(SIGBREAKF_CTRL_C))
                  {
                     PrintFault(ERROR_BREAK,NULL);
                     Break=RETURN_WARN;
                  }
            }
      }
   return MinBlocksFree;
}

/***********************************************/

static void MakeFileList(void)                                    

{
   int k;
   long MinBlocksFree, t;
   int i;
   int FileCount;

   MinBlocksFree=BlocksFree;
   for (k=0; k<Files && MinBlocksFree && !Break; k++)
      {
         if (MakeSelected(k) && MakeSelected(k+1) && MakeStack(k))
            {
               t=Select_k_Files(k,FileList,BlocksFree,&FileCount);
               if (!Break)
                  {
                     if (t<MinBlocksFree)
                        {
                           MinBlocksFree=t;
                           Printf("I was able to fill %ld blocks with %ld file%s:\n",BlocksFree-MinBlocksFree,FileCount,FileCount==1 ? "" : "s");
                           for (i=0; i<Files && Selected[k+1][i]; i++)
                              {
                                 Printf("%s (%ld blocks)\n",Selected[k+1][i]->Name,Selected[k+1][i]->Blocks);
                              }
                           PutStr("\n");
                        }
                  }
            }
      }
}

/***********************************************/

static long BlocksUsed(long ByteSize)

{
   if (Arguments.MSDOS)
      {
         return 2*((ByteSize+1023)/1024);
      }
   else
      {
         long DataBlocks;

         DataBlocks=Arguments.OFS ? (ByteSize+487)/488 : (ByteSize+511)/512;
         if (!DataBlocks) DataBlocks=1;
         return(DataBlocks+(DataBlocks+71)/72);
      }
}

/***********************************************/

static void InsertFile(struct FileInfo *FileInfo)

{
   struct FileInfo *PrevInfo, *CurrentInfo;

   CurrentInfo=FileList;
   PrevInfo=(struct FileInfo *)&FileList;
   while (CurrentInfo && CurrentInfo->Blocks>FileInfo->Blocks)
      {
         PrevInfo=CurrentInfo;
         CurrentInfo=CurrentInfo->Next;
      }
   PrevInfo->Next=FileInfo;
   FileInfo->Next=CurrentInfo;
}

/***********************************************/

static int ReadDir(void)

{
   static struct FileInfoBlock __aligned FileInfoBlock;
   struct FileInfo *FileInfo;
   BPTR Directory;

   if (Arguments.Directory)
      {
         Directory=Lock(Arguments.Directory,ACCESS_READ);
      }
   else
      {
         Directory=DupLock(((struct Process *)(SysBase->ThisTask))->pr_CurrentDir);
      }
   if (Directory)
      {
         if (Examine(Directory,&FileInfoBlock))
            {
               while (ExNext(Directory,&FileInfoBlock))
                  {
                     if (FileInfoBlock.fib_DirEntryType<0 && BlocksUsed(FileInfoBlock.fib_Size)<=BlocksFree)
                        {
                           if (FileInfo=LibAllocPooled(MemoryPool,sizeof(struct FileInfo)+strlen(FileInfoBlock.fib_FileName)))
                              {
                                 strcpy(FileInfo->Name,FileInfoBlock.fib_FileName);
                                 FileInfo->Blocks=BlocksUsed(FileInfoBlock.fib_Size);
                                 InsertFile(FileInfo);
                                 Files++;
                              }
                           else
                              {
                                 PrintFault(IoErr(),NULL);
                                 return RETURN_ERROR;
                              }
                        }
                  }
               if (IoErr()!=ERROR_NO_MORE_ENTRIES)
                  {
                     goto DirError;
                  }
            }
         else
            {
               goto DirError;
            }
      }
   else
      {
DirError:PrintFault(IoErr(),Arguments.Directory);
         return RETURN_ERROR;
      }
   return RETURN_OK;
}
