/**
 * @file cfgfile.h
 *
 * ConfigFile - Class for maintaining a configuration file similar to
 * INI-files.
 *
 * Copyright: 2005 Thomas Wintergerst. All rights reserved.
 *
 * $Id: cfgfile.h,v 1.1.2.1 2006/02/28 21:35:44 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     Common source files for c4b tools $
 * @date        06.05.2005
 * @author      "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * </p>
 */

#ifndef CFGFILE_H
#define CFGFILE_H

#include <string>
#include <map>
#include <iterator>
#include <sstream>

#ifndef ICLESS_H
#  include "icless.h"
#endif





// === Public declarations ===============================================





// === Class declarations ================================================





/**
 * Class for maintaining a configuration file similar to INI-files.
 *
 * Objects of this class can be used to handle configuration files that are
 * similar to the well known INI files. The difference to these file types is
 * that the files supported by this class may not only contain simple sections.
 * Instead every section may contain a sub-section. The hierarchy spanned is
 * declared by using section names like regular path names for the file system.
 *
 * Example:
 *
 * @code
 *
 * [/global]
 * glob-val = xyz
 *
 * [/global/not-so-global]
 * another-val = abc
 *
 * @endcode
 *
 * The above example defines the section "global" and its sub-section
 * "not-so-global". The leading separator character may be left out, it will
 * automatically be added for internal use.
 *
 * It is possible to iterate through the whole section and sub-section set or
 * to iterate only through one single layer within a section. E.g. when
 * requesting a section iterator through calling the member function
 * SectionBegin() one either specifies NULL for the whole hierarchy or one
 * specifies the path name for which to enumerate all direct sub-sections.
 *
 * Every section can contain key/value assignments. There (nearly) is no limit
 * on how to define a key or a value. This class handles both as simple strings
 * delimited by white space. The parsing algorithm simply searches for the "="
 * character separating the key from its value. The string before this
 * character is treated as the key, everything behind is the value. The only
 * thing to consider is that whitespace before the key, sourrounding the "="
 * and behind the value string is discarded.
 *
 * Comments may be inserted into the configuration file. Every line starting
 * with a "#" or ";" is assumed to be a comment. When reading a configuration
 * file all comments are preserved. They are linked to the section or key/value
 * line directly following it. I.e. all comment lines right above a section
 * definition is assumed to be a comment for this section. The same is true for
 * comments just above a key/value line. When writing the configuration
 * database to a new file or when overwriting the existing one, the comments
 * found will be written just above the lines they are assumed to belong to.
 *
 * When adding new sections or key/value entries to an existing database one
 * can specify a comment for the new data record. The same is true for
 * modifying existing entries. But note that old comments are discarded if a
 * section or key/value entry is removed or a key/value pair is overwritten.
 *
 * @todo Maybe one needs some value strings that contain whitespace at the
 *       front or at the end. To support this one has to introduce an escape
 *       mechanism like double quotes and/or backspaces.
 */
class CConfigFile
{
   public:
      /**
       * Class for handling a section within the database.
       */
      class CSection
      {
         /// Only a friend can create a new section object (private
         /// constructor).
         friend class CConfigFile;
         
         public:
            /**
             * Get the short name of the section (last path component).
             */
            const std::string &GetShortName (void) const;
            
            /**
             * Get the full path of the section.
             */
            const std::string &GetFullName (void) const;
            
            /**
             * Get the comment assigned to the section.
             */
            const std::string &GetComment (void) const;
            
            /**
             * Set the comment assigned to the section.
             */
            void SetComment
               (const std::string &str);

            /**
             * Check existing value.
             */
            bool KeyExists
               (const std::string &strKey) const;

            /**
             * Get the value for a given key as a string.
             */
            std::string GetKeyValueAsString
               (const std::string &strKey) const;

            /**
             * Get the value for a given key as a signed numeric value.
             */
            template<class T>
            T GetKeyValueAsSignedNumber
               (const std::string &strKey) const;

            /**
             * Get the value for a given key as an unsigned numeric value.
             */
            template<class T>
            T GetKeyValueAsUnsignedNumber
               (const std::string &strKey) const;

            /**
             * Get the comment assigned to a given key.
             */
            std::string GetKeyComment
               (const std::string &strKey) const;

            /**
             * Add or update a key/value pair for this section.
             */
            template<class T>
            void AddKeyValuePair
               (const std::string &strKey,
                T                  tValue,
                const std::string &strComment = std::string ());
         
         
         
         private:
            /// The parent section of this section. The main section has no
            /// parent.
            CSection *m_pParent;
            
            /// The reference to the modified flag of the surrounding
            /// configuration database.
            bool &m_fModified;
            
            /// The full path name for this section.
            std::string m_strFullName;
            
            /// The short name for this section, i.e. its basename.
            std::string m_strShortName;
            
            /// The comment associated with this section.
            std::string m_strComment;
            
            /// The type of information stored for a key/value pair.
            struct Value_t
            {
               std::string m_strKey;
               std::string m_strValue;
               std::string m_strComment;
               
               Value_t *m_pPrev;
               Value_t *m_pNext;
               
               Value_t
                  (const std::string &strKey,
                   const std::string &strValue,
                   const std::string &strComment):
                  m_strKey (strKey),
                  m_strValue (strValue),
                  m_strComment (strComment),
                  m_pPrev (NULL),
                  m_pNext (NULL)
                  { }
                  
            };
            
            /// The short name of the internally maintained map of key/value
            /// assignments.
            typedef std::map<std::string, Value_t *, CIcLess> CKeyValueMap;
            
            /// The internally maintained map of key/value pairs belonging to
            /// this section.
            CKeyValueMap m_mapKV;
            
            /// Pointer to the next section on the same level.
            CSection *m_pPrev;
            
            /// Pointer to the previous section on the same level.
            CSection *m_pNext;
            
            /// Pointer to the first sub-section.
            CSection *m_pFirstSub;
            
            /// Pointer to the last sub-section.
            CSection *m_pLastSub;
            
            /// Pointer to the first value record of this section.
            Value_t *m_pFirstValue;
            
            /// Pointer to the last value record of this section.
            Value_t *m_pLastValue;
            
            
            
            /**
             * Constructor.
             */
            CSection
               (CSection          *pParent,
                bool              &fModified,
                const std::string &strFullName,
                const std::string &strShortName,
                const std::string &strComment);

            /// Assignment operator, not supported.
            CSection & operator= (const CSection &);
         
         
         
      }; // CSection
      
      
      
      /**
       * The type of an iterator over the sections of a configuration database.
       */
      class CSectionIterator:
         public std::iterator<std::forward_iterator_tag, CConfigFile::CSection>
      {
         friend class CConfigFile;
         
         public:
            /**
             * Default constructor to create an iterator pointing nowhere.
             */
            CSectionIterator (void);
            
            /**
             * Copy constructor.
             */
            CSectionIterator
               (const CSectionIterator &arg);
            
            /**
             * Dereferencing operator for the current section (pointer).
             */
            CSection * operator-> (void);
            
            /**
             * Dereferencing operator for the current section (reference).
             */
            CSection & operator* (void);
            
            /**
             * Postfix-Increment operator.
             *
             * The next iterator position can either be directly the next
             * config section or the next section belonging to our parent
             * section. The first case matches the situation for an iteror over
             * all available sections. The later is true, if this iterator was
             * constructed to enumerate only the sub-sections of a parent
             * section. Then we must check if the directly next section still
             * belongs to our parent. If it does not, we have reached the end
             * of the enumeration and must set the internal map iterator to the
             * end of the map.
             */
            CSectionIterator operator++ (void);
            
            /**
             * Prefix-Increment operator.
             *
             * The next iterator position can either be directly the next
             * config section or the next section belonging to our parent
             * section. The first case matches the situation for an iteror over
             * all available sections. The later is true, if this iterator was
             * constructed to enumerate only the sub-sections of a parent
             * section. Then we must check if the directly next section still
             * belongs to our parent. If it does not, we have reached the end
             * of the enumeration and must set the internal map iterator to the
             * end of the map.
             */
            CSectionIterator operator++ (int);
            
            /**
             * Operator to compare for equality.
             */
            bool operator==
               (const CSectionIterator &arg);
         
            /**
             * Operator to compare for inequality.
             */
            bool operator!=
               (const CSectionIterator &arg);
         
            /**
             * Assignment operator.
             */
            CSectionIterator & operator=
               (const CSectionIterator &arg);

         
         
         private:
            /// The name of the parent section. An empty string denotes an
            /// iterator over all sections regardless of hierarchy.
            std::string m_strParent;
            
            /// The reference to the map this iterator works on.
            std::map<std::string, value_type *, CIcLess> *m_pMap;
            
            /// The internally maintained iterator over the map of sections.
            std::map<std::string, value_type *, CIcLess>::iterator m_iterMap;
            
            
            
            /**
             * Constructor to create an iterator pointing to a valid element.
             */
            CSectionIterator
               (const std::string                                      &strParent,
                std::map<std::string, value_type *, CIcLess>           *pMap,
                std::map<std::string, value_type *, CIcLess>::iterator  iterCurr);
         
         
         
      }; // CSectionIterator
      
      
      
      /**
       * Constructor for an empty configuration database.
       */
      CConfigFile (void);
      
      /**
       * Constructor from an existing configuration file.
       */
      CConfigFile
         (const std::string &strFileName);

      /**
       * Destructor.
       */
      ~CConfigFile (void);
      
      /**
       * Get the name of the underlying configuration file.
       */
      const std::string &GetFileName (void) const;
      
      /**
       * Get the current number of section of the database.
       */
      size_t GetNumSections (void) const;
      
      /**
       * Get an iterator to the beginning of a section hierarchy layer.
       */
      CSectionIterator SectionBegin
         (const std::string &strBase);

      /**
       * Get an iterator behind the end of any section hierarchy layer.
       */
      CSectionIterator SectionEnd (void);

      /**
       * Find a specific section through its name.
       */
      CSectionIterator SectionFind
         (const std::string &strSectionName);

      /**
       * Add a new section to the database.
       */
      CSectionIterator SectionAdd
         (const std::string &strSectionName,
          const std::string &strComment = std::string ());

      /**
       * Remove an entire section and its sub-sections through a section name.
       */
      void SectionRemove
         (const std::string &strSectionName);

      /**
       * Remove an entire section and its sub-sections through an iterator.
       */
      void SectionRemove
         (CSectionIterator &iterSect);

      /**
       * Check if the configuration database was modified after construction or
       * the last save operation.
       */
      bool IsModified (void) const;
      
      /**
       * Save the current database content to the current file.
       */
      void Save (void);
   
      /**
       * Save the current database content to a different file.
       */
      void SaveToFile
         (const std::string &strFileName,
          bool               fForceOverwrite);

   
   
   protected:
   
   
   
   private:
      /// The file name this database is read from or written to. If this field
      /// contains the empty string, there is still no file name set and the
      /// database cannot be written by Save(). This is the case after
      /// constructing an empty database. One has to use SaveToFile() to
      /// assign a file name and to save the database to it.
      std::string m_strFileName;
      
      /// Flag for a modified database after the construction or the last write
      /// operation, whichever is more recently.
      bool m_fModified;
      
      /// The short form for the type of the internally maintained map of
      /// sections for the configuration database.
      typedef std::map<std::string, CSection *, CIcLess> CSectionMap;
      
      /// The internally maintained map of sections for the configuration
      /// database.
      CSectionMap m_mapDb;
      
      /// Pointer to the top level section of the configuration file.
      CSection *m_pTopSect;
      
   

      /**
       * Remove a section and all its sub-sections.
       */
      void RemoveSection
         (CSection *pSectToRemove);

      /**
       * Remove a single section without any children.
       */
      void RemoveSingleSection
         (CSection *pSectToRemove);

      /**
       * Read a new database from a file.
       *
       * @pre The database is assumed to be empty.
       */
      void ReadFromFileInternal
         (const std::string &strFileName);

      /**
       * Save the database to a file.
       */
      void SaveToFileInternal
         (const std::string &strFileName);

      /**
       * Save a complete section to a stream.
       */
      void SaveSection
         (std::ofstream  &ofs,
          const CSection *pSect,
          bool           &fFirstSection);

      /**
       * Create a backup file name for a specified one.
       */
      std::string CreateBackupFileName
         (const std::string &strOrgFileName);

      /**
       * Create a new section from a configuration line.
       */
      CSectionIterator CreateNewSection
         (const std::string &strLine,
          const std::string &strComment);

      /**
       * Parse a key/value configuration line.
       */
      void ParseKeyValueLine
         (const std::string &strLine,
          std::string       &strKey,
          std::string       &strValue);



}; // CConfigFile





// === Implementation of template and inline methods =====================





// --- Class for handling a section within the database ------------------

/**
 * Get the value for a given key as a signed numeric value.
 */

template<class T>
T CConfigFile::CSection::GetKeyValueAsSignedNumber
   (const std::string &strKey) const
{
   CKeyValueMap::const_iterator iterMap;
   
   iterMap = m_mapKV.find (strKey);
   if (iterMap == m_mapKV.end ())
   {
      // key not found --> return null
      return (0);
   }
   // key found --> convert the associated string value into a numeric value
   return ((T) strtol (iterMap->second->m_strValue.c_str (), NULL, 0));
} // CConfigFile::CSection::GetKeyValueAsSignedNumber





/**
 * Get the value for a given key as an unsigned numeric value.
 */

template<class T>
T CConfigFile::CSection::GetKeyValueAsUnsignedNumber
   (const std::string &strKey) const
{
   CKeyValueMap::const_iterator iterMap;
   
   iterMap = m_mapKV.find (strKey);
   if (iterMap == m_mapKV.end ())
   {
      // key not found --> return null
      return (0);
   }
   // key found --> convert the associated string value into a numeric value
   return ((T) strtoul (iterMap->second->m_strValue.c_str (), NULL, 0));
} // CConfigFile::CSection::GetKeyValueAsUnsignedNumber





/**
 * Add or update a key/value pair for this section.
 */

template<class T>
void CConfigFile::CSection::AddKeyValuePair
   (const std::string &strKey,
    T                  tValue,
    const std::string &strComment)
{
   Value_t            *pKV;
   std::ostringstream  oss;

   oss << tValue;
   pKV = new Value_t (strKey, oss.str (), strComment);
   if (m_pFirstValue == NULL)
   {
      m_pFirstValue = pKV;
   }
   if (m_pLastValue != NULL)
   {
      m_pLastValue->m_pNext = pKV;
   }
   pKV->m_pPrev = m_pLastValue;
   pKV->m_pNext = NULL;
   m_pLastValue = pKV;
   
   m_fModified = true;

   (void) m_mapKV.insert (CKeyValueMap::value_type (strKey, pKV));

} // CConfigFile::CSection::AddKeyValuePair<T>





// === Prototypes of interface functions =================================





#endif // CFGFILE_H
