/*   ncbiargs.c
* ===========================================================================
*
*                            PUBLIC DOMAIN NOTICE                          
*               National Center for Biotechnology Information
*                                                                          
*  This software/database is a "United States Government Work" under the   
*  terms of the United States Copyright Act.  It was written as part of    
*  the author's official duties as a United States Government employee and 
*  thus cannot be copyrighted.  This software/database is freely available 
*  to the public for use. The National Library of Medicine and the U.S.    
*  Government have not placed any restriction on its use or reproduction.  
*                                                                          
*  Although all reasonable efforts have been taken to ensure the accuracy  
*  and reliability of the software and data, the NLM and the U.S.          
*  Government do not and cannot warrant the performance or results that    
*  may be obtained by using this software or data. The NLM and the U.S.    
*  Government disclaim all warranties, express or implied, including       
*  warranties of performance, merchantability or fitness for any particular
*  purpose.                                                                
*                                                                          
*  Please cite the author in any work or product based on this material.   
*
* ===========================================================================
*
* File Name:  ncbiargs.c
*
* Author:  Ostell, Schuler, Vakatov
*
* Version Creation Date:   07/15/91
*
* File Description:
*     GetArgs() for console applications 
*
* Modifications:  
* --------------------------------------------------------------------------
* $Log: ncbiargs.c,v $
* Revision 6.12  2009/08/17 19:42:08  lavr
* Formatting
*
* Revision 6.11  2009/08/14 18:01:09  lavr
* Use {Get|Set}ProgramName()
*
* Revision 6.10  2006/10/19 14:57:03  lavr
* Fix repetitive arg error message to show no argument value since it's empty
*
* Revision 6.9  2006/10/18 19:15:43  lavr
* Allow repetitive (boolean) flags to increment arg's intvalue [spec.cased]
*
* Revision 6.8  2004/04/01 13:43:06  lavr
* Spell "occurred", "occurrence", and "occurring"
*
* Revision 6.7  2002/11/06 21:22:32  ucko
* Accept "--help" as a synonym for the less intuitive "-"
*
* Revision 6.6  2001/07/03 20:03:11  juran
* Don't corruptly parse argv in Mac OS.
*
* Revision 6.5  2000/06/15 20:51:41  vakatov
* Use "const" in Args code
*
* Revision 6.4  1999/03/11 21:12:23  vakatov
* Get in-sync the "printf"/"scanf"'s format and args in some places
*
* Revision 6.3  1998/01/21 20:55:43  vakatov
* Nlm_GetArgs():  fixed memory leak(in the override of default str value)
*
* Revision 6.2  1997/10/29 02:43:14  vakatov
* Type castings to pass through the C++ compiler
*
* Revision 6.1  1997/10/27 22:00:43  vakatov
* Check for the Bool, Int and Float arg. value validity;  reset all
* arguments if an error occurred
*
* Revision 5.1  1997/07/22 19:06:56  vakatov
* Initial revision: merged GetArgs()'s from former "ncbiargs.msw" and
* "ncbimain.[msw,unx,mac,vms]"
*
* ==========================================================================
*/

#include <ncbi.h>

/*****************************************************************************
*
*   Nlm_GetArgs(ap)
*   	returns user startup arguments
*
*****************************************************************************/

NLM_EXTERN Nlm_Boolean Nlm_GetArgs(const char* progname,
                                   Nlm_Int2 numargs, Nlm_ArgPtr ap)
{
  static const char* types[] = {
    NULL,
    "T/F",
    "Integer",
    "Real",
    "String",
    "File In",
    "File Out",
    "Data In",
    "Data Out"
  };

  Nlm_Boolean  okay, all_default = TRUE, range;
  Nlm_Int2     i, j;
  Nlm_ArgPtr   curarg;
  Nlm_Boolean* resolved;
  Nlm_Int4     xx_argc = Nlm_GetArgc();
  char**       xx_argv = Nlm_GetArgv();

  if (StringHasNoText(GetProgramName()))
    SetProgramName(progname);

  if (!ap  ||  numargs <= 0)
    return FALSE;

  resolved = (Nlm_Boolean*) Nlm_MemNew(numargs * sizeof(Nlm_Boolean));
  if ( !resolved )
    return FALSE;

  /* reset all args */
  curarg = ap;
  for (i = 0;  i < numargs;  i++, curarg++) {
    curarg->intvalue   = 0;
    curarg->floatvalue = 0.0;
    curarg->strvalue   = NULL;
  }

  /* set defaults */
  curarg = ap;
  for (i = 0;  i < numargs;  i++, curarg++)
    {
      if ((curarg->type < ARG_BOOLEAN) ||
          (curarg->type > ARG_DATA_OUT))
        {
          ErrPostEx(SEV_ERROR, 0,0, "Invalid Arg->type in %s", curarg->prompt);
          Nlm_MemFree( resolved );
          Nlm_FreeArgs(i, ap);
          return FALSE;
        }
      curarg->intvalue   = 0;
      curarg->floatvalue = 0.0;
      curarg->strvalue   = NULL;
      if (curarg->defaultvalue)
        {
          resolved[i] = TRUE;
          switch (curarg->type)
            {
            case ARG_BOOLEAN:
              if (TO_UPPER(*curarg->defaultvalue) == 'T')
                curarg->intvalue = 1;
              else
                curarg->intvalue = 0;
              break;
            case ARG_INT:
              curarg->intvalue = atol(curarg->defaultvalue);
              break;
            case ARG_FLOAT:
              curarg->floatvalue = atof(curarg->defaultvalue);
              break;
            case ARG_STRING:
            case ARG_FILE_IN:
            case ARG_FILE_OUT:
            case ARG_DATA_IN:
            case ARG_DATA_OUT:
              curarg->strvalue = StringSave(curarg->defaultvalue);
              break;
            }
        }
      else if (curarg->optional == FALSE)
        all_default = FALSE;    /* must have some arguments */
    }

  /**** show usage if no args on command line ***/
  if (xx_argc == 1  &&  all_default)   /* no args ok */
    {
      Nlm_MemFree( resolved );
      return TRUE;
    }

  if (xx_argc == 1  ||  *(xx_argv[1]+1) == '\0'
      ||  !strcmp(xx_argv[1], "--help"))
    {
      printf("\n%s   arguments:\n\n", progname);
      curarg = ap;

      for (i = 0, j = 0;  i < numargs;  i++, j++, curarg++)
        {
          printf("  -%c  %s [%s]", curarg->tag, curarg->prompt,
                 types[curarg->type]);
          if (curarg->optional)
            printf("  Optional");
          printf("\n");
          if (curarg->defaultvalue)
            printf("    default = %s\n", curarg->defaultvalue);
          if (curarg->from  ||  curarg->to)
            {
              if (curarg->type == ARG_DATA_IN  ||
                  curarg->type == ARG_DATA_OUT)
                printf("    Data Type = %s\n", curarg->from);
              else if (curarg->type == ARG_BOOLEAN  ||
                       curarg->type == ARG_INT      ||
                       curarg->type == ARG_FLOAT)
                {
                  printf("    range from %s to %s\n", 
                         (curarg->from ? curarg->from: "?"),
                         (curarg->to   ? curarg->to  : "?"));
                }
            }
        }

      printf("\n");
      fflush (stdout);
      Nlm_MemFree( resolved );
      Nlm_FreeArgs(numargs, ap);
#ifdef COMP_MPW
      Message(MSG_OK, "To exit: ");
#endif
      return FALSE;
    }


  /* Parse the arguments */
  for (i = 1; i < xx_argc; i++)
    {
      const char* arg = xx_argv[i];
      if (*arg != '-')
        {
          ErrPostEx(SEV_ERROR, 0, 0, "Arguments must start with '-' (the offending argument #%d was: '%s')", (int)i, arg);
          Nlm_MemFree( resolved );
          Nlm_FreeArgs(numargs, ap);
          return FALSE;
        }
      arg++;
      curarg = ap;
      for (j = 0; j < numargs; j++, curarg++)
        {
          if (*arg == curarg->tag)
            break;
        }
      if (j == numargs)
        {
          ErrPostEx(SEV_ERROR, 0, 0, "Invalid argument: %s", xx_argv[i]);
          Nlm_MemFree( resolved );
          Nlm_FreeArgs(numargs, ap);
          return FALSE;
        }
      arg++;

      if (*arg == '\0')
        {
          Nlm_Boolean ok = FALSE;
          if ((i + 1) < xx_argc)  /* argument comes after space */
            {
              if (*xx_argv[i + 1] == '-')
                {
                  char tmp = *(xx_argv[i+1]+1);
                  if ((curarg->type == ARG_INT  ||  curarg->type == ARG_FLOAT)
                      &&  (tmp == '.'  ||  IS_DIGIT(tmp)))
                    ok = TRUE;
                }
              else
                ok = TRUE;

              if ( ok ) {
                i++;
                arg = xx_argv[i];
              }
            }

          if (!ok  &&  curarg->type != ARG_BOOLEAN)
            {
              ErrPostEx(SEV_ERROR, 0, 0,
                        "No argument value given for %s", curarg->prompt);
              Nlm_MemFree( resolved );
              Nlm_FreeArgs(numargs, ap);
              return FALSE;
            }
        }

      resolved[j] = TRUE;
      switch (curarg->type)
        {
        case ARG_BOOLEAN:
          if (TO_UPPER(*arg) == 'T')
            curarg->intvalue = 1;
          else if (TO_UPPER(*arg) == 'F')
            curarg->intvalue = 0;
          else if (*arg == '\0')
            {
              long idef, ifrom, ito;
              if (curarg->from  &&  curarg->to  &&
                  sscanf(curarg->defaultvalue, "%ld", &idef)  > 0  &&
                  sscanf(curarg->from,         "%ld", &ifrom) > 0  &&
                  sscanf(curarg->to,           "%ld", &ito)   > 0  &&
                  idef == 0  &&  ifrom == 0  &&  ito > 1)
                {
                  if (curarg->intvalue >= ito)
                    {
                      ErrPostEx(SEV_ERROR, 0, 0,
                                "%s allowed no more than %s times",
                                curarg->prompt, curarg->to);
                      Nlm_MemFree( resolved );
                      Nlm_FreeArgs(numargs, ap);
                      return FALSE;
                    }
                  else
                    curarg->intvalue++;
                }
              else
                curarg->intvalue = 1;
            }
          else
            {
              ErrPostEx(SEV_ERROR, 0, 0,
                        "%s [%s] must be one of {'T', 't', 'F', 'f'}"
                        " or omitted", curarg->prompt, arg);
              Nlm_MemFree( resolved );
              Nlm_FreeArgs(numargs, ap);
              return FALSE;
            }
          break;

        case ARG_INT: {
          long val;
          range = TRUE;
          if (sscanf(arg, "%ld", &val) <= 0)
            range = FALSE;
          curarg->intvalue = val;
          if (range && curarg->from && curarg->intvalue < atol(curarg->from))
            {
                range = FALSE;
            }
          if (range && curarg->to && curarg->intvalue > atol(curarg->to))
            {
                range = FALSE;
            }
          if ( !range )
            {
              ErrPostEx(SEV_ERROR, 0, 0,
                        "%s [%s] is bad or out of range [%s to %s]",
                        curarg->prompt, arg,
                        curarg->from ? curarg->from : "?",
                        curarg->to   ? curarg->to   : "?");
              Nlm_MemFree( resolved );
              Nlm_FreeArgs(numargs, ap);
              return FALSE;
            }
          break;
        }

        case ARG_FLOAT: {
          double val;
          range = TRUE;
          if (sscanf(arg, "%lf", &val) <= 0)
            range = FALSE;
          curarg->floatvalue = val;
          if (range && curarg->from && curarg->floatvalue < atof(curarg->from))
            {
                range = FALSE;
            }
          if (range && curarg->to && curarg->floatvalue > atof(curarg->to))
            {
                range = FALSE;
            }
          if ( !range )
            {
              ErrPostEx(SEV_ERROR, 0, 0,
                        "%s [%s] is bad or out of range [%s to %s]",
                        curarg->prompt, arg,
                        curarg->from ? curarg->from : "?",
                        curarg->to   ? curarg->to   : "?");
              Nlm_MemFree( resolved );
              Nlm_FreeArgs(numargs, ap);
              return FALSE;
            }
          break;
        }

        case ARG_STRING:
        case ARG_FILE_IN:
        case ARG_FILE_OUT:
        case ARG_DATA_IN:
        case ARG_DATA_OUT:
          if ( curarg->strvalue )
            MemFree(curarg->strvalue);
          curarg->strvalue = StringSave(arg);
          break;
        }    /*** end switch ****/
    }

  okay = TRUE;
  curarg = ap;
  for (i = 0;  i < numargs;  i++, curarg++)
    {
      if (!curarg->optional  &&  !resolved[i])
        {
          ErrPostEx(SEV_ERROR, 0, 0,
                    "%s was not given an argument", curarg->prompt);
          okay = FALSE;
        }
    }

  Nlm_MemFree( resolved );
  if ( !okay )
    Nlm_FreeArgs(numargs, ap);

  return okay;
}


NLM_EXTERN Nlm_Boolean Nlm_GetArgsSilent(const char* progname,
                                         Nlm_Int2 numargs, Nlm_ArgPtr ap)
{
  return Nlm_GetArgs(progname, numargs, ap);
}

