/*  036  8-Jun-87  ovdir.c

        Copyright (c) 1987 by Blue Sky Software.  All rights reserved.
*/

#include <stdio.h>
#include "ov.h"
#include "overr.h"
#include "menu.h"
#include "direct.h"
#include "strmem.h"
#include "dosfile.h"

#define DCOLSIZ 15
#define DCOLS (SCREEN_COLS / DCOLSIZ)

#define dr2sr(r) (FIRST_NROW + (r - drbase))
#define dc2sc(c) ((c - dcbase) * DCOLSIZ)

#define TO_NONE 0
#define TO_SUBDIR 1
#define TO_SIBLING 2
#define TO_PARENT 3
#define TO_ROOT 4

char *strrchr(), *strupr();
struct search_block *nxtfile();

struct dir_ent {
   struct dir_ent *subdir;
   struct dir_ent *sibling;
   struct dir_ent *parent;
   struct dir_ent *prev_sib;
   char name[13];
   unsigned char row;
   unsigned char col;
};

static last_drive = ' ';
static char dirpath[MAX_PATHLEN+6];
static int drbase, drend, dcbase, dcend;
static struct dir_ent *curdir, *logdir, *findir(), *dir_next();
static struct dir_ent root = { NULL, NULL, NULL, NULL, "", 0, 0 };

int dir_exit(), dir_login(), dir_mkdir(), dir_rmdir(), dir_new();
extern MENU top_file_menu[], *top_menu;

MENU top_dir_menu[] = {
   { "Login", "Login (switch) to the highlighted directory", dir_login, NULL },
   { "Mkdir", "Make a subdirectory of the highlighted directory", dir_mkdir, NULL },
   { "New", "Reread and redisplay directory tree", dir_new, NULL },
   { "Rmdir", "Remove (delete) the highlighted directory", dir_rmdir, NULL },
   { "Quit", "Return to file display", dir_exit, top_file_menu },
   { NULL, NULL, NULL, NULL }
};

extern WINDOW cw;
extern unsigned char dir_display, restricted;
char *strchr();


/******************************************************************************
 **                            D T R E E                                     **
 *****************************************************************************/

dtree() {              /* display / work with directory tree */

   int drive;

   dir_display = TRUE;                 /* dir tree is (will be) displayed */
   restricted = TRUE;                  /* disable some file commands */

   /* scan the current disk and create the internal directory tree if it
      hasn't already been built or the user has changed disks */

   if ((drive = current_drive()) != last_drive) {  /* has the drive switched? */

      if (root.subdir) {                       /* delete current tree */
         del_dtree(root.subdir);               /* if drive switch */
         root.subdir = NULL;
      }

      last_drive = drive;

      *dirpath = '\0';                         /* scan_dir() starts at */
      strncat(dirpath,cw.dirbuf,3);            /*   drives root dir */
      strcpy(root.name,dirpath);

      disp_msg(1,"Scanning disk");             /* takes awhile, tell user */

      scan_dir(&root);                         /* build new dir tree */
      dir_mark();                              /* assign row/col's to ent's */

      clr_msg();                               /* done scanning */

      drbase = dcbase = 0;                     /* assume dir */
      drend = NAME_ROWS;                         /* will start */
      dcend = DCOLS;                               /* at the root */
   }

   curdir = findir();                  /* locate current dir in tree */

   logdir = curdir;                    /* remember the logged dir */

   /* now display the current portion of the dir tree */

   adj_dir_dis();                      /* make sure logged dir will show */
   dir_distree();                      /* let user see it */

   top_menu = top_dir_menu;    /* setup the dir menu as the main menu */
}


/******************************************************************************
                              D I R _ N E W
 *****************************************************************************/

dir_new() {            /* rescan the disk and redisplay the dir tree */

   last_drive = ' ';   /* simply force a rescan */
   dtree();            /*   and let dtree() do the work */
   update_vol_stats(); /* in case its a new volume */
}


/******************************************************************************
 **                       D I R _ E X I T                                    **
 *****************************************************************************/

dir_exit() {           /* exit the dir display, return to file display */

   dir_display = FALSE;                /* dir tree will not be displayed */
   restricted = FALSE;                 /* allow all file commands */

   top_menu = top_file_menu;           /* file menu is main again */

   update_header();                    /* always rewrite the entire screen */
   refresh_screen(0);                  /* 'cause there may be > windows */
}


/******************************************************************************
 **                         S C A N _ D I R                                  **
 *****************************************************************************/

scan_dir(dp)           /* scan the specified dir tree for other directories */
struct dir_ent *dp;
{
   int dplen;
   int firsttime = TRUE;
   struct search_block *sbp;
   register struct dir_ent *ndp, *ldp = NULL;

   /* build the pathname of the dir to scan */

   dplen = strlen(dirpath);                    /* remember callers length */
   if (strcmp(dp->name+2,"\\") != 0) {         /* special case if root dir */
      strcat(dirpath,dp->name);                /* add name of dir to scan */
      strcat(dirpath,"\\");
   }
   strcat(dirpath,"*.*");                      /* add wildcard string */

   /* scan all files in directory looking for subdirectories.  When a
      subdirectory is found, add it to the dir_ent tree.  Note, the . and
      .. directory entries are ignored. */

   while (sbp = nxtfile(dirpath,0x16,&firsttime))
      if (sbp->attrib & DIR && *sbp->fn != '.') {

         /* found a subdir we want, build a struct dir_ent for it */
         ndp = (struct dir_ent *) Malloc(sizeof(struct dir_ent));
         strcpy(ndp->name,sbp->fn);
         ndp->subdir = NULL;
         ndp->sibling = NULL;
         ndp->parent = dp;
         ndp->prev_sib = ldp;

         /* now link it to the dir_ent tree either as a subdir of the
            parent (1st one only) or a sibling of the last one */

         if (ldp)
            ldp->sibling = ndp;        /* not 1st, is a sibling */
         else
            dp->subdir = ndp;          /* 1st one, subdir of parent */

         ldp = ndp;                    /* new one is now the last one */
      }

   /* if any subdirectories were found, scan 'em.  This isn't done
      earlier so the file search isn't complicated by the directory
      switches. */

   if (ldp) {                            /* NULL if no sub's found */
      dirpath[strlen(dirpath)-3] = '\0'; /* remove *.* for next level */
      ldp = dp->subdir;                  /* start with the first one */
      do {
         scan_dir(ldp);                /* call ourselves to scan this subtree */
      } while (ldp = ldp->sibling);    /* do all the subs found */
   }

   dirpath[dplen] = '\0';              /* restore dir pathname for caller */
}


/******************************************************************************
 **                           D E L _ D T R E E                              **
 *****************************************************************************/

del_dtree(dp)          /* purge the current in memory dir tree structure */
register struct dir_ent *dp;
{

   register struct dir_ent *ndp;

   /* delete this subdirectory ENTRY and all sibling ENTRIES (not the
      actual directories) */

   do {

      if (dp->subdir)            /* if it has a subdir, make a recursive call */
         del_dtree(dp->subdir);  /* to delete the substructure */

      ndp = dp->sibling;         /* get address of any sibling */

      free((char *)dp);          /* free the dir_ent space itself */

   } while (dp = ndp);           /* do until no more siblings */

}


/******************************************************************************
                           D I R _ D I S T R E E
 *****************************************************************************/

static int
dir_distree() {        /* display the dir tree */

   register int drow;
   int i, ldrow = -1;
   register struct dir_ent *dp = &root;

   do {

      drow = dp->row;          /* speed things up a little */

      /* if drow != ldrow, then this is the first entry on this display
         row, clear the line to remove any old junk that might be there */

      if (drow != ldrow && drow >= drbase && drow < drend) {
         gotorc(dr2sr(drow),dc2sc(dcbase));
         clr_eol();
         ldrow = drow;
      }

      /* see if its necessary to put some spacer bars in from the previous
         sibling.  This will be necessary if the prev_sib had a desendent
         with siblings because the siblings will occupy display lines
         between this ent's prev_sib and where this ent goes. */

      if (dp->prev_sib && (i = drow - dp->prev_sib->row - 1) > 0)
         for (; i; i--)
            if (dir_on_screen(drow-i,dp->col))
               disp_char_at(0xb3,dr2sr(drow-i),dc2sc(dp->col));

      /* only display a dir entry if it falls within the boundries, figure
         out if this one does and display the name if so - the dir under
         the pointer is highlighted, the logged dir is 'tagged' all others
         are displayed 'normal' */

      if (dir_on_screen(drow,dp->col))
         disp_dir_name(dp,dp == curdir ? DIS_HIGH :
                       (dp == logdir ? DIS_TAGD : DIS_NORM));

   } while (dp = dir_next(dp,&i,&i));

   /* if we didn't reach the bottom of the display area, clear out the rest
      of the lines to remove any garbage that might be there */

   drow++;                             /* next would be display row */
   if (drow - drbase < NAME_ROWS)
      for (; drow - drbase < NAME_ROWS; drow++) {
         gotorc(dr2sr(drow),dc2sc(dcbase));
         clr_eol();
      }
}


/******************************************************************************
 **                    D I S P _ D I R _ N A M E                             **
 *****************************************************************************/

disp_dir_name(dp,va)   /* display a single dir name */
register struct dir_ent *dp;
int va;
{
   gotorc(dr2sr(dp->row),dc2sc(dp->col));      /* move to display location */

   /* display the leadin bar to the dir name, it depends on whether
      this is the root, the 1st subdir, the last sibling, and if any
      siblings follow */

   if (dp->col) {                                 /* root dir? */
      if (dp->prev_sib == NULL)                   /* 1st subdir? */
         disp_char((dp->sibling) ? 0xc2 : 0xc4);  /* siblings follow? */
      else
         disp_char((dp->sibling) ? 0xc3 : 0xc0);
   } else
      disp_char(' ');                          /* root - no leadin */

   if (va != DIS_NORM)                         /* setup highlight if needed */
      setvattrib(va);

   disp_char(' ');
   disp_str(dp->name);                         /* display the name */
   disp_char(' ');

   /* draw bar to subdir or blank fill name */

   disp_rep(dp->subdir ? 0xc4 : ' ',DCOLSIZ - strlen(dp->name) - 3);

   if (va != DIS_NORM)         /* restore normal attribute if changed */
      setvattrib(DIS_NORM);
}


/*****************************************************************************
                             D I R _ M A R K
 *****************************************************************************/

static int
dir_mark() {   /* mark dir_ent's with logical row/column where they display */

   register struct dir_ent *dp;
   int nxtype, backup, drow, dcol;

   dp = &root;
   drow = dcol = 0;

   /* scan the dir_ent tree in order */

   while (dp = dir_next(dp,&nxtype,&backup)) {
      if (nxtype) {                            /* NZ if dp is a sibling */
         drow++;                               /* siblings go down a row */
         dcol -= backup;                       /* and maybe back some */
      } else
         dcol++;                               /* sub dir's go over a column */
      dp->row = drow;                          /* assign row/col to entry */
      dp->col = dcol;
   }
}


/******************************************************************************
                               D I R _ N E X T
 ******************************************************************************/

static struct dir_ent *
dir_next(dp,tp,bp)     /* return address of next dir_ent */
register struct dir_ent *dp;
int *tp, *bp;
{
   *bp = 0;                    /* assume we will not backup to parent */

   if (dp->subdir) {           /* subdir is next if there is one */
      *tp = 0;                 /* tell caller its a sub dir */
      return(dp->subdir);
   }

   *tp = 1;                    /* assume a sibling will be found */

   if (dp->sibling)            /* a sibling is next if there is one */
      return(dp->sibling);

   /* keep backing up until a parent is found with a sibling or root */

   while ((dp = dp->parent) && dp != &root) {
      (*bp)++;                         /* tell caller # levels backed up */
      if (dp->sibling)
         return(dp->sibling);
   }

   return(NULL);               /* no more dir ents to be found */
}


/******************************************************************************
                               D I R _ M O V E
 *****************************************************************************/

dir_move(move_cmd)     /* move the directory pointer around */
int move_cmd;
{
   int moved = TO_NONE;
   int redisplayed = FALSE;
   register struct dir_ent *last_dir, *cdp;

   last_dir = cdp = curdir;    /* remember where we are/were and fast ptr */

   switch (move_cmd) {

      case RIGHT:
         if (cdp->subdir) {
            cdp = cdp->subdir;
            moved = TO_SUBDIR;
         } else
            if (cdp->sibling) {
               cdp = cdp->sibling;
               moved = TO_SIBLING;
            }
            /* stuff about parents goes here */
         break;

      case LEFT:
         if (cdp->prev_sib) {
            cdp = cdp->prev_sib;
            moved = TO_SIBLING;
         } else
            if (cdp->parent) {
               cdp = cdp->parent;
               moved = TO_PARENT;
            }
         break;

      case UP:
         if (cdp->prev_sib) {
            cdp = cdp->prev_sib;
            moved = TO_SIBLING;
         }
         break;

      case DOWN:
         if (cdp->sibling) {
            cdp = cdp->sibling;
            moved = TO_SIBLING;
         }
         break;

      case HOME:
         cdp = &root;
         moved = TO_ROOT;
         break;

      case GOPAR:
         if (cdp->parent) {
            cdp = cdp->parent;
            moved = TO_PARENT;
         }
      break;

   }

   curdir = cdp;           /* assign it */

   /* adjust and redisplay the pathname as the user moves the dir pointer */

   if (moved != TO_NONE)
      update_dirpath(moved);

   /* display a different section of dir tree if current isn't displayed */

   if (adj_dir_dis()) {                /* is adjustment needed? */
      dir_distree();                   /* display the tree */
      redisplayed = TRUE;              /* save a disp_dir_name() call */
   }

   /* deselect the last dir if the dir pointer moved and the last one
      is still on the screen and the entire tree wasn't redisplayed */

   if (!redisplayed && last_dir != curdir &&
       dir_on_screen(last_dir->row,last_dir->col))
      disp_dir_name(last_dir,last_dir == logdir ? DIS_TAGD : DIS_NORM);

   /* select (highlight) a new dir entry if pointer moved and the tree
      wasn't redisplayed */

   if (last_dir != curdir && !redisplayed) {
      disp_dir_name(curdir,DIS_HIGH);
   }
}


/******************************************************************************
 **                       D I R _ L O G I N                                  **
 *****************************************************************************/

dir_login() {          /* login the dir selected by the dir pointer */

   /* switch to selected dir, if can't switch, user may have changed
      disks, force a scan of the disk for dirs */

   if (switch_dir(dirpath) != 0)       /* switch to the selected dir */
      last_drive = ' ';

   dir_exit();                         /* exit the dir display mode */

}


/******************************************************************************
 **                       D I R _ R M D I R                                  **
 *****************************************************************************/

dir_rmdir() {          /* remove the dir selected by the dir pointer */

   int moveto;
   register struct dir_ent *cdp;

   cdp = curdir;               /* try to reduce code size */

   /* don't even let user try to delete the root or current directories */

   if (cdp->col == 0 || strcmp(dirpath,cw.dirbuf) == 0)
      show_error(0,CANT_RMDIR,3,"You can't delete the ",(cdp->col == 0) ? "root" :
                "current"," directory!");

   /* use DOS to acutally remove the directory */

   if (rmdir(dirpath) != 0)
      show_error(SHOW_DOS,CANT_RMDIR,1,"Unable to remove dir: ");

   /* Okay, the dir is gone, now remove it from the dir tree */

   if (cdp->prev_sib)                                  /* make prev sib -> */
      cdp->prev_sib->sibling = cdp->sibling;           /* next sib */

   if (cdp->sibling) {                                 /* next sib -> */
      cdp->sibling->prev_sib = cdp->prev_sib;          /* prev sib && */
      if (cdp->parent->subdir == cdp)                  /* parent ->   */
         cdp->parent->subdir = cdp->sibling;           /* next sib    */
   }

   if (cdp->prev_sib == NULL && cdp->sibling == NULL)  /* parent has no */
      cdp->parent->subdir = NULL;                      /* more sub's */

   /* when deleting a subdirectory, try to make a sibling the current dir,
      if none, go back to parent */

   if (cdp->sibling) {
      curdir = cdp->sibling;
      moveto = TO_SIBLING;
   } else
      if (cdp->prev_sib) {
         curdir = cdp->prev_sib;
         moveto = TO_SIBLING;
      } else {
         curdir = cdp->parent;
         moveto = TO_PARENT;
      }

   update_vol_stats();         /* should be more free space now */

   update_dirpath(moveto);     /* show user what the dir pathname is */

   free((char *)cdp);          /* release mem used by the dir entry */

   dir_mark();         /* reassign row/column values for remaining dir's */
   adj_dir_dis();      /* make sure the current dir will display */
   dir_distree();      /* redisplay updated dir tree */
}


/******************************************************************************
 **                       D I R _ M K D I R                                  **
 *****************************************************************************/

dir_mkdir() {          /* make a subdirectory in the selected dir */

   int rc;
   char *name, *dirend;
   register struct dir_ent *ndp, *ldp;

   /* ask user what to call the new directory */

   name = strupr(prompt(NULL,"Enter the new subdirectory name: ",NULL,0,12));
   if (strlen(name) == 0)
      return;

   /* update dirpath to include the users new name */

   dirend = dirpath + strlen(dirpath); /* remember current dirpath end */
   if (*(dirend-1) != '\\')
      strcat(dirend,"\\");
   strcat(dirend,name);                /* add users dir name */

   /* use DOS to acutally make the directory */

   rc = mkdir(dirpath);                /* create it */

   *dirend = '\0';                     /* fixup dirpath */

   if (rc != 0)
      show_error(SHOW_DOS,CANT_MKDIR,1,"Unable to make dir: ");

   /* Okay, the dir is created, now make a dir_ent and add it to the dir tree */

   ndp = (struct dir_ent *) Malloc(sizeof(struct dir_ent));
   strcpy(ndp->name,name);
   ndp->subdir = NULL;
   ndp->sibling = NULL;
   ndp->parent = curdir;

   if (ldp = curdir->subdir) {                 /* any sibs to new dir? */
      while (ldp->sibling)                     /* find end of sib list */
         ldp = ldp->sibling;
      ndp->prev_sib = ldp;                     /* new one is last */
      ldp->sibling = ndp;
   } else {
      ndp->prev_sib = NULL;                    /* no sibs */
      curdir->subdir = ndp;                    /* parent now has subdir */
   }

   update_vol_stats();                 /* less free space now */

   dir_mark();         /* assign new row/col values */
   dir_distree();      /* redisplay dir tree */
}


/******************************************************************************
 **                    U P D A T E _ D I R P A T H                           **
 *****************************************************************************/

static int
update_dirpath(moved)          /* update & redisplay dir path */
int moved;
{
   char *cp;

   switch (moved) {

      case TO_SUBDIR:                  /* moved to a subdir */
         if (curdir->col != 1)         /* append a \ if parent wasn't root */
            strcat(dirpath,"\\");
         strcat(dirpath,curdir->name); /* append the subdirectory name */
         break;

      case TO_SIBLING:                 /* moved to a sibling */
         *(strrchr(dirpath,'\\')+1) = '\0';
         strcat(dirpath,curdir->name);
         break;

      case TO_PARENT:                  /* moved to parent */
         cp = strrchr(dirpath,'\\');
         if (curdir->col == 0)
            ++cp;
         *cp = '\0';
         break;

      case TO_ROOT:                    /* moved to the root */
        dirpath[3] = '\0';
        break;
   }

   gotorc(VOL_ROW,PATH_COL+1);
   out_str(dirpath,65,' ');
}


/*****************************************************************************
                          A D J _ D I R _ D I S
 *****************************************************************************/

adj_dir_dis() {        /* make sure current dir ent will be on screen */

   register int r, c;

   /* adjust bounds and redisplay dir tree if the current entry is not
      displayed */

   r = curdir->row;  c = curdir->col;  /* cutout a level of indirection */

   if (!dir_on_screen(r,c)) {

      if (r >= drend)                  /* current below display? */
         drbase = r - NAME_ROWS + 1;
      else
         if (r < drbase)               /* current above display? */
            drbase = r;

      if (c >= dcend)                  /* current right of display? */
         dcbase = c - DCOLS + 1;
      else
         if (c < dcbase)               /* current left of display? */
            dcbase = c;

      drend = drbase + NAME_ROWS;      /* reset display end markers */
      dcend = dcbase + DCOLS;

      return(1);                       /* tell caller to redisplay */
   }

   return(0);                          /* no adjustment needed */
}


/*****************************************************************************
                             F I N D I R
 *****************************************************************************/

static struct dir_ent *
findir() {             /* find the current dir_ent */

   register char *lcp;
   char *cp = cw.dirbuf+3;
   char dirname[MAX_NAMELEN+2];
   register struct dir_ent *dp = &root;

   *dirpath = '\0';
   strncat(dirpath,cw.dirbuf,3);       /* roots better be the same */

   while (strcmp(dirpath,cw.dirbuf) != 0) {    /* have we found it yet? */

      /* haven't found it yet, go down another dir level, also do some
         protective error checks, I don't feel real secure 'bout this yet */

      if (dp == NULL || (dp = dp->subdir) == NULL || (lcp = cp) == NULL)
         return(&root);                        /* shouldn't happen, but... */

      if (cp = strchr(cp+1,'\\')) {            /* isolate the next dir level */
         *dirname = '\0';                      /*   in cw.dirbuf */
         strncat(dirname,lcp,cp-lcp);
      } else
         strcpy(dirname,lcp);                  /* this must be the last level */

      strcat(dirpath,dirname);                 /* add dir name to full path */

      if (*(lcp = dirname) == '\\')            /* 1st doesn't have leading \ */
         lcp++;                                /*   others do */

      /* check all siblings at this level until we find it (or run out?) */

      while (dp && strcmp(lcp,dp->name) != 0)
         dp = dp->sibling;
   }

   return(dp);                 /* this should be the one */
}


/*****************************************************************************
                        D I R _ O N _ S C R E E N
 *****************************************************************************/

static int
dir_on_screen(r,c)     /* determine if dir name is displayed */
register int r, c;
{
   return(r >= drbase && r < drend && c >= dcbase && c < dcend);
}
