/*
 * browser - Rummage around on disks.
 *
 *   copyright (c) 1986, Mike Meyer
 *
 * Permission is hereby granted to distribute this program, so long as this
 * source file is distributed with it, and this copyright notice is not
 * removed from the file.
 *
 * modified Mark Schretlen  86-10-04
 *           last update    87-01-11
 *
 */

#include <exec/types.h>
#include <graphics/gfxbase.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <stdio.h>
#define INTUITION_REV      1L
#define GRAPHICS_REV      1L

#define LONGEST_NAME      80   /* Longest file name we can deal with */
#define LONGEST_LINE      256   /* Longest line we will deal with */
#define AVG_LINE_LENGTH      62   /* A guess, tune it if you need to */
#define FILEINFOBLOCKSIZE    256  /* m.e.s.  for memory allocation in Add_Dir */
#define FUDGE                6L  /* A fudge factor for the scroll bar */

#define UP_GADGET       0  /* switch flags for Gadget ID */
#define DOWN_GADGET     1
#define SCROLL_GADGET   2
#define NEW_FILE        3

#define GWIDTH         14   /* Width of gadget */
#define GHEIGHT         9
struct SpriteImage {
   UWORD posctl[2];
   UWORD sprdata[2][15];
   UWORD reserved[2];
   };

#define FIRST 18L

/*
 *  Gadget structures put in browser.h
 */

#include "browser.h"

/*
 *   doggy sprite (M.E.S. 86-10-01)
 */
struct SpriteImage Doggy[34] = {
      0x0002, 0x0002, /* VStart, VStop */
      0x0002, 0x0,
      0x3A02, 0x0,
      0x2A02, 0x1000,
      0xFA02, 0x8000,
      0xF9FE, 0x0,
      0xFFFE, 0x0,
      0x1FFE, 0x0,
      0x1FFE, 0x0,
      0x1FFC, 0x0,
      0x1FFC, 0x0,
      0x0A14, 0x0,
      0x0A14, 0x0,
      0x0A14, 0x0,
      0x0A14, 0x0,
      0x0A14, 0x0,
      0x0000, 0x0000 }; /* End of Sprite */
/*
 * Now, the window for it all
 */
struct NewWindow New_Window = {
   0, 0, 640, 200,         /* Full screen */
   -1L, -1L,            /* Default pens */
   MENUPICK | CLOSEWINDOW      /* I want to know about menupicks */
   | GADGETUP,         /* Window closes and gadgets */
   ACTIVATE         /* Standard window */
   | SMART_REFRESH | NOCAREREFRESH | SIZEBBOTTOM
   | WINDOWSIZING | WINDOWDEPTH | WINDOWCLOSE | WINDOWDRAG,
   &Down_Gadget,         /* Add scroll gadget */
   /*(struct Image *)*/ NULL,
   "Browser 1.2 by Mark Schretlen ;original-Mike Meyer;scroll-B.Leivian",   /* Title */
   /*(struct Screen *)*/NULL,
   /*(struct BitMap *)*/NULL,
   100, 57,         /* Minimum sizes */
   640, 200,         /* Maximum sizes */
   WBENCHSCREEN         /* and put it on the workbench */
   } ;

/*
 * My very own variables (mostly for done)
 */
struct Window   *My_Window = NULL ;
static FILE      *infile = NULL ;   /* Current input file */
static void      Page_File(unsigned short) ;

/* file statistics  M.E.S.  87-01-11 */
struct FStatistics *stats;
struct FStatistics fileInformation = { 0L,0L,0L };

/*
 * And someone else's variables
 */
struct IntuitionBase   *IntuitionBase ;
struct GfxBase      *GfxBase ;
extern struct Menu   *AutoMenu ;

/*
 * Finally, declare the string twiddling functions as voids
 */
void   strcat(char *, char *), strcpy(char *, char *) ,
   strncat(char *, char *, int) ;

main() {
   register struct IntuiMessage   *message,*GetMsg(struct MsgPort *);
   register unsigned short      class, code ;
   APTR address;
   USHORT gadID;

   /* Set up the world this lives in */
   if ((IntuitionBase = (struct IntuitionBase *)
       OpenLibrary("intuition.library", INTUITION_REV)) == NULL)
      done(20, "can't open intuition library") ;

   if ((GfxBase = (struct GfxBase *)
       OpenLibrary("graphics.library", GRAPHICS_REV)) == NULL)
      done(20, "can't open graphics library") ;

   /* set up the scroll bar */
   prop.Flags = FREEVERT | AUTOKNOB | KNOBHIT ;
   prop.VertBody = 0x1000;

   if ((My_Window = (struct Window *) OpenWindow(&New_Window)) == NULL)
      done(20, "can't open the window") ;

   SetAPen(My_Window -> RPort,3) ;   /* Should be default! */

   show_message("Pick Disk with Menu Button ");

   /* Set up the first menu level */
   Menu_Init() ;
   Menu_Add("disks ", 1L) ;
   Menu_Item_Add("df0:", ITEMENABLED, 0L, 0) ;
   Menu_Item_Add("df1:", ITEMENABLED, 0L, 0) ;
   Menu_Item_Add("df2:", ITEMENABLED, 0L, 0) ;
   Menu_Item_Add("DH0:", ITEMENABLED, 0L, 0) ;
   Menu_Item_Add("RAM:", ITEMENABLED, 0L, 0) ;
   Menu_Item_Add("VDK:", ITEMENABLED, 0L, 0) ;
   SetMenuStrip(My_Window, AutoMenu) ;

   SetPointer(My_Window, Doggy,15,16,0,-4);

   /* Now spin on messages, handling them as they arrive */
   for (;;) {
      Wait(1L << (long) My_Window -> UserPort -> mp_SigBit) ;
      while (message = GetMsg(My_Window -> UserPort)) {
         class = message -> Class ;
         code = message -> Code ;
         address = message -> IAddress;
         ReplyMsg(message) ;
         switch (class) {
            case CLOSEWINDOW:
               done(0, NULL) ;

            case MENUPICK:
               if (code != MENUNULL)
                  Examine_Menu_Pick(code) ;
               break ;

            case GADGETUP:
               gadID = ((struct Gadget *)address) -> GadgetID;
               Page_File(gadID);
               break ;

            default:
               printf("browser: intuition event 0x%x\n", class) ;
               done(20, "unexpected intution event") ;
               break ;
            }
         }
      }
   done(20, "broke out of never-breaking loop!") ;
}


/*
 * Examine_Menu_Pick - takes a menu pick, and twiddles the state variables
 *   to match that pick.
 */
static
Examine_Menu_Pick(Menu_Number)
unsigned short Menu_Number; 
{
   register unsigned short   level, i, dirp ;
   register char      *cp ;
   char         *name, *strchr(char *,int) ;
   struct MenuItem      *ItemAddress(struct Menu *, unsigned short) ;
   /* State variables that describe the current directory */
   static char      Dir_Name[LONGEST_NAME] ;
   static unsigned short   Menu_Level = 0 ;

   name = ((struct IntuiText *)
      (ItemAddress(AutoMenu,Menu_Number) -> ItemFill)) -> IText ;
   level = MENUNUM(Menu_Number) ;

   /* Got what we want, so clear the menu to avoid confusing the user */
   ClearMenuStrip(My_Window);
   OffGadget((APTR)&Scroll_Gadget, My_Window, NULL);
   OffGadget((APTR)&Up_Gadget, My_Window,NULL);
   OffGadget((APTR)&Down_Gadget, My_Window,NULL);

   SetWindowTitles(My_Window,"Browser 1.2 by Mark Schretlen (original by Mike Meyer) ",-1);

   /* set dirp to FALSE if the name is not a directory or disk */
   dirp = (strchr(name, '/') != NULL || strchr(name, ':') != NULL) ;

   /* First, set the directory name right */
   if (level > Menu_Level)         /* Not possible, die */
      done(20, "impossible menu value returned") ;
   else if (level == 0)         /* picked a new disk */
      Dir_Name[0] = '\0' ;
   else if (level < Menu_Level) {      /* Throw away some levels */
      for (i = 1, cp = strchr(Dir_Name, ':'); i < level; i++) {
         if (cp == NULL) done(20, "broken file name") ;
         cp = strchr(cp, '/') ;
      }
      if (cp == NULL) done(20, "broken file name") ;
      *++cp = '\0' ;
   }
   /* else Menu_Level == level, chose a file at current level */

   /* Now, fix up the menu and it's state variable */
   while (Menu_Level > level) {
      Menu_Level-- ;
      Menu_Pop() ;
   }

   /* If we added a directory, tack it onto the name */
   if (dirp) {
      Menu_Level++ ;
      strncat(Dir_Name, name,   LONGEST_NAME - strlen(Dir_Name) - 1) ;
   }

   /* Now, tell the user all about it */
   if (dirp)
      Add_Dir(Dir_Name, name) ;
   else
      Display_File(Dir_Name, name) ;
   SetMenuStrip(My_Window, AutoMenu) ;
}

/*
 * Add_Dir - given a dir and a name, add the menu name with the files in
 *   dir as entries.
 */
static
Add_Dir(dir, name) char *dir, *name; {
   struct FileInfoBlock     *File_Info ;
   register char         *last_char ;
   register struct FileLock   *my_lock, *Lock(char *, int) ;
   unsigned short         count ;
   static char         Name_Buf[LONGEST_NAME] ;

   File_Info = (struct FileInfoBlock *)AllocMem(FILEINFOBLOCKSIZE,0);

   /* Fix up the trailing / if it needs it */
   last_char = &dir[strlen(dir) - 1] ;
   if (*last_char == '/') *last_char = '\0' ;

   /* Now, start on the directory */
   if ((my_lock = Lock(dir, ACCESS_READ)) == NULL) {
      done(20, "can't get lock on file") ;
   }

   if (!Examine(my_lock, File_Info)) done(20, "can't examine file") ;
   if (File_Info -> fib_DirEntryType < 0)
      done(20, "Add_Dir called with a non-directory") ;

   Menu_Add(name, TRUE) ;

   SetAPen(My_Window -> RPort,3) ;
   show_message("Loading Directory          ");

   for (ExNext(my_lock, File_Info), count = 0;
        IoErr() != ERROR_NO_MORE_ENTRIES;
        ExNext(my_lock, File_Info), count++)
      if (File_Info -> fib_DirEntryType < 0)
         /* file */
         Menu_Item_Add(File_Info -> fib_FileName, ITEMENABLED, 0L, 0) ;
      else {
         /* directory */
         strcpy(Name_Buf, File_Info -> fib_FileName) ;
         strcat(Name_Buf, "/");
         Menu_Item_Add(Name_Buf, ITEMENABLED, 0L, 0) ;
      }
   if (count == 0) Menu_Item_Add("EMPTY", 0L, 0L, 0) ;

   show_message("Directory Available         ");

   /* Put everything back */
   if (*last_char == '\0') *last_char = '/' ;
   FreeMem(File_Info,FILEINFOBLOCKSIZE);
   UnLock(my_lock) ;
}

/*
 * Display_File - given a directory path and file name, put the first page of
 *   the file in the window.
 */

long aprox_lines,file_size;
static long Page_Length = 22L;

static
Display_File(dir, name)
char *dir, *name;
{
   static char   File_Name[LONGEST_NAME];
   FILE *fopen();
   long ftell();
   long i,pages;
   extern char *ChangeToString();
   extern FILE *parsefile();
   extern struct FStatistics *stats,fileInformation;
   char *num_of_lines,*num_of_bites;

   stats = &fileInformation;
   /* Get the file name */
   strcpy(File_Name, dir);
   strcat(File_Name, name);

   if (infile != NULL)
      fclose(infile);

   if ((infile = fopen(File_Name, "r")) == NULL) {
#ifdef   DEBUG
      printf("Can't open File: %s\n", File_Name) ;
#endif
      done(20, "can't open file") ;
   }

   /* set up the prop gadget for scrolling */
   infile = parsefile(infile);
   aprox_lines = stats->lines;
   file_size = stats->bites;

   prop.Flags = FREEVERT | AUTOKNOB;
   if (Page_Length >= aprox_lines)
      i = 0xFFFF;
   else
      {
      pages = aprox_lines/Page_Length;
      if (aprox_lines%Page_Length != 0) pages++;
      i =  0xFFFF / pages;                  /* FFFF=100% - 0000=0% */
      }
   prop.VertBody = i;
   prop.VertPot = 0L;
   fseek(infile, 0L, 0);


   if (Page_Length < aprox_lines)
      {
         OnGadget((APTR)&Down_Gadget,My_Window,NULL);
         OnGadget((APTR)&Up_Gadget,My_Window,NULL);
            /* enable the scroll bar */
         OnGadget((APTR)&Scroll_Gadget, My_Window, NULL);
      }

   SetWindowTitles(My_Window,"Browser 1.2   **** To page, hit appropriate arrow gadget. ****",-1);
   Move(My_Window->RPort,FIRST + 5L ,(long)(My_Window->Height - 5L));
   Text(My_Window->RPort,"                                              ",45L);
   Move(My_Window->RPort,FIRST + 5L ,(long)(My_Window->Height - 5L));
   SetAPen(My_Window->RPort,3);
/*   SetSoftStyle(My_Window->RPort, FSF_UNDERLINED,255); */
   Text(My_Window->RPort,name,(long)strlen(name));
   Text(My_Window->RPort,"     ",3L);
   num_of_bites = ChangeToString(file_size);
   Text(My_Window->RPort,num_of_bites,(long)strlen(num_of_bites));
   Text(My_Window->RPort," bytes",6L);
   Text(My_Window->RPort,"     ",3L);
   num_of_lines = ChangeToString(aprox_lines);
   Text(My_Window->RPort,num_of_lines,(long)strlen(num_of_lines));
   Text(My_Window->RPort," lines",6L);

   SetAPen(My_Window->RPort,1);
/*   SetSoftStyle(My_Window->RPort, FS_NORMAL,255);       */

   Move(My_Window->RPort,FIRST,17L);

   Page_File((USHORT)NEW_FILE);      /* Down from page 0 */
}

/*
 * Page_File - move the file up or down one "page"
 */
static void
Page_File(direction) 
USHORT direction;
{
   long where;
   static char   buffer[LONGEST_LINE];
   static char edited[84]; /* allow room for a tab at end */
   static long old_pos;
   static long bites_this_page;
   static long next_page;
   static long size;
   int end_flag = 0;
   long  Line_Length;
   int i,j;
   long new_pos;
   char *p;

   if (infile == NULL) return ;

   Page_Length = ((My_Window -> Height) - 20) / 8 ;
   Line_Length = (My_Window -> Width - (3+FIRST)) / 8;

   switch(direction)
      {
         case UP_GADGET:
            new_pos = old_pos;
            old_pos = 1L;
            break;
         case DOWN_GADGET:
            new_pos = next_page;
            break;
         case SCROLL_GADGET:
             /* compute new position based on the users scroll bar pot */
            new_pos = (file_size * prop.VertPot) / 0xFFFF;
            break;
         case NEW_FILE:
            bites_this_page = 0L;
            next_page = 0L;
            new_pos = 0L;
            old_pos = 1L;
            size = 0L;
            break;
      }



      if (new_pos < 0) new_pos = 0L;

      /* if past end of file, back up approx 1/2 page */
      if (new_pos > file_size)
         new_pos = file_size - (bites_this_page / 2);

      if ( new_pos == old_pos )  return;   /* no change */

      where = (new_pos * 0xFFFF)/file_size ;
      ModifyProp(&Scroll_Gadget,My_Window,NULL,prop.Flags,0,where,0,prop.VertBody);

      old_pos = (new_pos + (size +2)) - bites_this_page ;  /* backup one page */
      bites_this_page = 0L;

      fseek(infile, new_pos, 0);

      /* discard a partial line */
      if (new_pos)
         fgets(buffer, LONGEST_LINE, infile);

   /* now put out one page's worth of the file's data */


   for (where = 17L, j = Page_Length; j--; where = where + 8L)
   {
      Move(My_Window -> RPort,FIRST,where ) ;

      /* blank the buffer first */
      for (i = 0; i < 80; i++)
         edited[i] = ' ';
 
      if (!end_flag)
       {
         if (fgets(buffer, LONGEST_LINE, infile) == NULL) 
            end_flag = TRUE;
         else
          {
            size = strlen(buffer);
            bites_this_page = size + bites_this_page;

            /* remove the newline */
            buffer[size-1] = '\0';
            size--;

            p = buffer;
            size = 0;

            /* edit the buffer for tabs and non-printables */
            while(*p)
              {
                if (*p == '\t')
                  {
                    do {
                         edited[size++] = ' ';
                       } while(size&3);
                  }
                else if(*p < ' ')
                      {
                        edited[size++] = '^';
                        edited[size++] = *p + '@';
                      } else 
                          edited[size++] = *p;

                p++;

                /* mark the line as longer than the window */
                if (size >= Line_Length)
                 {
                   edited[size-1] = '>';
                   break;
                 }
              }
          }
       }
      Text(My_Window -> RPort, edited, Line_Length);
   } /* for 1 page to size of window */
   next_page = (bites_this_page + new_pos) - (size + 2);
   return;
}

/*
 * done - just close everything that's open, and exit.
 */
static
done(how, why)
int how;
char *why; 
{

   if (My_Window) {
      ClearMenuStrip(My_Window) ;
      Menu_Clear() ;
      CloseWindow(My_Window) ;
   }
   if (GfxBase) CloseLibrary(GfxBase) ;
   if (IntuitionBase) CloseLibrary(IntuitionBase) ;
   if (infile) fclose(infile) ;

   if (why)
      printf("browser: %s\n", why) ;
   (void) OpenWorkBench() ;
   exit(how) ;
}

show_message(s)
char *s;
{
   Move(My_Window -> RPort, FIRST, 17L);
   Text(My_Window -> RPort, s, (long) strlen(s));
   Move(My_Window -> RPort, FIRST, 25L);
   Text(My_Window -> RPort, "                    ", 20L);
}
