/*
 * SmartReadArgs.c -- CLI/Workbench transparent ReadArgs()
 *
 * $VER: SmartReadArgs.c 1.6 (7.9.98)
 *
 * Copyright 1998 by Thomas Aglassinger <agi@sbox.tu-graz.ac.at>
 *
 * Based on ExtReadArgs Copyright 1994,1995 by Stefan Ruppert
 */

/****** SmartReadArgs/--history-- *******************************************
 * HISTORY
 *   Version 1.6, 7-Sep-1998
 *
 *   - Changed name to SmartReadArgs to avoid confusion with other work based
 *     on the same material
 *   - Changed function parameters for SmartReadArgs() so that no more SAS/c
 *     specific values of argc/argv are required (Of course it still works
 *     with SAS/c, but you have to provide the WBStartup from "outside").
 *   - Changed all #include <clib/...> to #include <proto/..>, except for
 *     <clib/alib_stdio_protos.h> in "test.c". Where the hell is this one?
 *   - Added feature to ignore tooltypes that are not in the template
 *   - Added some missing includes in SmartReadArgs.c so the source codes
 *     compile without warnings
 *   - Changed #include <debug.h> to #include "debug.h" and provided a proper
 *     debug.h
 *   - The WINDOW tooltype is handled properly even if it is not entirely
 *     written in upper case.
 *   - Requires "utility.library" to be open as Stricmp() is used several
 *     times
 *   - Changed from Printf() to printf() using stdio of amiga.lib to make the
 *     code compile easier on non-SAS environments
 *   - Changed autodoc tool to Robodoc
 *   - Fixed enforcer hit if no tooltypes were provided at all
 *   - Remove some "char filename[34]" stuff and replaced the array dimension
 *     by MAXIMUM_FILENAME_LENGTH for future compatibility
 *   - Cleaned-up autodocs
 * ANCIENT HISTORY
 *   ExtReadArgs() by Stefan Ruppert
 *
 *   See aminet:dev/misc/extrdargs_v1.5.lha for the original version.
 *
 *   $HISTORY
 *   08.01.95 : 001.005 :  changed to ExtReadArgs()
 *   24.09.94 : 001.004 :  now checks after ReadArgs the SIGBREAKF_CTRL_C
 *                         flag, thus if anyone canceled during ReadArgs()
 *                         help ExtReadArgs() fails
 *   08.09.94 : 001.003 :  between two switches (no equal sign) there was
 *                         no space, this is fixed
 *   08.09.94 : 001.002 :  wb files now enclosed in quotes
 *   04.09.94 : 001.001 :  bumped to version 1
 *   19.05.94 : 000.001 :  initial
 ***************************************************************************/

/* ------------------------------ include's ------------------------------- */

#include "debug.h"
#include "SmartReadArgs.h"

#include <exec/memory.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>
#include <dos/exall.h>
#include <utility/tagitem.h>

#include <string.h>

#include <proto/dos.h>
#include <proto/icon.h>
#include <proto/exec.h>
#include <proto/utility.h>

/* ---------------------------- local defines ----------------------------- */

#define TEMPSIZE                     512
#ifndef MAX
#define MAX(a,b)  (((a) > (b)) ? (a) : (b))
#endif

#ifndef EOS
#define EOS    '\0'
#endif

#define MODE_MULTI      1

#define MAXIMUM_FILENAME_LENGTH 108

/* --------------------------- library bases ------------------------------ */
extern struct Library *IconBase;
extern struct Library *UtilityBase;

#ifdef __GNUC__
extern struct WBStartup *_WBenchMsg;
#endif

/* -------------------------- static prototypes --------------------------- */

static struct DiskObject *smart_get_icon(struct SmartArgs *args, struct WBStartup *wbarg);
static void fstrcpy(struct SmartArgs *args, STRPTR string);
static void get_arg_name(struct SmartArgs *args, STRPTR buffer, ULONG size, ULONG * modes);
static void get_wbarg_name(struct WBArg *wbarg, STRPTR buffer, ULONG size);
static BOOL is_in_template(STRPTR name, STRPTR template);

/****** SmartReadArgs/--background-- ****************************************
 * COPYRIGHT
 *   SmartReadArgs is Copyright 1998 Thomas Aglassinger
 *
 *   ExtReadArgs, its prequel, is Copyright 1994,1995 Stefan Ruppert
 *
 *   Permission is granted to freely distribute the material (also only
 *   parts of it) as long this ReadMe is included and all the copyright
 *   notes are left unaltered except for a description of your changes.
 * MOTIVATION
 *   The way of parsing ToolTypes provided by "icon.library" is rather
 *   clumsy. This is particular annoying as many programmers and users got
 *   used to ReadArgs(), which does the argument handling for many CLI
 *   commands and ARexx ports.
 *
 *   Unfortunately, ReadArgs lacks a interface to Workbench tooltypes, thus
 *   its usage preventes your programs from being started from Workbench.
 *
 *   SmartReadArgs() copies all Workbench arguments in a single string and
 *   passes this string to the ReadArgs() function. If started from CLI, it
 *   calls ReadArgs() without this step.
 * AUTHOR
 *   Stefan Ruppert wrote most parts of the source code, designed the general
 *   interface and implemented loads of nice features. Basically he did the
 *   "hard work".
 *
 *   He got the main idea for the implementation from Stefan Winterstein,
 *   the author of ARoach.
 *
 *   Thomas Aglassinger <agi@sbox.tu-graz.ac.at> did some minor changes,
 *   established a more consistent naming schema, reworked the documentation
 *   and also added support for gcc/libnix.
 *
 *   Contact him in case of problems or if you made some enhancements.
 *
 *   Updates are available from aminet:dev/misc/SmartReadArgs.lha.
 * DISCLAIMER
 *   There is no warranty for this software package. Although the author
 *   has tried to prevent errors, he can't guarantee that the software
 *   package described in this document is 100% reliable. You are
 *   therefore using this material at your own risk. The author cannot be
 *   made responsible for any damage which is caused by using this
 *   software package.
 ****************************************************************************/

/* The define below is used to rename the example main() used in the autodoc
 * to dummy_main(). Using two main()s would cause problems for the linker. */
#define main dummy_main

/****** SmartReadArgs/SmartReadArgs ******************************************
 * NAME
 *   SmartReadArgs -- Workbench/CLI transparent ReadArgs().
 * SYNOPSIS
 *   error = SmartReadArgs(wb_startup, smart_args);
 * 
 *   LONG SmartReadArgs(struct WBStartup *, struct SmartArgs *);
 * FUNCTION
 *   This function is a CLI/Workbench transparent interface to ReadArgs().
 *
 *   In case of a Workbench start, it analyzes the WBStartup message and
 *   possible tooltypes. These are converted to a text string that can be
 *   passed to ReadArgs() like before.
 *
 *   Tooltypes that are not part of the template are ignored. This includes
 *   tooltypes being disabled with "(...)", NewIcons image data on systems
 *   without NewIcons installed and all this «« Icon by some idiot »» crap.
 * 
 *   If the application was stared from CLI, it simply calls ReadArgs()
 *   without the conversion step.
 *
 *   If all went well you get a return value of zero. This means the passed
 *   arguments fit the template and are ready to use. Otherwise you get a
 *   IoErr()-like return code.
 * INPUTS
 *    wb_startup - Workbench startup message. Refer to your compiler manual
 *        to learn how to obtain it. If this is NULL, the program was
 *        started from CLI.
 *    smart_args - structure which holds all information used by
 *        SmartReadArgs(). You have to setup the following fields before the
 *        call:
 *
 *        sa_Template - The template passed to ReadArgs()
 *        sa_Parameter - ReadArgs() LONG WORD array to hold the arguments
 *        sa_FileParameter - number of Argument in the template to use
 *            for the files passed via WBStartup->sm_ArgList or -1, that
 *            means you don't want any files
 *        sa_Window - Window description string to open when the program
 *            is started from the workbench. NULL means no window. If the
 *            icon has a WINDOW tooltype, this value is ignored.
 *        sa_RDArgs - RDArgs structure to use for ReadArgs() call. This
 *            can be used to provide extended help.
 *        sa_Buffer - Pointer to a buffer to use for the Workbench startup
 *            or NULL, that means SmartReadArgs() allocates a buffer for you
 *        sa_BufferSize - Size of the optional buffer. If it is smaller than
 *            SA_MINIMUM_BUFFER_SIZE it will be adjusted.
 *
 *        All other fields should be set to NULL.
 * RESULTS
 *   Zero for success. You can check the sa_Flags field for the
 *   SAF_WORKBENCH flag to learn how the program was started.
 *
 *   Otherwise an IoErr()-like error code is returned. This can be passed
 *   directly to PrintFault() or similar.
 * NOTES
 *   Always call SmartFreeArgs(), even if SmartReadArgs() failed! See example
 *   below.
 *
 *   This function requires "dos.library", "icon.library" and
 *   "utility.library" to be opened by the application. Normally this
 *   already has been done by the compiler startup code.
 *
 *   There is a not widely known feature of ReadArgs(): with templates like
 *   "FROM/M/A,TO/A", you can select the files from workbench performing the
 *   following steps:
 *
 *   - Select the program
 *   - Select the FROM files
 *   - Select and double click the TO file
 *
 *   This is available because ReadArgs() grabs the last string from a
 *   multi-argument FROM and uses it as the TO parameter, if none is passed
 *   explicitely.
 * BUGS
 *   There are some known problems when used with GCC, mostly related to the
 *   fact that I never bothered creating a useable developer environment
 *   around it (and I'm not sure if this is even possible >:) ...):
 *
 *   - Debugging output shows up in the console instead of SER:. Does
 *     debug.lib exist for gcc? (Wasn't there this strange hunk2gcc
 *     converter?)
 *   - "Read from 0" Enforcer hit in SmartReadArgs(). Couldn't figure out
 *     the exact location yet because of the asynchronous debugging output
 *     mentioned above.
 *
 *   For someone with a reasonable experience with GCC, it should be easy to
 *   fix this.
 *
 *   The SAS/c implementation does not have these problems.
 * SEE ALSO
 *   SmartFreeArgs(), dos.library/ReadArgs(), icon.library/GetDiskObjectNew()
 * EXAMPLE
 *   The main archiev comes with a "test.c" and a couple of icons to start
 *   the corresponding executable "test". Take a look at the source code and
 *   play with it.
 *
 *   See below for a smaller code segment that expects the "dos.library",
 *   "icon.library" and "utility.library" to be open already.
 * SOURCE
 */
int main(int argc, STRPTR argv[])
{
   struct SmartArgs smart_args =
   {NULL};
   LONG argument[2];
   LONG error;

   /* Obtain WBStartup; depends on your compiler environment */
   struct WBStartup *wb_startup = NULL;
#ifdef __GNUC__
   wb_startup = _WBenchMsg;
#else
   if (argc == 0)
   {
      wb_startup = (struct WBStartup *) argv;
   }
#endif

   smart_args.sa_Template = "FILES/M/A,VERBOSE";
   smart_args.sa_Parameter = argument;
   smart_args.sa_FileParameter = 0;
   smart_args.sa_Window = "CON:////My WB-Window/AUTO/CLOSE/WAIT";

   error = SmartReadArgs(wb_startup, &smart_args);
   if (error == 0)
   {
      /* do something */
   }
   else
   {
      PrintFault(error, "MyProgram");
   }

   SmartFreeArgs(&smart_args);

   return ((error == 0) ? RETURN_OK : RETURN_FAIL);
}
/****************************************************************************/
LONG SmartReadArgs(struct WBStartup * wb_startup, struct SmartArgs * args)
{
   LONG error;

   args->sa_Flags = 0;

   D(bug("UtilityBase = 0x%lx\n", (ULONG) UtilityBase));
   D(bug("IconBase    = 0x%lx\n", (ULONG) IconBase));
   D(bug("WBStartup   = 0x%lx\n", (ULONG) wb_startup));

   if (wb_startup != NULL)
   {
      struct WBArg *wbarg = wb_startup->sm_ArgList;
      LONG arg_counter = 0;

      D(bug("  numArgs   = %ld\n", wb_startup->sm_NumArgs));
      while (arg_counter < wb_startup->sm_NumArgs)
      {
         D(bug("  name[%ld] = \"%s\"\n", arg_counter, wbarg->wa_Name));
         wbarg += 1;
         arg_counter += 1;
      }               
   }

   if (wb_startup != NULL)
   {
      if (!(args->sa_RDArgs = AllocDosObject(DOS_RDARGS, NULL)))
      {
         return (ERROR_NO_FREE_STORE);
      }
      else
      {
         args->sa_Flags |= SAF_ALLOCRDARGS;

         if (!args->sa_Buffer)
         {
            args->sa_BufferSize = MAX(SA_MINIMUM_BUFFER_SIZE, args->sa_BufferSize);
            args->sa_Buffer = AllocMem(args->sa_BufferSize, MEMF_ANY);
            args->sa_Flags |= SAF_ALLOCBUFFER;
         }

         if (!args->sa_Buffer)
            return (ERROR_NO_FREE_STORE);
         else
         {
            struct DiskObject *dobj;

            args->sa_ActualPtr = args->sa_Buffer;
            args->sa_EndPtr = args->sa_Buffer + args->sa_BufferSize - 1;

            if (!(dobj = smart_get_icon(args, wb_startup)))
            {
               return (ERROR_OBJECT_NOT_FOUND);
            }
            else
            {
               struct WBArg *wbarg = args->sa_WBArg;
               ULONG num = args->sa_NumArgs;

               STRPTR *tooltypes = (STRPTR *) dobj->do_ToolTypes;
               STRPTR name;
               STRPTR temp;
               STRPTR ptr;

               if (num > 1 && args->sa_FileParameter >= 0 && (temp = AllocMem(TEMPSIZE, MEMF_ANY)))
               {
                  ULONG modes = 0;

                  get_arg_name(args, temp, TEMPSIZE, &modes);
                  fstrcpy(args, temp);
                  fstrcpy(args, " ");

                  /* no "/M" specifier in the ReadArgs() template, thus use only the first file */
                  if (modes != MODE_MULTI)
                     num = 2;

                  while (num > 1)
                  {
                     get_wbarg_name(wbarg, temp, TEMPSIZE);
                     fstrcpy(args, "\"");
                     fstrcpy(args, temp);
                     fstrcpy(args, "\" ");
                     num--;
                     wbarg++;
                  }

                  FreeMem(temp, TEMPSIZE);
               }

               D(bug("tooltypes=%lx\n", (ULONG) tooltypes));
               if (tooltypes)
               {
                  while (*tooltypes)
                  {
                     ptr = *tooltypes;
                     name = ptr;

                     /* check if this tooltype enabled and part of the
                      * template */
                     if ((*ptr != '(')
                         && is_in_template(name, args->sa_Template))
                     {
                        while (*ptr != '=' && *ptr != EOS)
                           ptr++;

                        if (*ptr == '=')
                        {
                           *ptr = EOS;

                           if (!Stricmp(name, "WINDOW"))
                           {
                              STRPTR win;
                              if ((win = AllocVec(strlen(ptr + 1) + 1, MEMF_ANY)))
                              {
                                 strcpy(win, ptr + 1);
                                 args->sa_Window = win;
                                 args->sa_Flags |= SAF_ALLOCWINDOW;
                              }

                           }
                           else
                           {
                              fstrcpy(args, name);

                              /* enclose the argument in "" */
                              if (*(ptr + 1) == '"')
                              {
                                 fstrcpy(args, "=");
                                 fstrcpy(args, ptr + 1);
                              }
                              else
                              {
                                 fstrcpy(args, "=\"");
                                 fstrcpy(args, ptr + 1);
                                 fstrcpy(args, "\"");
                              }

                              *ptr = '=';
                           }
                        }
                        else
                           fstrcpy(args, name);

                        fstrcpy(args, " ");
                     }
                     tooltypes++;
                  }             /* while (*tooltypes) */
               }                /* if (tooltypes) */
               fstrcpy(args, "\n");

               D(bug("final wb command line : \"%s\"\n", args->sa_Buffer));
            }
         }
      }

      args->sa_RDArgs->RDA_Source.CS_Buffer = args->sa_Buffer;
      args->sa_RDArgs->RDA_Source.CS_Length = strlen(args->sa_Buffer);

      args->sa_Flags |= SAF_WORKBENCH;
   }

   args->sa_FreeArgs = ReadArgs(args->sa_Template, args->sa_Parameter, args->sa_RDArgs);

   if (SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
   {
      SetIoErr(ERROR_BREAK);
   }

   if ((error = IoErr()) == 0 && (wb_startup != NULL))
   {
      if (args->sa_Window)
      {
         args->sa_WindowFH = Open(args->sa_Window, MODE_NEWFILE);
         if (args->sa_WindowFH)
         {
            args->sa_OldOutput = SelectOutput(args->sa_WindowFH);
            args->sa_OldInput = SelectInput(args->sa_WindowFH);
         }
      }
   }

   return (error);
}

/****** SmartReadArgs/SmartFreeArgs ******************************************
 * NAME
 *   SmartFreeArgs -- Free all resources allocated by SmartReadArgs().
 * SYNOPSIS
 *   SmartFreeArgs(smart_args);
 *
 *   void SmartFreeArgs(struct SmartArgs *);
 * FUNCTION
 *   Free all resources allocated by a previous call to SmartReadArgs().
 * INPUTS
 *   smart_args - Same pointer as passed to SmartReadArgs() before
 * NOTES
 *   Always call SmartFreeArgs(), even if SmartReadArgs() failed! Take a look
 *   at the example for SmartReadArgs().
 * SEE ALSO
 *   SmartReadArgs()
 ****************************************************************************/
void SmartFreeArgs(struct SmartArgs *args)
{
   /* FreeArgs() can handle a NULL pointer */
   FreeArgs(args->sa_FreeArgs);

   if (args->sa_Flags & SAF_ALLOCRDARGS)
      if (args->sa_RDArgs)
         FreeDosObject(DOS_RDARGS, args->sa_RDArgs);

   if (args->sa_Flags & SAF_ALLOCBUFFER)
      FreeMem(args->sa_Buffer, args->sa_BufferSize);

   if (args->sa_WindowFH)
   {
      SelectOutput(args->sa_OldOutput);
      SelectInput(args->sa_OldInput);
      Close(args->sa_WindowFH);
   }

   if (args->sa_Flags & SAF_ALLOCWINDOW && args->sa_Window)
      FreeVec(args->sa_Window);

}

/* This code was grapped from IconImage/wbarg.c/IconFromWBArg()
 * Commodore-Amiga Example code
 */
static struct DiskObject *smart_get_icon(struct SmartArgs *args, struct WBStartup *wb_startup)
{
   struct DiskObject *dob = NULL;
   struct WBArg *wbarg = wb_startup->sm_ArgList;
   ULONG num = wb_startup->sm_NumArgs;

   UBYTE work_name[MAXIMUM_FILENAME_LENGTH];
   BPTR old_lock, new_lock;

   /* Copy the WBArg contents */
   strncpy(work_name, wbarg->wa_Name, MAXIMUM_FILENAME_LENGTH);

   new_lock = DupLock(wbarg->wa_Lock);
   if (new_lock != NULL)
   {
      D(bug("work_name : \"%s\"\n", work_name));

      /* go to the directory where the icon resides */
      old_lock = CurrentDir(new_lock);

      dob = GetDiskObjectNew(work_name);

      /* test, if the first icon is a project icon and if so, get its icon */
      if (wb_startup->sm_NumArgs > 1)
      {
         BPTR new_lock2;

         if ((new_lock2 = DupLock(wbarg[1].wa_Lock)))
         {
            struct DiskObject *prj;

            CurrentDir(new_lock2);

            UnLock(new_lock);
            new_lock = new_lock2;

            strncpy(work_name, wbarg[1].wa_Name, MAXIMUM_FILENAME_LENGTH);
            D(bug("work_name2 : \"%s\"\n", work_name));

            if ((prj = GetDiskObjectNew(work_name)))
            {
               if (prj->do_Type == WBPROJECT)
               {
                  BPTR test;

                  /* if this is only an icon skip it */
                  if (!(test = Lock(work_name, SHARED_LOCK)))
                  {
                     wbarg++;
                     num--;
                  }
                  else
                     UnLock(test);

                  if (dob)
                     FreeDiskObject(dob);

                  dob = prj;
               }
            }
         }
      }

      if (dob)
      {
         D(bug("dobj window : %s\n", dob->do_ToolWindow));
      }

      /* go back to where we used to be */
      CurrentDir(old_lock);

      /* release the duplicated lock */
      UnLock(new_lock);

      args->sa_WBArg = wbarg + 1;
      args->sa_NumArgs = num;
   }

   D(bug("return (dob)\n"));

   return (dob);
}

static void fstrcpy(struct SmartArgs *args, STRPTR string)
{
   STRPTR ptr = args->sa_ActualPtr;
   STRPTR end = args->sa_EndPtr;

   while (ptr < end && *string)
      *ptr++ = *string++;

   *ptr = EOS;                  /* Mark end of string */

   args->sa_ActualPtr = ptr;
}

static void get_arg_name(struct SmartArgs *args, STRPTR buffer, ULONG size, ULONG * modes)
{
   ULONG num = args->sa_FileParameter;
   STRPTR ptr = args->sa_Template;

   *modes = 0;

   while (num > 0)
   {
      while (*ptr != ',' && *ptr != EOS)
         ptr++;

      if (*ptr == ',')
         ptr++;
      num--;
   }

   if (*ptr != EOS)
   {
      while (*ptr != ',' && *ptr != '/' && *ptr != EOS && size > 0)
      {
         *buffer++ = *ptr++;
         size--;
      }

      while (*ptr == '/')
      {
         ptr++;

         if (*ptr == 'M' || *ptr == 'm')
            *modes = MODE_MULTI;

         ptr++;
      }
   }

   *buffer = EOS;
}

static void get_wbarg_name(struct WBArg *wbarg, STRPTR buffer, ULONG size)
{
   BPTR new;

   if ((new = DupLock(wbarg->wa_Lock)))
   {
      if (!NameFromLock(new, buffer, size))
         *buffer = EOS;
      else if (!AddPart(buffer, wbarg->wa_Name, size))
         *buffer = EOS;

      UnLock(new);
   }
   else
      *buffer = EOS;
}

/* Enable extended debugging for is_in_template() */
#if 0
#define D2(x) x
#else
#define D2(x) /* nufin */
#endif
static BOOL is_in_template(STRPTR name, STRPTR template)
{
   BOOL found = FALSE;
   STRPTR current_word = template;
   BOOL skip_switch = FALSE;
   size_t name_length;

   /* Evaluate length of name part of whole tooltype */
   name_length = 0;
   while ((name[name_length] != EOS)
          && (name[name_length] != '='))
   {
      name_length += 1;
   }

   D(bug("find `%s' in template `%s'\n", name, template));
   while ((current_word[0] != '\0') && (!found))
   {
      STRPTR next_word = strpbrk(current_word, "/=,");
      size_t current_word_length;

      if (next_word == NULL)
      {
         next_word = current_word + strlen(current_word);
      }
      current_word_length = next_word - current_word;

      if (skip_switch)
      {
         D2(bug("  skip  (`%s', %lu)\n", current_word, current_word_length));
         skip_switch = FALSE;
      }
      else
      {
         D2(bug("  check (`%s', %lu)\n", current_word, current_word_length));
         if ((name_length == current_word_length)
             && !Strnicmp(name, current_word, name_length))
         {
            D(bug("  found!\n"));
            found = TRUE;
         }
      }

      current_word = next_word;
      if (current_word[0] != '\0')
      {
         if (current_word[0] == '/')
         {
            skip_switch = TRUE;
         }
         current_word += 1;
      }
   }

   return found;
}

