/******************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Binary configuration file based on key/value tuples.
 * @remarks Similar to GNOME's gnome_config*().
 *//*
 * LEGAL:   COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 ******************************************************************************/

#ifndef BASE_CONF_BIN_HH
#define BASE_CONF_BIN_HH 1

#include <vector>
#include <map>
#include <fstream>

namespace base {

////////////////////////////////////////////////////////////////////////////////
/// @brief Storing and reading configuration (key,value) to a file.
/// @verbatim
///
/// ctor THROWS EXCEPTION if config file cannot be opened.
///
/// Overview:
/// ---------
/// This class is for storing and reading a program's configuration to a file
/// (and it could be used as a simple database in lieue of DB or GDBM).
/// The configuration data consisting of type/key/value tuples similar in concept
/// to GNOME's gnome_config*().  A tuple's value has a type (int, float, string).
///
/// NOTE/WARNING: If auto-flush is disabled (for speed), client must periodically call Flush().
/// Write() just updates a STL map, it doesn't write to the configuration file!
///
/// How to use:
/// -----------
/// The configuration file is opened by the constructor and closed by the destructor.
/// Example of opening the configuration file:
///
///    ConfBin config( "ProgramName" );
///    if ( config.IfEmpty() ) return;          // configuration is new (ok)
///    if ( ! config.IfValid() ) { Error(); }   // error
///
/// Each tuple consists of a key (name), its value, and its value's type.
/// To store a tuple, call Write().
/// To retrieve a tuple, call Read().
/// To remove a tuple, call Remove().
///
///    config.Write( "highscore", (int)    536000  );
///    config.Write( "game",      (string) "PacMan" );
///    config.Write( "offset",    (double) 0.123   );
///    config.Read(  "highscore", highscore );
///    config.Remove( "highscore" );
///
/// Access() is provided in order to write a a common function that
/// stores and retrieves tuples depending on a variable:
///
///    (don't write "access" as it's in unistd.h)
///
///    int acc = ConfBin::eAccess_READ;
///    int acc = ConfBin::eAccess_WRITE;
///    config.Access( acc, "highscore", &highscore );
///
/// Methods that modify the configuration will automatically update (flush)
/// changes to the configuration file.  But optionally for speed, autoflush
/// can be disabled by a constructor arg.  Disabling autoflush is faster but
/// then the user is responsible for calling Flush() or else changes will be lost!
///
/// Restrictions/limitations:
/// -------------------------
/// o Keys must not contain any whitespace (but string values can).
/// o If the end-user writes comments to the configuration file
///   (or does any change for that matter), they'll be discarded/overwritten
///   when the program modifies the configuration.
///
/// Implementation notes:
/// ---------------------
/// The configuration file consists of a set of lines containing tuples.
/// A tuple consists of a key, its value, its value's type (int,double,string).
///
/// For example, starting with a new configuration file, this code...
///
///    Write( "game",      "PacMan"        );
///    Write( "highscore", (int)    536000 );
///    Write( "offset",    (double) 0.123  );
///
/// ...would produce these lines:
///
/// string game 6 PacMan
/// int highscore 536000
/// double offset 0.123
/// END
///
/// String values have a length field, hence the 6 for "PacMan".
///
/// "END" may indeed appear more than once.  The tuples after the first "END"
/// are removed tuples.  This will happen if the configuration file once had
/// more tuples than it currently does.
///
/// @endverbatim
///
class ConfBin
{
PREVENT_COPYING(ConfBin)
public:
    // (Functions for complex types are at glue/conf_bin_ext.hh)

    enum { NO_AUTO_FLUSH, AUTO_FLUSH };
                ConfBin( const string& programName, const string& fname = "", bool autoflush = true );
                ~ConfBin() { }
    bool        IfValid( void ) { return mValid; }
    bool        IfEmpty( void ) { return mTupleMap.empty(); }
    bool        Write( const string& key, const int&    val );
    bool        Write( const string& key, const double& val );
    bool        Write( const string& key, const string& val );
    bool        Read( const string& key, int&    val /*OUT*/ );
    bool        Read( const string& key, double& val /*OUT*/ );
    bool        Read( const string& key, string& val /*OUT*/ );
    enum        eAccess { eAccess_WRITE, eAccess_READ };
    bool        Access( ConfBin::eAccess acc, const string& key, int&    val /*IN/OUT*/ );
    bool        Access( ConfBin::eAccess acc, const string& key, double& val /*IN/OUT*/ );
    bool        Access( ConfBin::eAccess acc, const string& key, string& val /*IN/OUT*/ );
    bool        Remove( const string& key );
    bool        Flush( void );

private:
    bool        Parse( void );

private:
    ////////////////////////////////////////////////////////////////////////////
    /// @brief Defines a configuration item value and type (int/double/string).
    ///
    class ConfTuple
    {
    COPYABLE(ConfTuple)
    public:
                ConfTuple( void );
                ~ConfTuple();
        void    Clear( void );
        bool    Write( std::fstream& fs );
        bool    Read( std::fstream& fs, const string& type );
        enum eType
        {
            eType_INVALID,
            eType_INT,
            eType_DOUBLE,
            eType_STRING
        };
        ConfTuple::eType    mType;          ///< type of the value
        string              mKey;           ///< its name
        int                 mValInt;        ///< valid only if mType is int
        double              mValDouble;     ///< valid only if mType is double
        string              mValString;     ///< valid only if mType is string
        DECLARE_TYPESIG( TYPESIG_CONF_TUPLE )
    };

private:
    bool                        mValid;         ///< if configuration is OK
    bool                        mAutoflush;     ///< if false the user must call Flush()
    string                      mProgramName;   ///< might determine name of configuration file
    std::fstream                mStream;        ///< configuration file
    std::map<string,ConfTuple>  mTupleMap;      ///< map of tuples

friend bool CompareTuples( const ConfBin::ConfTuple& t1, const ConfBin::ConfTuple& t2 );
};

bool CompareTuples( const ConfBin::ConfTuple& t1, const ConfBin::ConfTuple& t2 );

string
CheckObsoleteConfFilename( const string& programName );

} // namespace base

#endif // BASE_CONF_BIN_HH
