#include <stdio.h>
#include <assert.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <exec/memory.h>
#include <exec/types.h>
#include <intuition/intuition.h>

#include "ccd.h"
#include "win.h"

#define GRAPHICS
#define VERSION "2"
#define WINDOWTITLE "ccd v2.0, beta version         by Cédric BEUST"

#define CCDCONFIGFILE "ccd:.ccdconfig"
#define WIDTH 640             /* width of the display */
#define HEIGHT 200           /* its height */
#define DELTAX 80           /* # of pixels between two entries */
#define DELTAY 10          /* vertical step */
#define UPPERX 25         /* upper corner where we start to write */
#define UPPERY 7         /* ditto */

#define LINESPERWINDOW 18          /* # of lines displayed */
#define BETWEEN_GADGETS 10        /* # of vert pixels between gadgets */

#define min(a,b) (a < b ? a : b)

#define NEW(v, t) v = (t *) malloc(sizeof(*v))

static char actualdir[128];
struct Window *mainwindow;
struct Screen *mainscreen;
long IntuitionBase;
long GfxBase;
int configlines = 0;    /* # of lines in ccdconfig */
int actualline = 1;
int nbvolumes = 0;    /* # of volumes found */
int volumesid = 0;   /* id of the first Volume gadgets */

USHORT *chippointer = NULL, *chippointer2 = NULL;

struct Tree_t {
  struct Tree_t *brother;
  struct Tree_t *son;      /* could be sister and daughter... */
  char name[128];
  char fullname[128];
  int x;
  int line;
} *the_tree = NULL;

int show_ambiguities = 0;
int update_list = 0;
int no_expand = 0;

BPTR locklist[128];
int lockpt = 0;

BPTR
get_a_lock(char *name, long code)
{
  locklist[lockpt] = Lock(name, code);
  if (locklist[lockpt])
    return locklist[lockpt++];
  else
    return 0;
}

void
unlock(BPTR lock)
{
/*
  UnLock(lock);
*/
}

void
free_all_locks()
{
  int i;
  for (i = 0; i < lockpt; i++)
    UnLock(locklist[i]);
}

void
myputs(char *s)
{
  Write(Output(), s, strlen(s));
}

void
Read_Dirs(char *dir, FILE *f)
{
  BPTR lock;
  struct FileInfoBlock info, *fib;

  lock = Lock(dir, ACCESS_READ);
  if (lock == 0) {
    fprintf(stderr,"*** Yup! Couldn't lock %s\n", dir);
    return;
  }
  
  fib = (struct FileInfoBlock *) Examine(lock, &info);
  fib = &info;
  while (1) {
    if (ExNext(lock, fib) == 0) break;
    if (fib -> fib_DirEntryType > 0) {
      char t[50],c;
      strcpy(t, dir);
      if ((c = t[strlen(t) - 1]) != ':' && c != '/')
        strcat(t,"/");
      strcat(t, fib -> fib_FileName);
      fprintf(f, "%s\n", t);
      Read_Dirs(t,f);
    }
  }
  unlock(lock);
}

void
Update(char **dirname, int ndirs)
/* Update the config file with the list of dirs (dirname) of length ndirs */
{
  FILE *f = fopen(CCDCONFIGFILE,"w");
  int i;

  if (f == NULL) {
    fprintf(stderr, "*** Couldn't open '%s', maybe an assign is missing?\n", CCDCONFIGFILE);
    return(0);
  } 

  Put_Version(f);
  printf("Updating %s with", CCDCONFIGFILE);
  for (i = 0; i < ndirs; i++) {
    printf(" %s", *dirname);
    fflush(stdout);
    fprintf(f, "%s\n", *dirname);
    Read_Dirs(*dirname++, f);
  }
  printf(" -- done!\n");
  fclose(f);
}

int
mystricmp(char *bigdir, char *shortdir)
/* Check if shortdir is part of the path bigdir, return 0 if it is */
{
  char t[50], *pt = t, *ps;
  int end = 0;
  int i = 0;

  while (*bigdir) {
    while (*bigdir && *bigdir != ':' && *bigdir != '/' && *bigdir != '\n')
      *pt++ = *bigdir++;
    if (*bigdir) bigdir++;
    *pt++ = '\0';
    pt = t;
  }

/* Now, we must see if shortdir is included in t */

  for (i = 0; i <= strlen(t) - strlen(shortdir); i++) {
    pt = &t[i]; ps = shortdir;
    while (*pt && *ps && (*pt | 0x20) == (*ps | 0x20)) {
      ps++; pt++;
    };
    if (*pt == 0 || *ps == 0) return(0);   /* match! */
  }
  return(1);
}

void
Update_Prompt(char *currentdir)
/* Update the concerned field with the new current dir                 */
/* This routine is for users of wshell or such, thar display this name */
/* as the shell prompt.                                                */
/* This trick was previously pointed to me by Henry J. Cobb on Usenet  */
/* for my 'find' program (another great utility of mine :-)).          */
/* Let him be thanked again!                                           */
{
  struct Process *pr = (struct Process *) FindTask(0L);
  struct CommandLineInterface *cli =
    (struct CommandLineInterface *) (pr -> pr_CLI) << 2;
  char *p = (char *) (cli -> cli_SetName) << 2;

  *p++ = strlen(currentdir);          /* it is a BSTR, so length first */
  while (*currentdir) *p++ = *currentdir++;  /* don't add '\0' */
}

int
Check_Version(FILE *f)
{
   char v[100];

   fscanf(f, "%s\n", v);
   if (! isdigit(v[0]) || atoi(v) < atoi(VERSION)) {
      printf("*** config file is obsolete for this version, run ccd -u\n");
      fclose(f);
      exit(0);
   }
}

int
Put_Version(FILE *f)
{
   fprintf(f, "%s\n", VERSION);
}

char *
locate_dir(char *dir, FILE *f)
/* Return the complete path for the fragment given, if any, in file *f */
{
  int match = 0;
  static char onedir[100];

  while (! feof(f) && ! match) {
    int i;
    fgets(onedir, 100, f);
    i = strlen(onedir) - 1;
    if (onedir[i] == '\n')
      onedir[i] = '\0';     /* strip trailing \n */
    if (mystricmp(onedir, dir) == 0) {
      match = 1;
      break;
    }
  }
  if (match) return(onedir);
  else return(NULL);
}

char *
reverse(char *s)
/* Return the string s backwards (modify s)*/
{
  register char c;
  char *result = s + strlen(s) - 1, *r2 = s;

  while (result > s) {
    c = *result;
    *result = *s;
    *s = c;
    s++; result--;
  }
  return(r2);
}

void
Get_Real_Name(BPTR lock, char *result)
/* This is a very useful function! Put in result path of lock */
/* Assume lock is not null */
/* Result always device:dir/dir/...   */
{
  char *p = result;
  struct FileInfoBlock ib;  

  assert(lock);

  if (no_expand) return;
  Examine(lock, &ib);
  strcpy(result, reverse(ib.fib_FileName));
  strcat(result, "/");
  while (lock) {
    lock = ParentDir(lock);
    if (lock) {
      Examine(lock, &ib);
      strcat(result, reverse(ib.fib_FileName));
      strcat(result,"/");
      unlock(lock);
    }
  }
  p = &p[strlen(p) - 1];
  *p-- = '\0';
  while (*p != '/') p--;
  *p = ':';
  reverse(result);
}

void
Call_CurrentDir(char *actualdir)
{
   BPTR lock;

   if (actualdir[0]) {
      lock = Lock(actualdir, ACCESS_READ);
      if (lock == 0) {
         fprintf(stderr,
      "*** Couldn't cd to %s, '%s' probably outdated (re-run ccd -u)\n",
            actualdir, CCDCONFIGFILE);
         exit(0);
      }
      CurrentDir(lock);
      Get_Real_Name(lock, actualdir);
      printf("Current directory is now %s\n", actualdir);
      Update_Prompt(actualdir);
      unlock(lock);
  }
  else
    printf("%s: no such dir\n", actualdir);
}

void
Change_Dir(char *dir, int occ)
/* The main function to change to the fragment of dir given,  */
/* to the occ'th occurence found in the config file */
/* New 1.3: first, try to cd right into 'dir' */
{
  char cmd[50];
  FILE *f = fopen(CCDCONFIGFILE,"r");
  int match = 0;
  BPTR lock;

  Check_Version(f);
  strcpy(actualdir, dir);
  if ((lock = Lock(actualdir, ACCESS_READ))) {   /* can we cd directly? */
    BPTR old;
    Get_Real_Name(lock, actualdir);
    lock = get_a_lock(actualdir, ACCESS_READ);
    printf("Current directory is now %s\n", actualdir);
    Update_Prompt(actualdir);
    old = CurrentDir(lock);                     /* yes! Do it and end */
/*
    UnLock(lock);
    UnLock(old);
*/
    return;
  }

                           /* no, dir is a path fragment */
   if (f == NULL) {
     fprintf(stderr,
        "*** Couldn't open '%s'; run ccd -u first, will you?\n", CCDCONFIGFILE);
     exit(0);
   };

   while (occ--) {
     strcpy(actualdir, locate_dir(dir, f));
   }

   Call_CurrentDir(actualdir);

  fclose(f);
}

void
Show_Ambiguities(char *dir)
{
  char *onedir = NULL;
  char cmd[50];
  FILE *f = fopen(CCDCONFIGFILE,"r");
  int match = 0;

  if (f == NULL) {
    fprintf(stderr,
       "*** Couldn't open '%s'; run ccd -u first, will you?\n", CCDCONFIGFILE);
    exit(0);
  };

  Check_Version(f);
  while ((onedir = locate_dir(dir, f))) {
     myputs(onedir);
     myputs("\n");
  }

  fclose(f);  
}

void
Usage()
{
  myputs("\nccd v2.0,  by [33mCedric Beust[31m    (C) 1991\n\n");
  myputs("    [4mUsage[0m:\n");
  myputs("           ccd                           Brings up the graphics display\n");
  myputs("     or    ccd -u <dir> <dir> ...        Rebuild dir list with dirs\n");
  myputs("     or    ccd -a <partial dir>          Show ambiguities\n");
  myputs("     or    ccd [opt] <partial dir> [n]   Change to this dir, nth occurence\n");
  myputs("             where opt is one of\n");
  myputs("                 -n    No path expansion\n\n");
  exit(0);
}

void
get_vol(char *path, char *vol)
/* String is a line of the .ccdconfig (volume:a/b/c) */
/* Return volume in the variable vol */
{
   char *p = path;
   while (*p && *p != ':') *vol++ = *p++;
   *vol++ = '\0';  
}

int
numberofdirs(char *path)
/* path is 'vol:a/b/c' */
/* Return number of dirs in it (3 here), actually = number of '/' + 1 */
{
   int result = 0;

   while (*path) {
      while (*path && *path != '/') path++;
      if (*path == '/') {
         result++;
         path++;
      }
   }
   return(++result);
}

void
nthdir(char *dir, char *path, int n)
/* Path is 'volume:a/b/c' */
/* Return in dir the nth dir in it (e.g. with n=2, it's b) */
{
   char *dirhead = dir;
   while (*path && *path != ':') path++;
   if (*path == ':') {
     path++;
     while (*path) {
         while (*path && *path != '/') {
            *dir++ = *path++;
         }
         if (*(dir - 1) == '\n') dir--;
         *dir++ = '\0';
         if (*path == '/') path++;
         if (n == 1) return;
         else {
            n--; dir = dirhead;
         }
      }
   }
   else {
      printf("nthdir: problem\n");
      exit(0);
   }
}

struct Tree_t *
new_node(char *s, int line, int x, char *fullname)
{
   struct Tree_t *newnode;

   NEW(newnode, struct Tree_t);
   if (s[strlen(s) - 1] == '\n')
      s[strlen(s) - 1] = '\0';
   strcpy(newnode -> name, s);
   strcpy(newnode -> fullname, fullname);
   newnode -> brother = NULL;
   newnode -> son = NULL;
   newnode -> line = line;
   newnode -> x = x;
   return(newnode);
}

struct Tree_t *
get_line(struct Tree_t *root, int line)
/* Return the struct for line line, searching from root */
{
/*
   printf("get_line with %s, %d\n", root->name, line);
*/
   if (root) {
      if (root -> line == line) return root;
      else if (root -> brother) {
         if (root -> brother -> line <= line)
            return get_line(root -> brother, line);
         else
            return get_line(root -> son, line);
      }
      else
         return get_line(root -> son, line);
   }
   return NULL;
}

void
add_node(char *s, struct Tree_t *root, int line)
/* Add the appropriate node to the tree */
/* Assert root != NULL */
/*
      dh0:   ->    dh1:    ->   dh2:
                    | 
                  Anews
                    |
                   New
*/
{
   char vol[30];
   char dir[30];
   struct Tree_t *newnode;
   int i;
   int x = UPPERX - DELTAX;

   get_vol(s, vol);
   assert(root);
   while (root -> brother && stricmp(root -> name, vol))
      root = root -> brother;

   if (strcmp(root -> name, vol)) {   /* new vol, must create a new node */
      root -> brother = new_node(vol, line, x + DELTAX, s);
      newnode = root -> brother;
   }
   else {
      struct Tree_t *t = root;
      newnode = root;
   }
   assert(newnode);
   assert(stricmp(newnode -> name, vol) == 0);

/* Now, newnode is a node that contains the volume. Create the path */

   for (i = 1; i <= numberofdirs(s); i++) {             
      x = newnode -> x;
      nthdir(dir, s, i);
      if (newnode -> son) {
         root = newnode -> son;     /* get down one level */

/* Search amid the brothers if we have a matching name */
         while (root -> brother && stricmp(dir, root -> name)) {
            root = root -> brother;
         }
         if (stricmp(root -> name, dir)) {    /* new dir, create new node */
            newnode = root -> brother = new_node(dir, line, x + DELTAX, s);
         }
         else {
            newnode = root;
         }
      }
      else {    /* no son yet, have to create one */
         newnode -> son = new_node(dir, line, x + DELTAX, s);
         newnode = newnode -> son;
      }

/* Now, newnode is a node that contains how far we've got in the path */
/* Let's go on */
      root = newnode;
   }
}

struct Tree_t *
Build_Tree()
{
   FILE *f;
   char string[128];
   char currentvol[30];
   struct Tree_t *result;

   f = fopen(CCDCONFIGFILE, "r");
   if (f == NULL) {
     fprintf(stderr,
        "*** Couldn't open '%s'; run ccd -u first, will you?\n", CCDCONFIGFILE);
     exit(0);
   };

   Check_Version(f);
   NEW(result, struct Tree_t);
   strcpy(result -> name, "Root");
   result -> brother = NULL;
   result -> son = NULL;
   result -> line = -1;

   while (! feof(f)) {
      configlines++;
      fgets(string, 128, f);
      if (string[strlen(string) - 1] == '\n')
         string[strlen(string) - 1] = '\0';
      add_node(string, result, configlines);
   }
   return result;
}

void
Find_Dir_Coo(int x, int y, int line)
{
   struct Tree_t *root;

   root = get_line(the_tree, line + (y / DELTAY) - 1);
   Call_CurrentDir(root -> fullname);
}

int
Display_Tree(struct Tree_t *root, int line, int x, int y)
/* Graphically display the tree for the volume 'vol', from the line */
/* line in the ccdconfig file, at coordinates x and y */
/* Return the y coordinate at the end */
{
   int len, i;
   struct RastPort *rp = mainwindow -> RPort;
   int xinit = x;

   root = get_line(the_tree, line);
/*
   printf("For line %d, found %s\n", line, root -> name);
*/
   while (root && y < 200) {
      y += 10;
#ifdef GRAPHICS
      assert(rp);
      Move(rp, root -> x, y);
      Text(rp, root -> name, strlen(root -> name));
#endif
      y = Display_Tree(root -> son, line + 1, x, y);
      root = root -> brother;
   }
   return y;
}

int
Create_Gadgets()
/* Create the Volumes gadgets. Return the id of the first one */
{
   struct Tree_t *t;
   struct Gadget *gad = mainwindow -> FirstGadget, *lastgadget = NULL;
   struct IntuiText *tex;
   int id = 1, result;
   int gady = w1Gadget2.TopEdge;

/* First, transfer the gadget imageries into chip ram */
   chippointer = (USHORT *)
       AllocMem(sizeof(w1ImageData1), MEMF_PUBLIC | MEMF_CHIP);
   if (chippointer == 0) {
      printf("*** Couldn't allocate in chip mem...\n");
      exit(0);
   }
   memcpy(chippointer, w1ImageData1, sizeof(w1ImageData1));
   w1Image1.ImageData = chippointer;

   chippointer2 = (USHORT *)
       AllocMem(sizeof(w1ImageData2), MEMF_PUBLIC | MEMF_CHIP);
   if (chippointer2 == 0) {
      printf("*** Couldn't allocate in chip mem...\n");
      exit(0);
   }
   memcpy(chippointer2, w1ImageData2, sizeof(w1ImageData2));
   w1Image2.ImageData = chippointer2;

   while (gad -> NextGadget) {
      gad = gad -> NextGadget;
   }
   lastgadget = gad;
   result = lastgadget -> GadgetID + 1;
   id = result;
   t = the_tree -> brother;
   while (t) {

/* Dynamically create the gadget, using w1Gadget2 as template */
      gad = (struct Gadget *) malloc(sizeof(*gad));
      memcpy(gad, & w1Gadget2, sizeof(*gad));
      gad -> TopEdge = gady;
      gady += BETWEEN_GADGETS + w1Gadget2.Height;
      gad -> GadgetID = id++;

/* Link the previous gadget to the one we're building up */
      if (lastgadget) lastgadget -> NextGadget = gad;
      lastgadget = gad;
      gad -> NextGadget = NULL;

/* And now create its IntuiText structure, using w1IText1 */
      tex = (struct IntuiText *) malloc(sizeof(*tex));
      memcpy(tex, & w1IText1, sizeof(*tex));
      tex -> IText = (char *) malloc(sizeof(t -> name) + 1);
      strcpy(tex -> IText, t -> name);

/* Link the text to the gadget */
      gad -> GadgetText = tex;

      nbvolumes++;
      t = t -> brother;
   }
   return result;
}

void
Display_Dir(int line)
/* Display the directory of the volume in the window, starting at the */
/* line in the config file */
{
   struct Tree_t *t;
   char dir[128];
   struct Window *w = mainwindow;
   struct Gadget *gad = NULL, *lastgadget;
   struct IntuiText *tex = NULL;
   int gady = w1Gadget2.TopEdge;
   int id = 2;

   assert(mainwindow);

/* Warning!!! Here, I assume the 1st gadget is the scrollbar, so I */
/* start from the second... */
   lastgadget = w -> FirstGadget;
   if (! the_tree) the_tree = Build_Tree();
   dir[0] = '\0';

   actualline = 1;
   SetAPen(w -> RPort, 0);
   RectFill(w -> RPort, UPPERX, UPPERY + 4, WIDTH - 4, HEIGHT - 2);
   SetAPen(w -> RPort, 2);
   (void) Display_Tree(the_tree -> brother, line, UPPERX + DELTAX, UPPERY);
}

int
Locate_Vol(char *vol)
/* Return first line where vol (dh0:, dh1:, ...) appears */
{
   struct Tree_t *t = the_tree -> brother;

   while (t && stricmp(vol, t -> name)) {
      t = t -> brother;
   }
   if (t) return t -> line;
   else assert(0);
}

void
Display_Window()
/* Main routine for the graphic display */
{
   struct IntuiMessage *message;
   long class, code;
   int i, fin = 0;
   int firstline = 1, previousline = 1;
   struct Gadget *g;
   struct PropInfo *info;
   struct Tree_t *highlight;

   IntuitionBase = OpenLibrary("intuition.library", 0);
   GfxBase = OpenLibrary("graphics.library", 0);

#ifdef GRAPHICS
   mainscreen = OpenScreen(& NewScreenStructure);
   LoadRGB4(&mainscreen -> ViewPort, Palette, PaletteColorCount);
   w1NewWindowStructure1.Screen = mainscreen;
   w1NewWindowStructure1.Title = (char *) malloc(sizeof(WINDOWTITLE));
   strcpy(w1NewWindowStructure1.Title, WINDOWTITLE);
   w1Gadget1.NextGadget = NULL;
   mainwindow = OpenWindow(& w1NewWindowStructure1);
#endif
   Display_Dir(1);
#ifdef GRAPHICS
   volumesid = Create_Gadgets();
   info = (struct PropInfo *) w1Gadget1.SpecialInfo;
   info -> VertBody = (0xffff * LINESPERWINDOW) / configlines;
   RefreshGadgets(mainwindow -> FirstGadget, mainwindow, NULL);

   while (! fin) {
      while ((message = (struct IntuiMessage *)
                           GetMsg(mainwindow -> UserPort))) {
         class = message -> Class;
         code = message -> Code;
         ReplyMsg(message);

         switch(class) {
            case CLOSEWINDOW:
               fin = 1;
               break;
            case GADGETUP:
               g = (struct Gadget *) message -> IAddress;
               if (g -> GadgetID >= volumesid) {
                  firstline = Locate_Vol(g -> GadgetText -> IText);
                  w1w1Gadget1SInfo.VertPot = (firstline * 0xffff) / configlines;
               }
               else
                  firstline = (configlines * w1w1Gadget1SInfo.VertPot) / 0xffff;

/* Take care of trunc problems */
               if (firstline == previousline + LINESPERWINDOW + 1 ||
                   firstline == previousline + LINESPERWINDOW - 1)
                      firstline = previousline + LINESPERWINDOW;
               else
               if (firstline == previousline - LINESPERWINDOW + 1 ||
                   firstline == previousline - LINESPERWINDOW - 1)
                       firstline = previousline - LINESPERWINDOW;

               if (firstline == 0) firstline = 1;
               if (firstline > configlines - LINESPERWINDOW - 1)
                  firstline = configlines - LINESPERWINDOW - 1;
               Display_Dir(firstline);
               RefreshGadgets(mainwindow -> FirstGadget, mainwindow, NULL);
               break;

            case GADGETDOWN:
               printf("Gadgetdown, class=%d, code=%d\n", class, code);
               g = (struct Gadget *) message -> IAddress;
               printf("Gadgetdown, gadget id: %d\n", g -> GadgetID);
               break;
            case MOUSEBUTTONS:
               if (message -> MouseX >= UPPERX && message -> MouseY>=UPPERY
                   && code == SELECTDOWN) {
                  Find_Dir_Coo(message -> MouseX, message -> MouseY, firstline);
                  fin = 1;
               }
               break;
            case MOUSEMOVE:
               printf("Mousemove\n");
               highlight = get_line(the_tree, firstline + (message -> MouseY / DELTAY) - 1);
               SetAPen(mainwindow -> RPort, 1);
               Move(mainwindow -> RPort, highlight -> x, message -> MouseY);
               Text(mainwindow -> RPort, highlight -> name, strlen(highlight -> name));
               break;
            default:
               printf("Class=%d, Code = %d\n", class, code);
               break;
         } /* switch class */
      } /* while message */
   }
   if (mainwindow) CloseWindow(mainwindow);
   if (mainscreen) CloseScreen(mainscreen);
#endif
}

int
main(int argc, char **argv)
{
  int i = 1;

  if (argc == 1) {
      Display_Window();
      exit(0);
  }
  while (argv[i][0] == '-') {
    switch(argv[i][1]) {
      case 'u' :         /* update config file */
        if (argc == i + 1) Usage();
        update_list = 1;
        break;
      case 'a' :         /* show ambiguities */
        if (argc == i) Usage();
        show_ambiguities = 1;
        break;
      case 'n' :
         if (argc == i) Usage();
         no_expand = 1;
         break;
      default :
         Usage();
    } /* switch */
    i++;
  } /* while */

  if (update_list)
     Update(&argv[i], argc - 2);
  else if (show_ambiguities) {
     printf("Ambiguities for %s\n", argv[i]);
     Show_Ambiguities(argv[i]);
  }
  else
     Change_Dir(argv[i], (argc == i + 2 ? atoi(argv[i+1]) : 1));

/*
  free_all_locks();
*/
  if (chippointer) FreeMem(chippointer, sizeof(w1ImageData1));
  if (chippointer2) FreeMem(chippointer2, sizeof(w1ImageData2));
  exit(0);
}

