/*   ---------------------------------      -------
 *   |\  | | | | |  |.| |   \|  |/ /|\      |||||||
 *   | | | |/  | |\ |/  |/|  |\ |/  |    ?  ---+---  =<
 *   | | | |   | |  |     |  |  |   |     \qqqqqqqqq/
 *   ---------------------------------  ~~~~~~~~~~~~~~~~
 *  Concat - Concatenate and print files. AmigaDOS equivalent of UNIX cat.
 *  Copyright (C) 1992, 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: Concat.c,v 37.12 93/03/30 12:58:19 Torsten Rel $
 * $Log:	Concat.c,v $
 * Revision 37.12  93/03/30  12:58:19  Torsten
 * Added explicit check for read errors to fastcat().
 * 
 * Revision 37.11  93/03/26  11:11:53  Torsten
 * Now uses another copying algorithm if the VISIBLE switch is not
 * specified and the destination is not a virtual terminal.
 * An ad hoc test showed a twentyfold speed increase.
 * Removed the QUIET switch as it wasn't used anyway ;-).
 * 
 * Revision 37.10  93/03/01  12:40:46  Torsten
 * Changed all occurrences of "struct DosBase *" to "struct DosLibrary *"
 * 
 * Revision 37.9  93/02/24  00:21:33  Torsten
 * Bug fix: called FreeArgs() too soon.
 * 
 * Revision 37.8  93/02/23  22:40:36  Torsten
 * Now it only stores filenames in a list if the sort switch is given.
 * The entrypoint function has been restructured a bit to get rid of
 * the gotos.
 * 
 * Revision 37.7  93/02/22  18:15:42  Torsten
 * Changed to be linked with the support library
 * 
 * Revision 37.6  93/02/19  18:00:14  Torsten
 * Rewritten almost from scratch.
 * UNBUF switch removed.
 * 
 * Revision 37.5  93/02/18  15:24:25  Torsten
 * Removed explicit utility.library/Stricmp() pragma and prototype.
 * Replaced AllocMem()/FreeMem() pair with AllocVec()/FreeVec().
 * 
 * Revision 37.4  93/02/11  22:47:48  Torsten
 * Source reformatted with GNU indent.
 * exec.library/SetSignal() replaced by dos.library/CheckSignal().
 * 
 * Revision 37.3  93/02/07  11:41:37  Torsten
 * Updated the copyright tag.
 *
 * Revision 37.2  93/02/05  11:36:10  Torsten
 * checked in with -k by Torsten at 93.02.05.11.36.10.
 *
 */

#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 <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/utility_protos.h>
#ifdef __SASC
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/utility_pragmas.h>
#endif
#include <string.h>
#include "tastlib.h"
#include "concat_rev.h"


#define PROGNAME "Concat"
#define TEMPLATE "FILE=FROM/M,AS=TO/K,SORT/S,VISIBLE/S,TABS/S,EOL/S"
#define OPT_FROM    0
#define OPT_TO      1
#define OPT_SORT    2
#define OPT_VISIBLE 3
#define OPT_TABS    4
#define OPT_EOL     5

#define CONCATBUFSIZE 102400L

typedef struct Global {
  struct DosLibrary *DOSBase;
  struct Library *UtilityBase;
  struct List *list;
  BPTR output;
  BOOL visible;
  BOOL tabs;
  BOOL eol;
  LONG (*concat)(UBYTE *filename, struct Global *global);
  UBYTE *buffer;
  LONG  bufsize;
} 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 concatall(Global *global);
LONG concat(UBYTE *filename, Global *global);
LONG fastcat(UBYTE *filename, Global *global);

APTR allocBufferVec(LONG *bufsize, Global *global);

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


LONG entrypoint(VOID)
{
  struct DosLibrary *DOSBase;
  struct RDArgs *args;
  Global *global;
  LONG arg[6];
  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
    {
      arg[OPT_FROM] = arg[OPT_TO] = arg[OPT_SORT] =
	arg[OPT_VISIBLE] = arg[OPT_TABS] = arg[OPT_EOL] = 0L;

      if (!(args = ReadArgs(TEMPLATE, arg, NULL)))
      {
	printerror(PROGNAME, global);
	rc = RETURN_ERROR;
      }
      else
      {
	global->visible = (BOOL) arg[OPT_VISIBLE];
	global->tabs = (BOOL) arg[OPT_TABS];
	global->eol = (BOOL) arg[OPT_EOL];

	if (!arg[OPT_TO])
	  global->output = Output();
	else if (!(global->output = Open((UBYTE *) arg[OPT_TO],
					 MODE_NEWFILE)))
	{
	  PutStr("Cannot open ");
	  PutStr((UBYTE *) arg[OPT_TO]);
	  PutStr("\n");
	  rc = RETURN_ERROR;
	}

	if (global->output)
	{
	  global->concat = concat;
	  if (!global->visible && !global->eol && !global->tabs &&
	      !IsInteractive(global->output))
	  {
	    /* If we can't get the buffer, we simply use the slower
	     * method.
	     */
	    global->bufsize = CONCATBUFSIZE;
	    if (global->buffer = allocBufferVec(&global->bufsize, global))
	      global->concat = fastcat;
	  }

	  if (!arg[OPT_FROM])
	    rc = global->concat(NULL, global);
	  else if (!arg[OPT_SORT])
	    rc = foreach((UBYTE **) arg[OPT_FROM], global->concat, global);
	  else if ((rc = initlist(global)) != ERROR_NO_FREE_STORE)
	  {
	    if (!(rc = foreach((UBYTE **) arg[OPT_FROM], insert, global)))
	      rc = concatall(global);
	    freelist(global);
	  }
	  if (arg[OPT_TO])
	    Close(global->output);
	  if (global->buffer)
	    FreeVec(global->buffer);
	}

	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->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 concatall(Global *global)
{
  struct Node *node;
  LONG rc = RETURN_OK;

  if (global->list->lh_TailPred != (struct Node *) global->list)
    for (node = global->list->lh_Head; node->ln_Succ; node = node->ln_Succ)
      if ((rc = global->concat(node->ln_Name, global)) == ERROR_BREAK)
	break;
  return rc;
}


LONG concat(UBYTE *filename, Global *global)
{
  struct DosLibrary *DOSBase = global->DOSBase;
  register UBYTE breakcheck = 0;
  BPTR input;
  LONG c;

  if (!filename)
    input = Input();
  else if (!(input = Open(filename, MODE_OLDFILE)))
  {
    PutStr("Cannot open ");
    printerror(filename, global);
    return RETURN_WARN;
  }

  while ((c = FGetC(input)) != -1)
  {
    if (global->visible)
    {
      if (global->eol && c == '\n')
	FPutC(global->output, '$');
      else if (global->tabs && c == '\t')
      {
	FPutC(global->output, '^');
	c = 'I';
      }
      else if (c >= 0x80)
      {
	FPutC(global->output, 'M');
	FPutC(global->output, '-');
	c &= 0x7f;
      }
      if ((c < ' ' && c != '\t' && c != '\n') || c == 0x7F)
      {
	FPutC(global->output, '^');
	if (c == 0x7F)
	  c = '?';
	else
	  c += 'A' - 1;
      }
    }
    FPutC(global->output, c);
    if (!(breakcheck -= 4) && CheckSignal(SIGBREAKF_CTRL_C))
    {
      if (filename)
        Close(input);
      return ERROR_BREAK;
    }
  }
  if (filename)
    Close(input);
  return RETURN_OK;
}


LONG fastcat(UBYTE *filename, Global *global)
{
  struct DosLibrary *DOSBase = global->DOSBase;
  BPTR input;
  LONG count, actual;

  if (!filename)
    input = Input();
  else if (!(input = Open(filename, MODE_OLDFILE)))
  {
    PutStr("Cannot open ");
    printerror(filename, global);
    return RETURN_WARN;
  }

  count = 1;
  while ((actual = Read(input,global->buffer,global->bufsize))>0 && count>0)
  {
    count = Write(global->output, global->buffer, actual);

    if (CheckSignal(SIGBREAKF_CTRL_C))
    {
      if (filename)
        Close(input);
      return ERROR_BREAK;
    }
  }
  if (filename)
    Close(input);
  return RETURN_OK;
}


APTR allocBufferVec(LONG *bufsize, Global *global)
{
  APTR buffer;

  for (; *bufsize > 256L; *bufsize /= 2)
    if (buffer = AllocVec(*bufsize, MEMF_CLEAR))
      return buffer;
  return NULL;
}
