/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

    $Log$
    Revision 1.3  2005/11/29 12:39:42  marc
    make it compilable with gcc 4.0.2 and newer doxygen

    Revision 1.2  2005/01/28 07:49:32  marc
    Save configuration using file.hpp

    Revision 1.1  2005/01/07 00:31:38  marc
    initial version


    */
#ifndef __MRW_CONFIGFILE_HPP__
#define __MRW_CONFIGFILE_HPP__

#include <string>
#include <map>

namespace mrw {

  /** @defgroup config Configuration File Handler

      @pre \#include <mrw/configfile.hpp>

      Read configuration parameters from a file.

      @section configSyntax Configuration File Syntax

      @subsection configSyntaxSection Sections: [Section]

      Syntax (regular expression):
      <code>\\[([^[=#]*)\\]</code>
      
      A configuration file consists of none or more sections. The
      sections start with tag <code>[Section&nbsp;Name]</code>, where
      <code>Section&nbsp;Name</code> is the name of the section.
      The default section has an empty name.

      @subsection configSyntaxVariables Variables: name = value

      Syntax (regular expression):
      <code>^[^[\\n\\t&nbsp;]*([^[=#\\n]]+)[\\n\\t&nbsp;]*=[\\n\\t&nbsp;]*([^[=#]]*)[\\n\\t&nbsp;]*</code>
      or
      <code>^[^[\\n\\t&nbsp;]*([^[=#\\n]]+)[\\n\\t&nbsp;]*=[\\n\\t&nbsp;]*("[^"]]*")[\\n\\t&nbsp;]*</code>
      or
      <code>^[^[\\n\\t&nbsp;]*([^[=#\\n]]+)[\\n\\t&nbsp;]*=[\\n\\t&nbsp;]*('[^']]*')[\\n\\t&nbsp;]*</code>

      In the section, there are variable definitions in the form of
      <code>name=value</code>, where <code>name</code> is the name of
      the variable and <code>value</code> is it's contents. White
      spaces at the begin or end of the name and the value are
      stripped. Whitespaces inside a name or value remain
      unchanged. Please note that a variable name starts at the begoin
      of line and ends before the equal sign, while the contents
      starts after the equal sign and ends before the next token
      starts. If you need a token inside a value, or a variable must
      start or end with white spaces, you can enclose it in either
      <code>"</code> or <code>'</code> quotes. If a value is quoted
      and the quoting is not terminated, then the contents is taken up
      to the end of file. Please note that the same quote must not
      occur inside the value. All possible tokens are: <code>[</code>,
      <code>#</code> and <code>=</code>. All other characters are not
      treated in a special way.

      @subsection configSyntaxComments Comments: #

      Comments start with <code>#</code> and end at the end of the
      line.

      @subsection configSyntaxSpecial Special Characters \\n, [, ], #, =, ", '

      I have chosen a syntax that is downwards compatible with the
      common Unix configuration file syntax, but with a grammar as
      loose as possible and with as few restrictions as
      possible. Special characters have only special meanings in
      certain contexts. In other contexts, they can be used with no
      restrictions.

      The following characters are treated in a special way:

      - <code>[</code> and <code>]</code> enclose a section name. A
      section name may contain any arbitrary character, including new
      line and white spaces, except a <code>]</code>.

      - <code>\\n</code> (begin of line or file) and <code>=</code>
      enclose a variable name. In other circumstances (except in
      comments), a new line has no special meaning. A <code>=</code>
      inside a value is allowed only if the value is quoted. A
      variable name may contain any arbitrary character, including
      white spaces, except a new line or <code>=</code>.

      - <code>=</code> and one out of <code>[</code>, <code>#</code>
      and the begin of a line that contains <code>=</code> enclose a
      variable contents if the contents is not quoted. A variable's
      contents may contain any arbitrary character, including new line
      and white spaces, except a <code>]</code>, <code>#</code> or the
      begin of a line that contains <code>=</code>.

      - <code>"</code> or <code>'</code> may enclose a variables
      contents. These characters have only a special meaning, if a
      variable's contents starts with one of them. Then the variable's
      contents may contain any arbitrary character, except the same
      quote itself, which marks the end of the contents.

      - <code>#</code> and <code>\\n</code> (end of line or file)
      enclose a comment. A variable name may contain any arbitrary
      character, including white spaces, except a new line.

      @subsection configSyntaxRestrictions Restrictions

      @attention Due to the actual syntax, it is impossible that a
      variable's contents contains the following combination:
      <code>"</code> @b and <code>'</code> @b and one out of
      <code>[</code>, <code>=</code> and <code>#</code>

      @attention If a variable's contents contains heading or trailing
      white spaces or one out of <code>[</code>, <code>=</code> and
      <code>#</code>, then it must be quoted either with
      <code>"</code> or with <code>'</code>.

      @section configFile Example File

      @subsection configFileSamp1 Nice Example

      @verbatim
      [Section 1]
      name1 = value1
      name2 = value2
      [Section 2]
      name1 = value1
      name2 = value2
      @endverbatim

      @subsection configFileSamp2 Enhanced Example

      @verbatim
      # this is in the global section, named ""
      this is a variable name = this is a variable contents

      [Global] # start of section "Global"

        multi line text = this is a text
      that is longer than one
                     line!

      1234 = "var=17" # if you need an equal inside a text, enclose it
      # either in " or in '
      @endverbatim

      @section configFileCode Code Sample

      @code
      try {
        const char* home(getenv("HOME"));
        mrw::ConfigFileReader config(home?home+"/.myprogram":".myprogram");
        std::string userName(config("User Info", "Name"));
      } catch (...) { // file read or evaluation error
      }
      @endcode
      */
  //@{

  //============================================================================
  /** @brief Parse configuration file and access the contents.
      
      Parse a configuration file that consists of sections containing
      variables with values. This class does not store anything in the
      file, even if variables have changed.
      
      @see If you want %to save changes %to the file, use @ref ConfigFileWriter.
      @see For details, see @ref configSyntax.
      */
  class ConfigFileReader {

      //............................................................... typedefs
    public:

      //------------------------------------------------------------------ Value
      /// A configuration file value.
      class Value {

          //............................................................ methods
        public:
      
          /** @brief Get the value string.
          
              Get the value string. Trailing and leading white spaces have
              been truncated. If the variable was quoted, quotes have been
              removed.
          
              @return the variable's value as string
              */
          operator const std::string&() const throw() {
            return value;
          }
      
          /** @brief Get the value string.
          
              Alternative access to the variable's contents. Trailing and
              leading white spaces have been truncated. If the variable
              was quoted, quotes have been removed.

              @return the variable's value as string
              */
          const std::string& operator()() const throw() {
            return value;
          }
      
          /** @brief Assign a new value.

              Assign a new value. The new value is lost if you use the
              ConfigFileReader, but it is stored in the destructor, if you
              use the ConfigFileWriter instead.
              */
          Value& operator=(const std::string& newValue) throw() {
            changed = true;
            value = newValue;
            return *this;
          }
      
          /// Compare the value to a string.
          bool operator==(const std::string& o) const throw() {
            return o==value;
          }
      
          /// Compare a string to the value.
          friend bool operator==(const std::string& o, const Value& v) throw() {
            return v==o;
          }
          
          //............................................................ methods
        protected:
      
          friend class ConfigFileReader;
          friend class ConfigFileWriter;
      
          Value(const std::string& v, std::string::size_type s,
                std::string::size_type e) throw():
            value(v), changed(false), start(s), end(e) {}
          
          Value(const std::string& v) throw():
            value(v), changed(true), start(0), end(0) {
          }

          //.......................................................... variables
        protected:
          
          std::string value;
          bool changed;
          std::string::size_type start;
          std::string::size_type end;

          //............................................................ methods
        private:
      
          Value(); // not implemented, must be initialized
          Value& operator=(const Value&); // not implemented, no assignment
      
      };
      //------------------------------------------------------------------------

      //................................................................ methods
    public:
      
      /** @brief Uninitialized construction.
          
          Uninitialized construction. Use this constructor, if you
          want to call @ref load() later.
          */
      ConfigFileReader() throw() {}
      
      /** @brief Parse a file at construction.

          This should be the normal case: load and parse the file at
          construction.

          @param filename the name of the file to read
          @throw mrw::invalid_argument
                  - if the file does not exist
                  - if reading the file fails
                  - if there is an unterminated section name in the file
                  - if there is an empty variable name in the file
          */
      ConfigFileReader(const std::string& filename) throw(std::exception):
        _filename(filename) {
        reload();
      }
      
      /// Copy construct from other ConfigFileReader.
      ConfigFileReader(const ConfigFileReader& o) throw():
          _filename(o._filename), _values(o._values) {
      }
      
      virtual ~ConfigFileReader() throw() {}
      
      /// Copy from other ConfigFileReader.
      ConfigFileReader& operator=(const ConfigFileReader& o) {
        _filename = o._filename;
        _values = o._values;
        return *this;
      }
      
      /** @brief Access a value read from the file.

          Access a value read from the file.

          @param section the section in the configuration file
          @param name the variable name
          @param default_ the default value that is set, if the variable is
                          not configured
          @return the Value you are looking for
          */
      Value& operator()(const std::string& section, const std::string& name,
                        const std::string& default_)
        throw(std::bad_exception);
      
      /** @brief Load and parse a new file.

          Load and parse a new file.

          @param filename the name of the file to load
          @throw mrw::invalid_argument
                  - if the file does not exist
                  - if reading the file fails
                  - if there is an unterminated section name in the file
                  - if there is an empty variable name in the file
          */
      ConfigFileReader& load(const std::string& filename) throw(std::exception);
      
      /** @brief Reload the last file again.

          Reload the last file again.

          @throw mrw::invalid_argument
                  - if the file does not exist
                  - if reading the file fails
                  - if there is an unterminated section name in the file
                  - if there is an empty variable name in the file
          */
      virtual ConfigFileReader& reload() throw(std::exception);

      //............................................................... typedefs
    protected:
      
      typedef std::map< std::string, std::map<std::string, Value> > Values;
      typedef std::map<std::string, std::string::size_type> Sections;

      //.............................................................. variables
    protected:
      
      std::string _filename;
      Values _values;
      Sections _sections;

      //................................................................ methods
    protected:

      ConfigFileReader& parse(const std::string& file) throw(std::exception);
      
      std::string parseSection(const std::string& file,
                               std::string::size_type& pos)
        const throw(std::exception);
      
      Values::mapped_type::value_type parseValue(const std::string& file,
                                                 std::string::size_type& pos)
        const throw(std::exception);
      
      std::string parseComment(const std::string& file,
                               std::string::size_type& pos)
        const throw(std::bad_exception);
  };

  //============================================================================
  /** @brief Parse configuration file and offer read&nbsp;/ write
             access to the contents.
      
      Parse a configuration file that consists of sections containing
      variables with values. This class stores the file in the destructor
      if variables have changed.
      
      @see If you want %to leave the file untouched, use @ref ConfigFileReader.
      @see For details, see @ref configSyntax.
      */
  class ConfigFileWriter: virtual public ConfigFileReader {

      //................................................................ methods
    public:
      
      /** @brief Uninitialized construction.

          @copydoc ConfigFileReader()
          */
      ConfigFileWriter() throw() {}
      
      /** @brief Parse a file at construction.

          @copydoc ConfigFileReader(const std::string&)
          */
      ConfigFileWriter(const std::string& filename) throw(std::exception) {
        load(filename);
      }
      
      /// Copy construct from other ConfigFileWriter.
      ConfigFileWriter(const ConfigFileWriter& o) throw():
        ConfigFileReader(o) {
      }
      
      /// The file is stored at destruction.
      virtual ~ConfigFileWriter() throw() {
        save();
      }
      
      /// Copy from other ConfigFileWriter.
      ConfigFileWriter& operator=(const ConfigFileWriter& o) {
        ConfigFileReader::operator=(o);
        return *this;
      }
      
      // GNU g++ prior to 3.4 does not implement covariant returns
#     if __GNUC__ == 3 && __GNUC_MINOR__ < 4
      /** @brief Reload the last file again.

          @copydoc mrw::ConfigFileReader::reload()

          @bug Bug in GNU g++ compiler:
               return type should be <code>ConfigFileWriter&</code>,
               but the compiler sais:<br>
               <code>../mrw/configfile.hpp:295:
               sorry, unimplemented: adjusting pointers for
               covariant returns</code><br>
               The problem is fixed since GNU g++ 3.4, so the signature
               will be changed as soon as you upgrade to the new compiler
               version.
          */
      virtual ConfigFileReader& reload() throw(std::exception);
#     else
      /** @brief Reload the last file again.

          @copydoc mrw::ConfigFileReader::reload()
          */
      virtual ConfigFileWriter& reload() throw(std::exception);
#     endif
      
      /** @brief Saves changes back to the file.

          All changed parameters are stored in the configuration
          file. The rest of the file is left untouched.
          */
      ConfigFileWriter& save() throw(std::exception);

      //.............................................................. variables
    protected:
      
      std::string _file;
      
  };
  
  //@}
}
#endif
