/*****************************************************************************/
/*!
 * \file command_line_flags.h
 * 
 * Author: Sergey Berezin
 * 
 * Created: Mon Feb 10 16:22:00 2003
 *
 * <hr>
 * Copyright (C) 2003 by the Board of Trustees of Leland Stanford
 * Junior University and by New York University. 
 *
 * License to use, copy, modify, sell and/or distribute this software
 * and its documentation for any purpose is hereby granted without
 * royalty, subject to the terms and conditions defined in the \ref
 * LICENSE file provided with this distribution.  In particular:
 *
 * - The above copyright notice and this permission notice must appear
 * in all copies of the software and related documentation.
 *
 * - THE SOFTWARE IS PROVIDED "AS-IS", WITHOUT ANY WARRANTIES,
 * EXPRESSED OR IMPLIED.  USE IT AT YOUR OWN RISK.
 * 
 * <hr>
 * 
 */
/*****************************************************************************/

#ifndef _CVC_lite__command_line_flags_h_
#define _CVC_lite__command_line_flags_h_

#include <sstream>
#include <vector>
#include <map>
#include "command_line_exception.h"
#include "debug.h"

namespace CVCL {

  //! Different types of command line flags
  typedef enum {
    CLFLAG_NULL, 
    CLFLAG_BOOL,
    CLFLAG_INT,
    CLFLAG_STRING,
    CLFLAG_STRVEC //!< Vector of pair<string, bool>
  } CLFlagType;

  /*!
    Class CLFlag (for Command Line Flag)

    Author: Sergey Berezin
    
    Date: Fri May 30 14:10:48 2003
    
    This class implements a data structure to hold a value of a single
    command line flag.
  */

class CLFlag {
 private:
  //! Type of the argument
  CLFlagType d_tp;
  //! The argument
  union {
    bool b;
    int i;
    std::string* s;
    std::vector<std::pair<std::string,bool> >* sv;
  } d_data;
  //! This tag is set to true when the flag is assigned a new value
  bool d_modified;
  //! Help string
  std::string d_help;
 public:
  //! Constructor for a boolean flag
  CLFlag(bool b, const std::string& help)
    : d_tp(CLFLAG_BOOL), d_modified(0), d_help(help)
    { d_data.b = b; }
  //! Constructor for an integer flag
  CLFlag(int i, const std::string& help)
    : d_tp(CLFLAG_INT), d_modified(0), d_help(help)
    { d_data.i = i; }
  //! Constructor for a string flag
  CLFlag(const std::string& s, const std::string& help)
    : d_tp(CLFLAG_STRING), d_modified(0), d_help(help) {
    d_data.s = new std::string(s);
  }
  //! Constructor for a string flag from char*
  CLFlag(const char* s, const std::string& help)
    : d_tp(CLFLAG_STRING), d_modified(0), d_help(help) {
    d_data.s = new std::string((char*)s);
  }
  //! Constructor for a vector flag
  CLFlag(const std::vector<std::pair<std::string,bool> >& sv,
	 const std::string& help)
    : d_tp(CLFLAG_STRVEC), d_modified(0), d_help(help) {
    d_data.sv = new std::vector<std::pair<std::string,bool> >(sv);
  }
  //! Default constructor
  CLFlag(): d_tp(CLFLAG_NULL), d_modified(0), d_help("Undefined flag") { }
  //! Copy constructor
  CLFlag(const CLFlag& f)
    : d_tp(f.d_tp), d_modified(f.d_modified), d_help(f.d_help) {
    switch(d_tp) {
    case CLFLAG_STRING:
      d_data.s = new std::string(*f.d_data.s); break;
    case CLFLAG_STRVEC:
      d_data.sv = new std::vector<std::pair<std::string,bool> >(*f.d_data.sv); break;
    default: d_data = f.d_data;
    }
  }
  //! Destructor
  ~CLFlag() {
    switch(d_tp) {
    case CLFLAG_STRING: delete d_data.s; break;
    case CLFLAG_STRVEC: delete d_data.sv; break;
    default: break;// Nothing to do
    }
  }
  //! Assignment from another flag
  CLFlag& operator=(const CLFlag& f) {
    if(this == &f) return *this; // Self-assignment
    // Try to preserve the existing heap objects if possible
    if(d_tp == f.d_tp) {
      switch(d_tp) {
      case CLFLAG_STRING: *d_data.s = *f.d_data.s; break;
      case CLFLAG_STRVEC: *d_data.sv = *f.d_data.sv; break;
      default: d_data = f.d_data;
      }
    } else {
      switch(d_tp) {
      case CLFLAG_STRING: delete d_data.s; break;
      case CLFLAG_STRVEC: delete d_data.sv; break;
      default: break;
      }
      switch(f.d_tp) {
      case CLFLAG_STRING: d_data.s = new std::string(*f.d_data.s); break;
      case CLFLAG_STRVEC:
	d_data.sv=new std::vector<std::pair<std::string,bool> >(*f.d_data.sv);
	break;
      default: d_data = f.d_data;
      }
    }
    d_tp = f.d_tp;
    d_modified = f.d_modified;
    d_help = f.d_help;
    return *this;
  }
  //! Assignment of a boolean value
  /*! The flag must already have the right type */
  CLFlag& operator=(bool b) {
    DebugAssert(d_tp == CLFLAG_BOOL, "");
    d_data.b = b;
    d_modified = true;
    return *this;
  }
  //! Assignment of an integer value
  /*! The flag must already have the right type */
  CLFlag& operator=(int i) {
    DebugAssert(d_tp == CLFLAG_INT, "");
    d_data.i = i;
    d_modified = true;
    return *this;
  }
  //! Assignment of a string value
  /*! The flag must already have a string type. */
  CLFlag& operator=(const std::string& s) {
    DebugAssert(d_tp == CLFLAG_STRING, "");
    *d_data.s = s;
    d_modified = true;
    return *this;
  }
  //! Assignment of an string value from char*
  /*! The flag must already have a string type. */
  CLFlag& operator=(const char* s) {
    DebugAssert(d_tp == CLFLAG_STRING, "");
    *d_data.s = s;
    d_modified = true;
    return *this;
  }
  //! Assignment of a string value with a boolean tag to a vector flag
  /*! The flag must already have a vector type.  The pair of
    <string,bool> will be appended to the vector. */
  CLFlag& operator=(const std::pair<std::string,bool>& p) {
    DebugAssert(d_tp == CLFLAG_STRVEC, "");
    d_data.sv->push_back(p);
    d_modified = true;
    return *this;
  }
  //! Assignment of a vector value
  /*! The flag must already have a vector type. */
  CLFlag& operator=(const std::vector<std::pair<std::string,bool> >& sv) {
    DebugAssert(d_tp == CLFLAG_STRVEC, "");
    *d_data.sv = sv;
    d_modified = true;
    return *this;
  }
  // Accessor methods
  //! Return the type of the flag
  CLFlagType getType() const { return d_tp; }
  /*! @brief Return true if the flag was modified from the default
    value (e.g. set on the command line) */
  bool modified() const { return d_modified; }

  // The value accessors return a reference.  For the system-wide
  // flags, this reference will remain valid throughout the run of the
  // program, even if the flag's value changes.  So, the reference can
  // be cached, and the value can be checked directly (which is more
  // efficient).
  const bool& getBool() const {
    DebugAssert(d_tp == CLFLAG_BOOL, "CLFlag::getBool: not a boolean flag");
    return d_data.b;
  }

  const int& getInt() const {
    DebugAssert(d_tp == CLFLAG_INT, "CLFlag::getInt: not an integer flag");
    return d_data.i;
  }

  const std::string& getString() const {
    DebugAssert(d_tp == CLFLAG_STRING,
		"CLFlag::getString: not a string flag");
    return *d_data.s;
  }

  const std::vector<std::pair<std::string,bool> >& getStrVec() const {
    DebugAssert(d_tp == CLFLAG_STRVEC,
		"CLFlag::getStrVec: not a string vector flag");
    return *d_data.sv;
  }

  const std::string& getHelp() const {
    return d_help;
  }

}; // end of class CLFlag

///////////////////////////////////////////////////////////////////////
// Class CLFlag (for Command Line Flag)
// 
// Author: Sergey Berezin
// Date: Fri May 30 14:10:48 2003
// 
// Database of command line flags.
///////////////////////////////////////////////////////////////////////

class CLFlags {
 private:
  typedef std::map<std::string, CLFlag> CharMap;
  CharMap d_map;

  // Private methods

  // Retrieve an existing flag for modification.  The 'name' must be a
  // full name of an existing flag.
  CLFlag& getFlag0(const std::string& name) {
    DebugAssert(d_map.count(name) > 0,
		"getFlag0("+name+"): there are no flags with this name");
    return (*d_map.find(name)).second;
  }
 public:
  // Public methods
  // Add a new flag.  The name must be a complete flag name.
  void addFlag(const std::string& name, const CLFlag& f) {
    d_map[name] = f;
  }
  // Count how many flags match the name prefix
  size_t countFlags(const std::string& name) const {
    size_t res(0), len(name.size());
    for(CharMap::const_iterator i=d_map.begin(), iend=d_map.end();
	i!=iend; ++i) {
      if(strncmp(name.c_str(), (*i).first.c_str(), len) == 0) res++;
    }
    return res;
  }
  // Match the name prefix and add all the matching names to the vector
  size_t countFlags(const std::string& name,
		    std::vector<std::string>& names) const {
    size_t res(0), len(name.size());
    for(CharMap::const_iterator i=d_map.begin(), iend=d_map.end();
	i!=iend; ++i) {
      if(strncmp(name.c_str(), (*i).first.c_str(), len) == 0) {
	names.push_back((*i).first);
	res++;
      }
    }
    return res;
  }
  // Retrieve an existing flag.  The 'name' must be a full name of an
  // existing flag.
  const CLFlag& getFlag(const std::string& name) const {
    DebugAssert(d_map.count(name) > 0,
		"getFlag("+name+"): there are no flags with this name");
    return (*d_map.find(name)).second;
  }

  const CLFlag& operator[](const std::string& name) const {
    return getFlag(name);
  }

  // Setting the flag to a new value, but preserving the help string.
  // The 'name' prefix must uniquely resolve to an existing flag.
  void setFlag(const std::string& name, const CLFlag& f) {
    CLFlag& oldF(getFlag0(name));
    DebugAssert(oldF.getType() == f.getType(),
		"setFlag("+name+"): flag type doesn't match");
    oldF = f;
  }

  // Variants of setFlag for all the types
  void setFlag(const std::string& name, bool b) { getFlag0(name) = b; }
  void setFlag(const std::string& name, int i) { getFlag0(name) = i; }
  void setFlag(const std::string& name, const std::string& s)
    { getFlag0(name) = s; }
  void setFlag(const std::string& name, const char* s)
    { getFlag0(name) = s; }
  void setFlag(const std::string& name, const std::pair<std::string,bool>& p)
    { getFlag0(name) = p; }
  void setFlag(const std::string& name,
	       const std::vector<std::pair<std::string,bool> >& sv)
    { getFlag0(name) = sv; }

}; // end of class CLFlags

} // end of namespace CVCL

#endif
