//------------------------------------------------------------------------
// ^FILE: cmdargs.c - implement the various predefined CmdArg subclasses
//
// ^DESCRIPTION:
//    This file implements the CmdArg derived classes that are declared
//    in <cmdargs.h>
//
// ^HISTORY:
//    03/25/92	Brad Appleton	<bradapp@enteract.com>	Created
//
//    03/03/93	Brad Appleton	<bradapp@enteract.com>
//    - Added exit_handler() and quit() member-functions to CmdLine
//-^^---------------------------------------------------------------------

#include <stdlib.h>
#include <iostream.h>
#include <string.h>
#include <ctype.h>

#include "cmdargs.h"
#include "exits.h"
#include "fifolist.h"

   // return values for operator()
enum { SUCCESS = 0, FAILURE = -1 } ;


//-----------------------------------------------------------------------------
// ^FUNCTION: compile, operator() - handle an argument from the command-line
//
// ^SYNOPSIS:
//    int  operator()(arg, cmd);
//    int  compile(arg, cmd, value);
//    int  compile(arg, cmd, value, default_value);
//
// ^PARAMETERS:
//    const char * & arg;
//    -- the prospective value for this command argument.
//       upon returning this value should be updated to point to the first
//       character of "arg" that was NOT used as part of the value for this
//       argument (set "arg" to NULL if all of it was used).
//
//    CmdLine & cmd;
//    -- the command that matched this argument on its command-line
//
//    <Type> & value;
//    -- The internal value (of some appropriate type) that is "managed"
//       by this command argument.
//
//    unsigned  default_value;
//    -- What to assign to "value" if "arg" is NOT a value for this command
//       argument.
//
// ^DESCRIPTION:
//    These member functions are responsible for taking whatever action
//    is appropriate when its corresponding command argument is matched
//    on the command-line.  For argument-types that simply "compile"
//    their argument into some kind of internal value, "compile()" does
//    all the work and operator() merely calls compile() with the proper
//    value as a reference parameter.
//
// ^REQUIREMENTS:
//    The "arg_flags" data member of this command-argument must have been
//    set appropriately (by "cmd") to indicate to us exactly how "arg" was
//    specified on the command-line for this (and only this) occurrence of
//    "arg".
//
// ^SIDE-EFFECTS:
//    - If (cmd.flags() & QUIET) is NOT TRUE and FAILURE is to be returned,
//      then error messages should be printed using cmd.error().
//
//    - arg is modified to be NULL of to point to the unused portion of itself.
//      
//    - If (cmd.flags() & TEMP) is TRUE and we need the value of "arg"
//      to stick around, then storage is allocated in order to make
//      a copy of "arg" (and the command-argument is responsible for
//      de-allocating this storage).
//
// ^RETURN-VALUE:
//    FAILURE (non-zero)  If something went wrong when performing the
//                        desired actions for this command-argument.
//                        A common problem would be that "arg" is
//                        syntactically incorrect.
//
//    SUCCESS (zero)  If "arg" is NULL and/or we were able to succesfully
//                    perform all desired actions for this command argument.
//-^^--------------------------------------------------------------------------


//-------------------------------------------------------------- Dummy Argument

CmdArgDummy::~CmdArgDummy(void)  {}

int
CmdArgDummy::is_dummy(void)  { return  1; }

   // For a CmdArgDummy - operator() is a No-OP and should NEVER
   // be called.
   //
int
CmdArgDummy::operator()(const char * & , CmdLine & )
{
   return  SUCCESS;
}

//-------------------------------------------------------------- Usage Argument

CmdArgUsage::CmdArgUsage(char         optchar,
                         const char * keyword,
                         const char * description,
                         ostream    * osp)
   : CmdArg(optchar, keyword, description, CmdArg::isOPT), os_ptr(osp)
{
   if (os_ptr == NULL)  os_ptr = &cout;
}

CmdArgUsage::~CmdArgUsage(void)  {}

void
CmdArgUsage::ostream_ptr(ostream * osp)
{
   os_ptr = (osp != NULL) ? osp : &cout ;
}

   // Just need to call cmd.usage and exit.
   //
int
CmdArgUsage::operator()(const char * & , CmdLine & cmd)
{
   cmd.usage(*os_ptr, CmdLine::VERBOSE_USAGE);
   cmd.quit(e_USAGE);
   return  SUCCESS;
}

//----------------------------------------------------------- Integer Arguments

CmdArgIntCompiler::~CmdArgIntCompiler(void)  {}

   // Compile a string into an integer value.
int
CmdArgIntCompiler::compile(const char * & arg, CmdLine & cmd, int & value)
{
   const char * ptr = NULL ;
   long  result = 0 ;

   if (arg == NULL) {
      return  SUCCESS ;  // no value given - nothing to do
   } else if (! *arg) {
      if (! (cmd.flags() & CmdLine::QUIET)) {
         cmd.error() << "empty integer value specified." << endl ;
      }
      return  FAILURE ;
   }

   // compile the string into an integer
   result = ::strtol(arg, (char **) &ptr, 0);  // watch out for -c0xa vs -axc0!
   if (ptr == arg) {
      // do we have a valid integer?
      if (! (cmd.flags() & CmdLine::QUIET)) {
         cmd.error() << "invalid integer value \"" << arg << "\"." << endl ;
      }
      return  FAILURE ;
   }
   value = (int) result;
   arg = ptr;

   return  SUCCESS ;
}


CmdArgInt::~CmdArgInt(void)  {}

int
CmdArgInt::operator()(const char * & arg, CmdLine & cmd)
{
   return  compile(arg, cmd, val);
}


ostream &
operator<<(ostream & os, const CmdArgInt & int_arg)
{
   return  (os << (int) int_arg) ;
}

//---------------------------------------------------- Floating-point Arguments

CmdArgFloatCompiler::~CmdArgFloatCompiler(void)  {}

   // Compile a string into a floating-point value.
int
CmdArgFloatCompiler::compile(const char * & arg, CmdLine & cmd, float & value)
{
   const char * ptr = NULL ;
   double  result = 0 ;

   if (arg == NULL) {
      return  SUCCESS ;  // no value given -- nothing to do
   } else if (! *arg) {
      if (! (cmd.flags() & CmdLine::QUIET)) {
         cmd.error() << "empty floating-point value specified." << endl ;
      }
      return  FAILURE ;
   }

   result = ::strtod(arg, (char **) &ptr);  // compile the string into a float
   if (ptr == arg) {
      // do we have a valid float?
      if (! (cmd.flags() & CmdLine::QUIET)) {
         cmd.error() << "invalid floating-point value \"" << arg << "\"."
                     << endl ;
      }
      return  FAILURE ;
   }
   value = (float) result;
   arg = ptr;

   return  SUCCESS ;
}


CmdArgFloat::~CmdArgFloat(void)  {}

int
CmdArgFloat::operator()(const char * & arg, CmdLine & cmd)
{
   return  compile(arg, cmd, val);
}


ostream &
operator<<(ostream & os, const CmdArgFloat & float_arg)
{
   return  (os << (float) float_arg) ;
}

//--------------------------------------------------------- Character Argumrnts

CmdArgCharCompiler::~CmdArgCharCompiler(void)  {}

int
CmdArgCharCompiler::compile(const char * & arg, CmdLine & cmd, char & value)
{
   if (arg == NULL) {
      return  SUCCESS ;  // no value given - nothing to do
   }

   // If "arg" contains more than 1 character, then the other characters
   // are either extraneous, or they are options (bundled together).
   //
   if (*arg  &&  *(arg+1)  &&
        ((! (flags() & CmdArg::OPTION)) || (flags() & CmdArg::VALSEP)))
   {
      if (! (cmd.flags() & CmdLine::QUIET)) {
         cmd.error() << "invalid character value \"" << arg << "\"." << endl ;
      }
      return  FAILURE ;
   }

   value = *arg;
   if (*arg) {
      ++arg;
   } else {
      arg = NULL;
   }

   return  SUCCESS ;
}


CmdArgChar::~CmdArgChar(void)  {}

int
CmdArgChar::operator()(const char * & arg, CmdLine & cmd)
{
   return  compile(arg, cmd, val);
}

ostream &
operator<<(ostream & os, const CmdArgChar & char_arg)
{
   return  (os << (char) char_arg) ;
}

//------------------------------------------------------------ String Arguments

typedef  CmdArgStrCompiler::casc_string  CmdArgString ;

#ifndef __GNUG__
CmdArgString::~casc_string(void)
{
   if (is_alloc)  delete [] (char *)str;
}
#endif 

   // Copy a string (allocating storage if necessary)
void
CmdArgString::copy(unsigned  is_temporary, const char * s)
{
   if (is_alloc)  delete [] (char *)str;
   is_alloc = (is_temporary) ? 1 : 0;
   str = s;
   if (is_alloc && s) {
      char * new_s = new char[::strlen(s) + 1] ;
      (void) ::strcpy(new_s, s);
      str = new_s;
   }
}


CmdArgStrCompiler::~CmdArgStrCompiler(void)  {}

int
CmdArgStrCompiler::compile(const char  * & arg,
                           CmdLine       & cmd,
                           CmdArgString  & value)
{
   if (arg == NULL) {
      return  SUCCESS;  // no value given -- nothing to do
   }

   value.copy((cmd.flags() & CmdLine::TEMP), arg);
   arg = NULL;

   return  SUCCESS;
}


CmdArgStr::~CmdArgStr(void)  {}

int
CmdArgStr::operator()(const char * & arg, CmdLine & cmd)
{
   return  compile(arg, cmd, val);
}

ostream &
operator<<(ostream & os, const CmdArgStrCompiler::casc_string & str)
{
   return  (os << str.str) ;
}

ostream &
operator<<(ostream & os, const CmdArgStr & str_arg)
{
   return  (os << (const char *) str_arg) ;
}

//-------------------------------------------------------------- List Arguments

          //------------------- Integer List -------------------

DECLARE_FIFO_LIST(IntList, int);

struct CmdArgIntListPrivate {
   IntList       list;
   IntListArray  array;

   CmdArgIntListPrivate(void);
} ;


CmdArgIntListPrivate::CmdArgIntListPrivate(void)
   : array(list)
{
   list.self_cleaning(1);
}

   // Compile the argument into an integer and append it to the list
int
CmdArgIntList::operator()(const char * & arg, CmdLine & cmd)
{
   int  value;
   const char * save_arg = arg;
   int  rc = compile(arg, cmd, value);
   if (save_arg && (rc == SUCCESS)) {
      if (val == NULL)  val = new CmdArgIntListPrivate;
      int * new_value = new int;
      *new_value = value;
      val->list.add(new_value);
   }
   return  rc;
}

unsigned
CmdArgIntList::count(void) const
{
   return  (val) ? val->list.count() : 0 ;
}

int &
CmdArgIntList::operator[](unsigned  index)
{
   return  val->array[index];
}

CmdArgIntList::~CmdArgIntList(void) {}


          //------------------- Float List -------------------


DECLARE_FIFO_LIST(FloatList, float);

struct CmdArgFloatListPrivate {
   FloatList       list;
   FloatListArray  array;

   CmdArgFloatListPrivate(void);
} ;

CmdArgFloatListPrivate::CmdArgFloatListPrivate(void)
   : array(list)
{
   list.self_cleaning(1);
}


   // Compile the argument into a float and append it to the list
int
CmdArgFloatList::operator()(const char * & arg, CmdLine & cmd)
{
   float  value;
   const char * save_arg = arg;
   int  rc = compile(arg, cmd, value);
   if (save_arg && (rc == SUCCESS)) {
      if (val == NULL)  val = new CmdArgFloatListPrivate;
      float * new_value = new float;
      *new_value = value;
      val->list.add(new_value);
   }
   return  rc;
}

unsigned
CmdArgFloatList::count(void) const
{
   return  (val) ? val->list.count() : 0 ;
}

float &
CmdArgFloatList::operator[](unsigned  index)
{
   return  val->array[index];
}

CmdArgFloatList::~CmdArgFloatList(void) {}

          //------------------- String List -------------------

DECLARE_FIFO_LIST(StringList, CmdArgString);

struct CmdArgStrListPrivate {
   StringList       list;
   StringListArray  array;

   CmdArgStrListPrivate(void);
} ;

CmdArgStrListPrivate::CmdArgStrListPrivate(void)
   : array(list)
{
   list.self_cleaning(1);
}

int
CmdArgStrList::operator()(const char * & arg, CmdLine & cmd)
{
   CmdArgString * value = new CmdArgString ;
   const char * save_arg = arg;
   int  rc = compile(arg, cmd, *value);
   if (save_arg && (rc == SUCCESS)) {
      if (val == NULL)  val = new CmdArgStrListPrivate;
      val->list.add(value);
   } else {
      delete  value;
   }
   return  rc;
}

unsigned
CmdArgStrList::count(void) const
{
   return  (val) ? val->list.count() : 0 ;
}

CmdArgString &
CmdArgStrList::operator[](unsigned  index)
{
   return  val->array[index];
}

CmdArgStrList::~CmdArgStrList(void) {}

//----------------------------------------------------------- Boolean Arguments

CmdArgBoolCompiler::~CmdArgBoolCompiler(void)  {}

int
CmdArgBoolCompiler::compile(const char  * & arg,
                            CmdLine       & cmd,
                            unsigned      & value,
                            unsigned        default_value)
{
   if (arg == NULL) {
         // if no argument was given use the default
      value = default_value ;
   } else {
      char ch = *arg;
      const char * kwd = arg++;

         // Map the argument to the corresponding value. We will accept
         // the following (case insensitive):
         //
         //     "+", "1", "ON", or "YES"  means set the value
         //     "-", "0", "OFF", or "NO"  means clear the value
         //     "~", "^", or "!" means toggle the value
         //
         // Anything else is considered to be an argument that is NOT
         // meant for us but for some other argument so we just use the
         // default value that was supplied and return SUCCESS.
         //
      if (isupper(ch))  ch = tolower(ch);
      switch(ch) {
         case '1' :
         case '+' : value = 1 ; break;

         case '0' :
         case '-' : value = 0 ; break;

         case '~' :
         case '^' :
         case '!' : value = (! value) ; break;

         default:
            if (flags() & CmdArg::KEYWORD) {
               char ch2 = *arg;
               arg = NULL;
               if (cmd.strmatch(kwd, "yes") != CmdLine::str_NONE) {
                  value = 1 ;
                  return  SUCCESS ;
               } else if (cmd.strmatch(kwd, "no") != CmdLine::str_NONE) {
                  value = 0 ;
                  return  SUCCESS ;
               } else if (cmd.strmatch(kwd, "true") != CmdLine::str_NONE) {
                  value = 1 ;
                  return  SUCCESS ;
               } else if (cmd.strmatch(kwd, "false") != CmdLine::str_NONE) {
                  value = 0 ;
                  return  SUCCESS ;
               } else if ((ch == 'o') && (! ch2)) {
                  // ambiguous - could be "ON" or "OFF"
                  if (! (cmd.flags() & CmdLine::QUIET)) {
                     cmd.error() << "ambiguous boolean value \"" << kwd
                                 << "\"." << endl ;
                  }
                  return  FAILURE ;
               } else if (cmd.strmatch(kwd, "on") != CmdLine::str_NONE) {
                  value = 1 ;
                  return  SUCCESS ;
               } else if (cmd.strmatch(kwd, "off") != CmdLine::str_NONE) {
                  value = 0 ;
                  return  SUCCESS ;
               } else {
                  // unknown
                  if (! (cmd.flags() & CmdLine::QUIET)) {
                     cmd.error() << "unknown boolean value \"" << kwd
                                 << "\"." << endl ;
                  }
                  return  FAILURE ;
               }
            } //if keyword
            arg = kwd;  // no characters used!
            value = default_value ;
            break;
      } //switch
   } //else

   return  SUCCESS ;
}

ostream &
operator<<(ostream & os, const CmdArgBool & bool_arg)
{
   return  (os << ((int)bool_arg)) ;
}

//------------------------------------------------------------------ CmdArgBool

CmdArgBool::~CmdArgBool(void)  {}

int
CmdArgBool::operator()(const char * & arg, CmdLine & cmd)
{
   unsigned  value = val;
   int  rc = compile(arg, cmd, value, 1);
   val = value;
   return  rc;
}

//----------------------------------------------------------------- CmdArgClear

CmdArgClear::~CmdArgClear(void)  {}

int
CmdArgClear::operator()(const char * & arg, CmdLine & cmd)
{
   unsigned  value = val;
   int  rc = compile(arg, cmd, value, 0);
   val = value;
   return  rc;
}

//---------------------------------------------------------------- CmdArgToggle

CmdArgToggle::~CmdArgToggle(void)  {}

int
CmdArgToggle::operator()(const char * & arg, CmdLine & cmd)
{
   unsigned  value = val;
   int  rc = compile(arg, cmd, value, (! value));
   val = value;
   return  rc;
}

//--------------------------------------------------------------- CmdArgBoolRef

CmdArgBoolRef::~CmdArgBoolRef(void)  {}

int
CmdArgBoolRef::operator()(const char * & arg, CmdLine & cmd)
{
   unsigned  val = ref;
   int rc = ref.compile(arg, cmd, val, 1);
   ref = val;
   return  rc;
}

//-------------------------------------------------------------- CmdArgClearRef

CmdArgClearRef::~CmdArgClearRef(void)  {}

int
CmdArgClearRef::operator()(const char * & arg, CmdLine & cmd)
{
   unsigned  val = ref;
   int rc = ref.compile(arg, cmd, val, 0);
   ref = val;
   return  rc;
}

//------------------------------------------------------------- CmdArgToggleRef

CmdArgToggleRef::~CmdArgToggleRef(void)  {}

int
CmdArgToggleRef::operator()(const char * & arg, CmdLine & cmd)
{
   unsigned  val = ref;
   int rc = ref.compile(arg, cmd, val, (! val));
   ref = val;
   return  rc;
}

