/*   ---------------------------------      -------
 *   |\  | | | | |  |.| |   \|  |/ /|\      |||||||
 *   | | | |/  | |\ |/  |/|  |\ |/  |    ?  ---+---  =<
 *   | | | |   | |  |     |  |  |   |     \qqqqqqqqq/
 *   ---------------------------------  ~~~~~~~~~~~~~~~~
 *  SCD - Smart CD
 *  Copyright (C) 1993 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 s-mail at
 *    Torsten Poulin
 *    Banebrinken 99, 2, 77
 *    DK-2400 Copenhagen NV
 *    DENMARK
 *
 * $Id: SCD.c,v 37.4 93/04/28 15:11:53 Torsten Exp $
 * $Log:	SCD.c,v $
 * Revision 37.4  93/04/28  15:11:53  Torsten
 * Memory leak plugged.
 * 
 * Revision 37.3  93/04/14  19:01:02  Torsten
 * Allows SCDCOMMAND to be an empty string (implicit CD).
 * Adjusted the size of the ListView gadget to avoid having its
 * label overlap the other gadgets on a lores screen.
 * 
 * Revision 37.2  93/04/14  10:58:16  Torsten
 * Default place to look for table changed to ENV:SCD/Table
 * to comply with the `User Interface Style Guide'.
 * If the local or global environment variable SCDCOMMAND is
 * set, SCD will queue its value (with ACTION_QUEUE) instead
 * of using CurrentDir() and SetCurrentDirName() to circumvent
 * a problem with WShell.
 * 
 * Revision 37.1  93/04/12  23:18:42  Torsten
 * Initial revision
 * 
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/lists.h>
#include <exec/nodes.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <dos/var.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <intuition/gadgetclass.h>
#include <libraries/gadtools.h>
#include <rexx/rexxio.h>

#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/utility_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#ifdef __SASC
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/gadtools_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#endif
#include <string.h>
#include "tastlib.h"
#include "scd_rev.h"


#define PROGNAME "SCD"
#define TABNAME "SCDTABLE"
#define DEFNAME "ENV:SCD/Table"
#define CDNAME  "SCDCOMMAND"
#define COPYRIGHT "Copyright © 1993 Torsten Poulin"
#define TEMPLATE "DIR/A"
#define OPT_DIR 0

typedef struct {
  struct DosLibrary *DOSBase;
  struct Library    *UtilityBase;
  struct Library    *GadToolsBase;
  struct Library    *IntuitionBase;
  struct Library    *GfxBase;
  struct FileInfoBlock fib;
  struct List *list;
  UBYTE buf[MAXNAMELEN+1];
  UBYTE pattern[(MAXNAMELEN+1)*2];
} Global;

typedef struct {
  struct Node nn_Node;
  UBYTE nn_filename[MAXNAMELEN+1];
} NameNode;


LONG initlist(Global *global);
VOID freelist(Global *global);
LONG insert(UBYTE *filename, Global *global);
LONG SCD(UBYTE *, Global *);
LONG namereq(LONG *, Global *);
VOID handleevents(struct Window *, LONG *, struct Gadget *g[], Global *);
LONG changedir(LONG, Global *);
LONG queuecommand(UBYTE *cmd, UBYTE *arg, Global *);

struct TextAttr Topaz80 = { "topaz.font", 8, 0, 0 };
char const versionID[] = VERSTAG;
char const copyright[] = "$COPYRIGHT:" COPYRIGHT "$";

LONG entrypoint(VOID)
{
  struct DosLibrary *DOSBase;
  struct RDArgs *args;
  Global *global;
  LONG arg[1];
  LONG rc = RETURN_OK;

  if (!(DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", 37L)))
    return RETURN_FAIL;

  if (!(global = AllocVec(sizeof(Global), MEMF_CLEAR)))
  {
    PrintFault(ERROR_NO_FREE_STORE, PROGNAME);
    rc = RETURN_FAIL;
  }
  else
  {
    global->DOSBase = DOSBase;
    if (!(global->UtilityBase = OpenLibrary("utility.library", 37L)))
      rc = RETURN_FAIL;
    else
    {
      if (!(global->IntuitionBase = OpenLibrary("intuition.library", 37L)))
	rc = RETURN_FAIL;
      else
      {
	if (!(global->GadToolsBase = OpenLibrary("gadtools.library", 37L)))
	  rc = RETURN_FAIL;
	else
	{
	  if (!(global->GfxBase = OpenLibrary("graphics.library", 37L)))
	    rc = RETURN_FAIL;
	  else
	  {
	    arg[OPT_DIR] = 0L;

	    if (!(args = ReadArgs(TEMPLATE, arg, NULL)))
	    {
	      printerror(PROGNAME, global);
	      rc = RETURN_ERROR;
	    }
	    else
	    {
	      rc = SCD((UBYTE *) arg[OPT_DIR], global);
	      FreeArgs(args);

	      if (rc == ERROR_BREAK)
	      {
		PrintFault(ERROR_BREAK, NULL);
		rc = RETURN_WARN;
	      }
	      else if (rc == ERROR_NO_FREE_STORE)
	      {
		PrintFault(ERROR_NO_FREE_STORE, PROGNAME);
		rc = RETURN_FAIL;
	      }
	      else if (rc != RETURN_OK)
		printerror(PROGNAME, global);
	    }
	    CloseLibrary(global->GfxBase);
	  }
	  CloseLibrary(global->GadToolsBase);
	}
	CloseLibrary(global->IntuitionBase);
      }
      CloseLibrary(global->UtilityBase);
    }
    FreeVec(global);
  }
  CloseLibrary((struct Library *) DOSBase);
  return rc;
}


LONG initlist(Global *global)
{
  if (!(global->list = AllocVec(sizeof(struct List), MEMF_CLEAR)))
    return ERROR_NO_FREE_STORE;
  /* Initialize list header */
  global->list->lh_Head = (struct Node *) &global->list->lh_Tail;
  global->list->lh_Tail = 0;
  global->list->lh_TailPred = (struct Node *) &global->list->lh_Head;
  return RETURN_OK;
}


VOID freelist(Global *global)
{
  NameNode *worknode;
  NameNode *nextnode;

  worknode = (NameNode *) (global->list->lh_Head);
  while (nextnode = (NameNode *) (worknode->nn_Node.ln_Succ))
  {
    FreeVec(worknode);
    worknode = nextnode;
  }
  FreeVec(global->list);
}


LONG insert(UBYTE *filename, Global *global)
{
  struct Library *UtilityBase = global->UtilityBase;
  NameNode *namenode;
  struct Node *node;

  if (!(namenode = AllocVec(sizeof(NameNode), MEMF_CLEAR)))
    return ERROR_NO_FREE_STORE;
  else
  {
    strcpy(namenode->nn_filename, filename);
    namenode->nn_Node.ln_Name = namenode->nn_filename;

    if (global->list->lh_TailPred == (struct Node *) global->list)
      AddHead(global->list, (struct Node *) namenode);
    else
    {
      for (node=global->list->lh_Head; node->ln_Succ; node=node->ln_Succ)
	if (Stricmp(node->ln_Name, filename) >= 0)
	  break;
      if (node->ln_Succ)
	Insert(global->list, (struct Node *) namenode, node->ln_Pred);
      else
	AddTail(global->list, (struct Node *) namenode);
    }

    return RETURN_OK;
  }
}


LONG SCD(UBYTE *dir, Global *global)
{
  struct DosLibrary *DOSBase = global->DOSBase;
  LONG number = 0;
  LONG rc = RETURN_OK;
  LONG len;
  UBYTE *lastpart;
  BPTR table;

  if (ParsePatternNoCase(dir, global->pattern, strlen(dir)*2+2) < 0)
    return RETURN_FAIL;

  if (GetVar(TABNAME, global->buf, MAXNAMELEN, 0) < 0)
    strcpy(global->buf, DEFNAME);

  if (!(table = Open(global->buf, MODE_OLDFILE)))
  {
    PrintFault(IoErr(), global->buf);
    return RETURN_FAIL;
  }

  if ((rc = initlist(global)) != ERROR_NO_FREE_STORE)
  {
    while (FGets(table, global->buf, MAXNAMELEN+1))
    {
      /* remove newline character */
      len = strlen(global->buf);
      if (len > 0 && global->buf[--len] == '\n')
	global->buf[len] = '\0';

      lastpart = FilePart(global->buf);
      if (MatchPatternNoCase(global->pattern, lastpart))
      {
	number++;
	rc = insert(global->buf, global);
      }
    }

    if (!rc)
    {
      --number;
      if (number == 0)
	rc = changedir(number, global);
      else if (number > 0)
      {
	rc = namereq(&number, global);
	if (number >= 0)
	  rc = changedir(number, global);
      }
      else
      {
	MyPrintf(global, "Can't find %s\n", dir);
	rc = RETURN_FAIL;
      }
    }
    freelist(global);
  }
  Close(table);
  return rc;
}


/*
 * The following three functions exist because we need global
 * library bases to call the amiga.lib versions and I
 * don't want the bother of building the taglists.
 */

struct Gadget *MyCreateGadget(Global *global,
			      ULONG kind,
			      struct Gadget *gad,
			      struct NewGadget *ng,
			      Tag tag1, ...)
{
  struct Library *GadToolsBase = global->GadToolsBase;
  return CreateGadgetA(kind, gad, ng, (struct TagItem *) &tag1);
}


struct Window *MyOpenWindowTags(Global *global,
				struct NewWindow *newWindow,
				Tag tag1, ...)
{
  struct Library *IntuitionBase = global->IntuitionBase;
  return OpenWindowTagList(newWindow, (struct TagItem *) &tag1);
}


struct Screen *MyOpenScreenTags(Global *global,
				struct NewScreen *newScr,
				Tag tag1, ...)
{
  struct Library *IntuitionBase = global->IntuitionBase;
  return OpenScreenTagList(newScr, (struct TagItem *) &tag1);
}


LONG namereq(LONG *number, Global *global)
{
  struct Library *IntuitionBase = global->IntuitionBase;
  struct Library *GadToolsBase = global->GadToolsBase;
  struct Library *GfxBase = global->GfxBase;
  struct DrawInfo *drawinfo;
  struct Screen *pubscreen;
  struct Screen *screen;
  struct Window *window;
  struct Gadget *glist, *gad;
  struct Gadget *gadgets[3];
  struct NewGadget ng;
  ULONG modeID;
  APTR vi;

  glist = NULL;
  if (pubscreen = LockPubScreen(NULL))
  {
    if (drawinfo = GetScreenDrawInfo(pubscreen))
    {
      if ((modeID = GetVPModeID(&(pubscreen->ViewPort))) != INVALID_ID)
      {
	if (screen = MyOpenScreenTags(global, NULL,
				      SA_Pens, (ULONG) (drawinfo->dri_Pens),
				      SA_Depth, drawinfo->dri_Depth,
				      SA_Height, STDSCREENHEIGHT,
				      SA_Width, STDSCREENWIDTH,
				      SA_DisplayID, modeID,
				      SA_Title, VERS " - " COPYRIGHT,
				      SA_Font, &Topaz80,
				      TAG_END))
	{
	  if (vi = GetVisualInfoA(screen, NULL))
	  {
	    gad = CreateContext(&glist);

	    /* ListView gadget */
	    ng.ng_TextAttr = &Topaz80;
	    ng.ng_VisualInfo = vi;
	    ng.ng_LeftEdge = screen->WBorLeft + 2;
	    ng.ng_TopEdge = screen->WBorTop + 1;
	    ng.ng_Width = screen->Width - (screen->WBorLeft
					   + screen->WBorRight + 4);
	    ng.ng_Height = (screen->Height
			    - ng.ng_TopEdge
			    - (screen->Font->ta_YSize + 1)
			    - 30);
	    ng.ng_GadgetText = "_Select directory";
	    ng.ng_GadgetID = 0;
	    ng.ng_Flags = PLACETEXT_BELOW;

	    gadgets[0] = gad = MyCreateGadget(global,
					      LISTVIEW_KIND, gad, &ng,
					      GTLV_Labels, global->list,
					      GTLV_ShowSelected, NULL,
					      GTLV_Selected, 0,
					      GT_Underscore, '_',
					      TAG_END);

	    ng.ng_Height = screen->Font->ta_YSize + 4;
	    ng.ng_Width = 100;
	    ng.ng_LeftEdge += INTERWIDTH;
	    ng.ng_TopEdge = (screen->Height
			     - (screen->WBorTop+(screen->Font->ta_YSize+1))
			     - screen->WBorBottom
			     - ng.ng_Height
			     - INTERHEIGHT);
	    ng.ng_GadgetText = "Change _dir";
	    ng.ng_Flags = PLACETEXT_IN;
	    ng.ng_GadgetID = 1;
	    gadgets[1] = gad = MyCreateGadget(global,
					      BUTTON_KIND, gad, &ng,
					      GT_Underscore, '_',
					      TAG_END);

	    ng.ng_LeftEdge = (screen->Width
			      - screen->WBorLeft - screen->WBorRight - 1
			      - ng.ng_Width
			      - INTERWIDTH);
	    ng.ng_GadgetText = "_Cancel";
	    ng.ng_GadgetID = 2;
	    gadgets[2] = gad = MyCreateGadget(global,
					      BUTTON_KIND, gad, &ng,
					      GT_Underscore, '_',
					      TAG_END);

	    if (gad)
	    {
	      if (window = MyOpenWindowTags(global, NULL, 
					    WA_Gadgets, glist,
					    WA_Width, screen->Width,
					    WA_Height,
					    (screen->Height
					     - (screen->WBorTop
						+ (screen->Font->ta_YSize
						   + 1))),
					    WA_RMBTrap, TRUE,
					    WA_SimpleRefresh, TRUE,
					    WA_Activate, TRUE,
					    WA_Backdrop, TRUE,
					    WA_IDCMP, IDCMP_REFRESHWINDOW
					    | IDCMP_VANILLAKEY
					    | LISTVIEWIDCMP
					    | BUTTONIDCMP,
					    WA_CustomScreen, screen,
					    TAG_END))
	      {
		GT_RefreshWindow(window, NULL);
		handleevents(window, number, gadgets, global);
		CloseWindow(window);
	      }
	    }
	    FreeGadgets(glist);
	    FreeVisualInfo(vi);
	  }
	  CloseScreen(screen);
	}
      }
      FreeScreenDrawInfo(pubscreen, drawinfo);
    }
    UnlockPubScreen(NULL, pubscreen);
  }
  return RETURN_OK;
}


VOID handleevents(struct Window *window,
		  LONG *number,
		  struct Gadget *gadgets[],
		  Global *global)
{
  struct Library *GadToolsBase = global->GadToolsBase;
  struct IntuiMessage *imsg;
  struct Gadget *gad;
  struct TagItem lvtags[3];
  LONG n = 0;
  BOOL done = FALSE;

  lvtags[0].ti_Tag = GTLV_Selected;
  lvtags[1].ti_Tag = GTLV_Top;
  lvtags[2].ti_Tag = TAG_END;
  lvtags[2].ti_Data = NULL;

  while (!done)
  {
    Wait(1 << window->UserPort->mp_SigBit);

    while (!done && (imsg = GT_GetIMsg(window->UserPort)))
    {
      switch (imsg->Class)
      {
      case IDCMP_GADGETUP:
	gad = (struct Gadget *) imsg->IAddress;
	switch (gad->GadgetID)
	{
	case 0: n = imsg->Code; break;
	case 1: *number = n; done = TRUE; break;
	case 2: *number = -1; done = TRUE; break;
	}
	break;
      case IDCMP_VANILLAKEY:
	switch (imsg->Code)
	{
	case 'D':
	case 'd': *number = n; done = TRUE; break;
	case 'C':
	case 'c': *number = -1; done = TRUE; break;
	case 'S':
	  if (--n < 0)
	    n = 0;
	  lvtags[0].ti_Data = lvtags[1].ti_Data = (UWORD) n;
	  GT_SetGadgetAttrsA(gadgets[0], window, NULL, lvtags);
	  break;
	case 's':
	  if (++n > *number)
	    n = *number;
	  lvtags[0].ti_Data = lvtags[1].ti_Data = (UWORD) n;
	  GT_SetGadgetAttrsA(gadgets[0], window, NULL, lvtags);
	  break;
	}
	break;
      case IDCMP_REFRESHWINDOW:
	GT_BeginRefresh(window);
	GT_EndRefresh(window, TRUE);
	break;
      }
      GT_ReplyIMsg(imsg);
    }
  }
}


LONG changedir(LONG number, Global *global)
{
  struct DosLibrary *DOSBase = global->DOSBase;
  struct Node *node;
  LONG rc = RETURN_OK;
  LONG n = 0;
  BPTR lock;

  for (node=global->list->lh_Head; node->ln_Succ; node=node->ln_Succ, n++)
    if (n == number)
      break;

  if (lock = Lock(node->ln_Name, SHARED_LOCK))
  {
    if (Examine(lock, &global->fib)
        && global->fib.fib_DirEntryType > 0
        && global->fib.fib_DirEntryType != ST_SOFTLINK)
    {
      if (GetVar(CDNAME, global->buf, MAXNAMELEN, 0) >= 0)
	rc = queuecommand(global->buf, node->ln_Name, global);
      else
      {
	lock = CurrentDir(lock);
	SetCurrentDirName(node->ln_Name);
      }
    }
    else
      rc = RETURN_ERROR;
    UnLock(lock);
  }
  else
    rc = RETURN_ERROR;
  return rc;
}


/*
 * Send ACTION_QUEUE packet to the handler associated with stdin.
 * The method was learned by `reverse engineering' the QUEUE command
 * shipped with the ConMan 1.3 console handler.
 * Information kindly provided by Kristian Nielsen (bombadil@diku.dk)
 * (and ultimately Bill Hawes, author of ARexx, ConMan, and WShell :-) ).
 */

LONG queuecommand(UBYTE *cmd, UBYTE *arg, Global *global)
{
  struct DosLibrary *DOSBase = global->DOSBase;
  struct FileHandle *console_fh;
  struct MsgPort *destport;
  struct MsgPort *port;
  struct Process *process;
  UBYTE *line;
  LONG line_len;
  BPTR console_fh_bptr;
  LONG rc = RETURN_OK;

  /* Add 1 for space, 2 for quotes, 1 for '\n', and 1 for '\0' */
  line_len = strlen(cmd) + strlen(arg) + 5;
  if (!(line = AllocVec(line_len, 0L)))
    return ERROR_NO_FREE_STORE;

  /* Build command line */
  strcpy(line, cmd);
  strcat(line," \"");
  strcat(line, arg);
  strcat(line, "\"\n");

  /* Get process and process msgport. */
  process = (struct Process *) FindTask(NULL);
  port = &process->pr_MsgPort;
  
  /*
   * ACTION_QUEUE works like ACTION_READ and ACTION_WRITE.
   * It takes three args: The fh_Arg1 field of the file handle
   * (NOT the file handle itself), the start of the data to queue, and
   * the number of bytes to queue.
   *
   * Since we need a file handle, we cannot just call GetConsoleTask().
   * The ConMan command 'queue' uses the pr_CIS field of the process
   * structure. We simply use Input().
   */

  console_fh_bptr = Input();	/* Could use process->pr_CIS */
  if (!console_fh_bptr)
  {
    MyPrintf(global, "%s: Cannot queue %s\n", PROGNAME, line);
    rc = RETURN_FAIL;
  }
  else
  {
    console_fh = BADDR(console_fh_bptr); /* Convert to C pointer. */
    if (!console_fh->fh_Type)
    {
      MyPrintf(global,
	       "%s: Input stream not associated with handler process\n",
	       PROGNAME);
      rc = RETURN_FAIL;
    }
    else
    {
      destport = console_fh->fh_Type;
      DoPkt(destport, ACTION_QUEUE, console_fh->fh_Arg1,
	    (LONG) line, line_len, 0L, 0L);
    }
  }
  FreeVec(line);
  return rc;
}
