/*
 *  PushD, PopD, Dirs - Directory stack commands.
 *  Copyright (C) 1994 Torsten Poulin
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *             The author can be contacted by mail at
 *               Torsten Poulin
 *               Banebrinken 99, 2, 77
 *               DK-2400 Copenhagen NV
 *               Denmark
 *             or via email: torsten@diku.dk
 *
 * $Id: PushD.c 37.1 1994/06/13 22:00:48 torsten Exp $
 * $Log: PushD.c $
 * Revision 37.1  1994/06/13  22:00:48  torsten
 * Initial revision.
 *
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#ifdef __SASC
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#endif

#include <string.h>
#include "pushd-popd-dirs_rev.h"


char const versionID[] = VERSTAG;
char const copyright[] = "$COPYRIGHT:©1994 Torsten Poulin$";

#define ACTION_QUEUE 2003L
#define DOSBase      g->dosbase
#define NAMELEN      256
#define SENTINEL     '~'

/*
 *  This one is not declared const to avoid warnings :-)
 */
char stack_var[] = "DStack";

typedef enum { unknown = -1, pushd, popd, dirs } identity;

typedef struct {
  struct Library *dosbase;
  struct FileInfoBlock fib;
  union {
    struct { STRPTR dir; LONG *top; } a_pushd;
    struct { LONG *discard; } a_popd;
    struct { LONG numbers; } a_dirs;
  } args;
  UBYTE program[108]; /* as in a struct FileInfoBlock */
  UBYTE name[NAMELEN+1];
  identity WeAre;
} Global;


static VOID WhatAreWe(Global *g);
static LONG parseargs(Global *);
static LONG PushD(Global *);
static LONG PopD(Global *);
static LONG Dirs(Global *);
static LONG changedir(STRPTR, Global *);
static LONG insertdir(STRPTR, Global *);
static LONG retrievedir(LONG, Global *);
static LONG updatestack(STRPTR, LONG, Global *);
static VOID PutULong(ULONG, Global *);


LONG __saveds
entrypoint(VOID)
{
  LONG rc = RETURN_OK;
  Global *g;

  if (g = AllocVec(sizeof(Global), MEMF_ANY | MEMF_CLEAR)) {
    if (DOSBase = OpenLibrary("dos.library", 37L)) {

      WhatAreWe(g);
      rc = parseargs(g);

      if (rc > RETURN_WARN) PrintFault(IoErr(), g->program);
      CloseLibrary((struct Library *) DOSBase);
    }
    else rc = RETURN_FAIL;
    FreeVec(g);
  }
  else rc = RETURN_FAIL;
  return rc;
}


static VOID WhatAreWe(Global *g)
{
  UBYTE *prog;

  g->WeAre = unknown;
  if (GetProgramName(g->name, NAMELEN)) {
    prog = FilePart(g->name);
    strcpy(g->program, prog);
    if (strcmpi(prog, "pushd") == 0) g->WeAre = pushd;
    else if (strcmpi(prog, "popd") == 0) g->WeAre = popd;
    else if (strcmpi(prog, "dirs") == 0) g->WeAre = dirs;
  }
}


static LONG parseargs(Global *g)
{
  struct RDArgs *args = NULL;
  LONG rc = RETURN_OK;

  switch (g->WeAre) {
  case unknown:
    PutStr("Unknown command\n");
    break;
  case pushd:
    if (args = ReadArgs("DIR,TOP/K/N", (LONG *) &g->args.a_pushd, NULL))
      rc = PushD(g);
    break;
  case popd:
    if (args = ReadArgs("DISCARD/N", (LONG *) &g->args.a_popd, NULL))
      rc = PopD(g);
    break;
  case dirs:
    if (args = ReadArgs("NUM/S", (LONG *) &g->args.a_dirs, NULL))
      rc = Dirs(g);
    break;
  }

  if (args) FreeArgs(args);
  else rc = RETURN_FAIL;

  return rc;
}


/*
 *  PushD command.
 *
 *  Template: DIR,TOP/K/N
 *
 *  If DIR is specified, it is made the current directory
 *  and the old work directory is pushed onto the stack.
 *  Otherwise, the current directory and the one on the
 *  top of the stack are swapped.
 *
 *  TOP extracts an arbitrary element from the stack and
 *  pushes it so it becomes the new current directory.
 *
 *  The two arguments are mutually exclusive.
 */

static LONG PushD(Global *g)
{
  LONG rc = RETURN_OK, element;

  if (g->args.a_pushd.dir && g->args.a_pushd.top) {
    PutStr(g->program);
    PutStr(": DIR and TOP are mutually exclusive\n");
    return RETURN_ERROR;
  }

  if (g->args.a_pushd.dir) {
    /*
     *  We were supplied a name.
     */
    if ((rc = changedir(g->args.a_pushd.dir, g)) == RETURN_OK)
      rc = insertdir(g->name, g);
  }
  else {
    /*
     *  No name. Make the TOPth element the current directory.
     *  If TOP is unspecified, it is treated as TOP=1
     *  (effectively it swaps the current dir and the stack top).
     *  TOP=0 is a no-op, of course.
     */
    if (g->args.a_pushd.top) {
      element = *g->args.a_pushd.top;
      if (element < 0) {
	SetIoErr(ERROR_BAD_NUMBER);
	return RETURN_ERROR;
      }
      else if (element == 0) return RETURN_OK;
    }
    else element = 1;

    if ((rc = retrievedir(element, g)) == RETURN_OK)
      if ((rc = changedir(g->name, g)) == RETURN_OK)
	rc = insertdir(g->name, g);
  }

  return rc;
}


/*
 *  PopD command.
 *
 *  Template: DISCARD/N
 *
 *  Pops the directory stack and makes the popped directory
 *  the current.  DISCARD can be used to remove entries
 *  from the stack.
 */

static LONG PopD(Global *g)
{
  LONG rc = RETURN_OK, element = 0;

  if (g->args.a_popd.discard) {
    element = *g->args.a_popd.discard;
    if (element < 0) {
      SetIoErr(ERROR_BAD_NUMBER);
      return RETURN_ERROR;
    }
  }

  if ((rc = retrievedir(element ? element : 1, g)) == RETURN_OK)
    if (element == 0)
      rc = changedir(g->name, g);

  return rc;
}


/*
 *  Dirs command.
 *
 *  Template: NUM/S
 *
 *  Prints the directory stack to the default output.
 *  If the NUM switch is used, each name is preceded by
 *  its number on the stack. The current directory is
 *  given the number 0 (it's not stored on the stack).
 */

static LONG Dirs(Global *g)
{
  BPTR lock;
  struct LocalVar *dirstack;
  STRPTR s, ds;
  LONG i;
  ULONG dnum = 0;
  LONG rc = RETURN_OK;

  /*
   *  Output the current directory
   */

  if (lock = Lock("", SHARED_LOCK)) {
    if (NameFromLock(lock, g->name, NAMELEN)) {
      if (g->args.a_dirs.numbers) {
	PutULong(dnum++, g);
	PutStr(" ");
      }
      PutStr("\"");
      PutStr(g->name);
      PutStr(g->args.a_dirs.numbers ? "\"\n" : "\"");
    } else rc = RETURN_FAIL;
    UnLock(lock);
  }
  else rc = RETURN_FAIL;

  /*
   *  Output the contents of stack_var using g->name as buffer.
   */
  if (dirstack = FindVar(stack_var, LV_VAR)) {
    s = g->name;
    ds = dirstack->lv_Value;
    for (i = 0; i < dirstack->lv_Len; i++, ds++) {
      if (*ds == SENTINEL) {
	if (g->args.a_dirs.numbers) PutULong(dnum++, g);
	PutStr(" \"");
	*s = '\0';
	PutStr(g->name);
	PutStr("\"");
	if (g->args.a_dirs.numbers) PutStr("\n");
	s = g->name;
      }
      else if (s - g->name < NAMELEN) *s++ = *ds;
      else {
	/*
	 *  This should only happen if something or someone
	 *  has tampered with the stack_var variable.
	 */
	PutStr(g->program);
	PutStr(": directory name too long\n");
	return RETURN_FAIL;
      }
    }
  }

  if (!g->args.a_dirs.numbers) PutStr("\n");

  return rc;
}


/*
 *  Make dirname the new work directory.
 */

static LONG changedir(STRPTR dirname, Global *g)
{
  BPTR lock;
  LONG rc = RETURN_OK;

  if (lock = Lock(dirname, SHARED_LOCK)) {
    if (Examine(lock, &g->fib) && g->fib.fib_DirEntryType > 0) {
      /*
       *  Seems to be a directory ...
       *
       *  Get its full name.
       */
      if (!NameFromLock(lock, g->name, NAMELEN)) rc = RETURN_FAIL;
      else {
	lock = CurrentDir(lock);
	SetCurrentDirName(g->name);
	/*
	 *  Get the full name of the old directory
	 *  so we can push it onto the stack.
	 */
	if (!NameFromLock(lock, g->name, NAMELEN)) rc = RETURN_FAIL;
      }
    }
    else {
      SetIoErr(ERROR_OBJECT_WRONG_TYPE);
      rc = RETURN_FAIL;
    }
    UnLock(lock);
  }
  else rc = RETURN_FAIL;

  return rc;
}


/*
 * Store a directory name in the directory stack.
 */

static LONG insertdir(STRPTR dirname, Global *g)
{
  LONG rc = RETURN_OK;
  LONG namelen = strlen(dirname), length = 0;
  STRPTR contents, oldcontents;
  struct LocalVar *dirstack;

  if (dirstack = FindVar(stack_var, LV_VAR))
    length = dirstack->lv_Len;

  length += namelen + 1;
  
  if (contents = AllocVec(length + 2, MEMF_CLEAR | MEMF_ANY)) {
    if (namelen) {
      strncpy(contents, dirname, namelen);
      contents[namelen] = SENTINEL;
    }
    if (dirstack) {
      oldcontents = &contents[namelen + 1];
      strncpy(oldcontents, dirstack->lv_Value, dirstack->lv_Len);
    }

    if (namelen)
      /*
       *  The directory stack did grow, so store it back.
       *  The above check shouldn't be necessary, as we are
       *  supposedly guaranteed to be called with a name ...
       */
      updatestack(contents, length, g);

    FreeVec(contents);
  }
  else rc = RETURN_FAIL;

  return rc;
}


/*
 *  Retrieve a directory name from the directory stack.
 */

static LONG retrievedir(LONG element, Global *g)
{
  LONG rc = RETURN_OK, i, count = 1;
  BOOL found = FALSE;
  STRPTR name, newcontents, newcptr, cptr;
  struct LocalVar *dirstack;

  if (dirstack = FindVar(stack_var, LV_VAR)) {
    cptr = dirstack->lv_Value;
    if (newcontents = AllocVec(dirstack->lv_Len + 1,
			       MEMF_CLEAR | MEMF_ANY)) {
      name = g->name;
      newcptr = newcontents;
      for (i = 0; i < dirstack->lv_Len; i++, cptr++) {
	if (*cptr == SENTINEL) {
	  if (count == element) {
	    *name = '\0';
	    if (name - g->name > 0) found = TRUE;
	  }
	  else *newcptr++ = *cptr;
	  count++;
	}
	else if (name - g->name < NAMELEN) {
	  if (count == element) *name++ = *cptr;
	  else *newcptr++ = *cptr;
	}
	else {
	  /*
	   *  This should only happen if something or someone
	   *  has tampered with the stack_var variable.
	   */
	  PutStr(g->program);
	  PutStr(": directory name too long\n");
	  return RETURN_FAIL;
	}
      }

      i = newcptr - newcontents;
      if (i == 0) DeleteVar(stack_var, LV_VAR | GVF_LOCAL_ONLY);
      else rc = updatestack(newcontents, i, g);

      FreeVec(newcontents);
    }
    else rc = RETURN_FAIL;
  }

  if (!found) {
    PutStr(g->program);
    PutStr(count > 1
	   ? ": not that many directory stack entries\n"
	   : ": directory stack is empty\n");
    rc = RETURN_WARN;
  }

  return rc;
}


/*
 *  Update the stack.
 */

static LONG updatestack(STRPTR contents, LONG length, Global *g)
{
  if (!SetVar(stack_var, contents, length, LV_VAR | GVF_LOCAL_ONLY)) {
    PutStr(g->program);
    PutStr(": failed to update directory stack\n");
    return RETURN_FAIL;
  }
  return RETURN_OK;
}


/*
 *  Output an unsigned long to the default output.
 */

static VOID PutULong(ULONG n, Global *g)
{
  UBYTE nbuf[12], *s;

  nbuf[11] = '\0';
  s = &nbuf[11];

  do
  {
    *--s = '0' + n % 10;
  } while (n /= 10);

  PutStr(s);
}
