/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 *
 * The iWear Framework is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation as in version 2 of the License.

 * 
 * The iWear Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __IWEAR_CONFIGURATION_H
#define __IWEAR_CONFIGURATION_H

#ifndef __IWEAR_UTILITY_H
#include <iwear/utility.h>
#endif

#ifndef __IWEAR_THREADLOCKED_H
#include <iwear/threadlocked.h>
#endif

#include <iwear/threadlocker.h>
#include <iwear/utility.h>
#include <iwear/debugstream.h>

#include <map>
#include <bitset>
#include <iomanip>
#include <string>
#include <ostream>
#include <sstream>
#include <set>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>

namespace iwear
{

/**
 * A ConfigNode is the key data element in a configuration tree. Think of it as
 * a directory (or if you want, as the windows registry) where every directory
 * is a ConfigNode and consists of either several key/value std::pairs (the files)
 * or other ConfigNodes (the further subdirectories). Additionally to their
 * contents, they all can have comments which are saved in the trees.
 * @note This class is for internal use only and its symbols are not exported.
 */
// TODO move this into the Configuration as a private type.
struct ConfigNodeBase
{
    std::string comment;

    /**
     * We don't need this for querying the configuration, but for iterating
     * over it in case of loading, saving and printing.
     */
    virtual bool is_subnode( void ) = 0;

    virtual void clear() = 0;

    virtual ConfigNodeBase* clone() const = 0;

    virtual void printon( std::ostream& o, int level, std::bitset<32>& bs ) const = 0;

    virtual void dot( std::ostream& ) const = 0;

    virtual ~ConfigNodeBase( void ) { }

    ConfigNodeBase( const std::string& c ) : comment(c) { }
};

class ConfigFunctor;
class ConfigEvent;

struct ConfigValue : public ConfigNodeBase
{
    std::string value;
    /**
     * Since we expect this to be mostly empty, we only allocate one when
     * needed. This is some kind of a trade-off, but we cannot really afford
     * to have the up to 72 Bytes overhead (usually 36 on 32Bit gcc, but
     * still).
     */

    std::set<ConfigFunctor*>* functors;
    bool deprecated;

    virtual bool is_subnode( void ) { return false; }
    virtual void clear()  { }

    virtual ConfigValue* clone() const
    {
	ConfigValue* cv = new ConfigValue( value, comment);
	cv->deprecated = deprecated;
	return cv;
    }

    virtual void dot( std::ostream& ) const;

    virtual void printon( std::ostream& o, int level, std::bitset<32>& bs ) const;

    ConfigValue( const std::string& v, const std::string& c  ) : ConfigNodeBase(c), value(v), functors(0), deprecated(false) 
    { 
    }
};

struct ConfigNode : public ConfigNodeBase
{
   /**
    * FIXME
    * std::maps the value names to their values (std::pair.first) and the comment of
    * that entry (std::pair.second).
    * Comment is anything that is above the value, and on the same line as the
    * value assignment.
    * Newlines should be preserved where possible, but an output module is
    * allowed to move the comment which is on the same line to another valid
    * position
    * @note <b> Important !</b> These are the restrictions about configuration
    * paths and values :
    * - Configuration path delimiter is always /
    * - This delimiter may never occur in the key
    * - Allowed characters in path and key are only those which can be used for
    *   the filesystem too.    
    * - For maximum compatibility you should stick to ASCII
    * - Although there is no real limit for the configuration value and path
    *   size, implementations of the loaders are required to load 256 Bytes for
    *   the value only.
    * - Values may be converted to other character std::sets if necessary, but it is
    *   recommended to stick to utf8
    */

    /**
     * FIXME: comment wrong
     * To be consistent with the normal use of filesystems, we do not allow an
     * directory to be of the same name as a "file" in it.
     * Also directories can have comments, although no values.
     */

    /**
     * Our virtual configuration entries. Can either be some subnode, or some value entry.
     */
    std::map<std::string, ConfigNodeBase* > entries;

    virtual void dot( std::ostream& ) const;
    /**
     * We don't need to do anything else.
     */
    inline ConfigNode( const std::string& comment) : ConfigNodeBase(comment) { }

    virtual ~ConfigNode();

    /**
     * This properly clears out this ConfigNode and deletes all subnodes.
     */
    virtual void clear();
    virtual bool is_subnode( void ) { return true; }

    virtual ConfigNode* clone() const;

    /**
     * This prints on a give std::ostream. Mainly for Configuration operator<< use.
     */
    virtual void printon( std::ostream&, int level, std::bitset<32>& ) const;

};

/**
 * The iterator is only some internal class that shall be used for doing the
 * save and load stuff. It is not intended for the use by the user. There shall
 * be no configuration enumeration possible by the user.
 */
#if 0
struct ConfigIterator
{
    /**
     * This is the config node we are currently active in.
     */
    ConfigNode* node;

    /**
     * This is the position in the std::map we are currently in.
     */
    std::map<std::string,ConfigNodeBase*>::iterator pos;

    /**
     * To make it easier for iterators, we save a pointer to the parents.
     * std::pair::first is the config node which is the parent node of the one we
     * currently iterate to (Similar to the .. entry of filesystems). The
     * std::pair::second is the iterator iterating to the entry in the std::map, that
     * leads to us (Similar to the . entry).
     */
    std::stack<
    std::pair<ConfigNode*,std::map<std::string,ConfigNodeBase*>::iterator> 
    >parent;

    ConfigIterator& operator++();
    ConfigIterator  operator++(int);

    ConfigIterator& operator--();
    ConfigIterator  operator--(int);

    std::string& operator*();
    std::string& operator->();
};
#endif

/**
 * A path leads to a config directory which contains several name/value std::pairs.
 * Paths are delimited by / and may never contain characters like
 * @todo Create an iterator with usable iteration semantics.
 */
class Configuration : public ThreadLocked
{
    friend std::ostream& operator<<( std::ostream& o, const Configuration& c );
    friend class ConfigSaver;
private:
    /**
     * @todo TODO FIXME if we want to save the whole config to one single file,
     * this wont be allowed, since we ask for a subconfig. we have to redo that
     * config part too.
     */
    ConfigNode root;

    std::string global_basepath;

    std::string basepath;

//    const ConfigNode* find_path( const std::string& path ) const;

    const std::map<std::string,ConfigNodeBase*>& get_entries( void ) const { return root.entries; }

/*
    ConfigIterator begin();
    ConfigIterator begin() const;
    ConfigIterator end();
    ConfigIterator end() const;
*/

// The bool tells us whether the value could be found. If its not found, what
// the std::string contains is unspecified.
    /**
     * This shall return a std::string reference to the stored value, as well as a
     * bool telling us if the value was found.
     */
    enum nodetype
    {
	none,	 // nothing valid was returned. Means nothing was found, or something was invalid.
	value,   // The pointer contains a value
	node,    // The pointer contains a node
	inserted // The node given was inserted.
    };
//    typedef std::pair<boost::reference_wrapper<const ConfigValue>,bool> const_getpair;
 
    union u_node 
    { 
	ConfigValue* value; 
	ConfigNode* node; 
	u_node( ConfigValue* v ) : value(v) { }
	u_node( ConfigNode* n  ) : node(n)  { }
	u_node( ) : node(0) { }
    };

    typedef std::pair< const u_node , nodetype > const_getpair;

    const_getpair internal_get( const std::string& p ) const;

// This definitely inserts some path.
// If the value specified is a path already, this will throw.
// The bool specifies, whether the entry was created.
// The std::string is in any case a reference to the std::string value at the specified path.
// (Note: in either case the comment will be updated when specified)

    /**
     * @param lastpath is to be set to true, if the last element in the path
     * list shall be a path, not a value.
     */
//    typedef std::pair<boost::reference_wrapper<ConfigValue>,bool> getpair;
    typedef std::pair< u_node , nodetype > getpair;
    getpair internal_insert( const std::string& path, const std::string& comment, bool lastpath );

    template<class T>
    std::pair<T,bool> convert_to( const std::string& ) const;

    template<class T>
    std::pair<std::string,bool> convert_to( const T& ) const;

public:

    void dot( std::ostream& o ) { root.dot(o); }
//    Configuration get_subconfig( const std::string& path) const;
    /**
     * @param bp is the base path at which the loading will be appended. Like
     * bp = "/home/$USER/.iwear" and then load from "output/..." so an
     * application can std::set its own path. 
     * @param gbp is the global base path to be loaded from like "/usr/share"
     * etc.
     */
    Configuration( const std::string& gbp, const std::string& bp) ;

    ~Configuration();

    const std::string& get_global_basepath( void ) { return global_basepath; }

    const std::string& get_basepath( void ) { return basepath; }

    /**
     * Gets a configuration value, and returns it as a T using a std::stringstream
     * based conversion sequence.
     * @throw iwruntime_error when the configuration value was not found.
     * @throw iwruntime_error When the conversion to the requested type failed.
     * @throw iwruntime_error When the specified path was a subpath
     */
    template<class T> T get( const std::string& path ) const;

    /**
     * Like the const version, this returns a configuration value as T.
     * But unlike the const version, if the value is not found, it will instead
     * be inserted. Then the specified default value will be returned.
     * If the value was found, but the conversion didn't work, then the default
     * value will be inserted instead of the value.
     * @note If the passed default value is not convertible to a std::string, the
     * configuration will not be altered, and this value will be returned
     * instead.
     * @throw iwruntime_error When the specified path was a subpath
     * @warning due to a bug in the current version, this overwrites a comment
     * also when it was already there. @todo
     */
    template<class T> T get( const std::string& path, const T&, const std::string& comment = "" );

    /**
     * Gets a configuration value. Returns the default value passed, when the
     * path wasn't found. Also returns the default value, when the type
     * conversion failed.  
     * @throw iwruntime_error When the specified path was a subpath
     * @todo Check if we can do some RVO here.
     */
    template<class T> T get( const std::string& path, const T& ) const;

    // TODO add a decent operator[] implementation. (try to distinguish between
    // rhs and lhs by some proxy objects)

    /**
     * This will register an event, which will be emitted, when the
     * corresponding entry is changed. It will then change the value of the
     * element given as second parameter to what was std::set into the
     * configuration. Failings to do so will silently be ignored.
     * @throw iwruntime_error If the path was not found in the configuration hierarchy
     * @throw iwlogic_error when the path was marked as deprecated
     * @warning You have to make sure yourself that whatever T& refers to never
     * goes out of scope, until the event is unregistered.
     */
    template<class T>
    void register_event( const std::string&, T& );

    /**
     * Use this to deregister an event via the event pointer you got. This is
     * the recommended version to do it instead of having to pass the
     * parameters etc. again. (Of course, if its easier for you to do it that
     * way, feel free to do it, it just is most likely slower and we cannot
     * guarantee anything for now *g*)
     */
    void deregister_event( ConfigEvent* );

    /**
     * Here we call the passed boost::function object. _1 and _2 are the name
     * of the configuration option, and the value resp. 
     * @throw iwruntime_error If the path was not found in the configuration hierarchy
     * @throw iwlogic_error when the path was marked as deprecated
     */
    void register_event( const std::string&, const boost::function<void(const std::string&,const std::string&)>& );

    /**
     * This inserts a subnode path and adds some comment for it. No values will
     * be inserted. 
     * @throw iwruntime_error If the subnode already existed as a value.
     */
    void insert( const std::string& path, const std::string& comment, int);

    /**
     * Inserts a config value, if it existed before, it will be overwritten.
     * The comment will only be updated, when it is not empty.
     */
    template<class T> 
    void insert( const std::string& path, const T& val, const std::string& comment = "");

    /**
     * This marks a path as deprecated. If the path exist, it will indeed be
     * marked. If it didn't exist, a path will be inserted, with empty values
     * and marked as deprecated.
     * Whenever a program requests a deprecated configuration option, a message
     * of warning level will be emitted to the console. This shall aid as a
     * hint for programmers.
     * @note No configuration saving class may ever store deprecated values to
     * its storage.
     * @warning Never shall a deprecated value be registered for change
     * notification. 
     */
    void deprecate( const std::string& path );

    /**
     * This completely removes the specified path from the configuration. All
     * subpaths, if applicable, are removed too.
     * This shall be used one or two releases, after some configuration option
     * has been deprecated.
     * If the path didn't exist, no action is performed.
     */
    void remove( const std::string& path );
};

template<> void Configuration::insert( const std::string&, const std::string&, const std::string& );

template<class T> void Configuration::insert( const std::string& s, const T& t, const std::string& c)
{
    std::stringstream ss;
    ss << std::boolalpha << t;
    insert(s,ss.str(),c);
}

// generic case of conversion to something
template<class T>
std::pair<T,bool> Configuration::convert_to( const std::string& val ) const
{
    std::stringstream ss(val);
    T t;
    bool ret = (ss>>t);
    return std::make_pair(t,ret);
}


// specialized to return some std::string
template<class T>
std::pair<std::string,bool> Configuration::convert_to( const T& val ) const
{
    std::stringstream ss;
    bool ret = (ss<<val);
    return std::make_pair(ss.str(),ret);
}

// specialized conversion for timeval
template<>
inline std::pair<timeval,bool> Configuration::convert_to( const std::string& val ) const
{
    return std::make_pair(time_from_string(val.c_str()),true);
}/*}}}*/

// specialized so that the stringstream does not eat up the rest of a string
template<>
inline std::pair<std::string,bool> Configuration::convert_to( const std::string& val ) const
{
    return std::make_pair(val,true);
}/*}}}*/


// specialized to convert to some bool
template<>
inline std::pair<bool,bool> Configuration::convert_to( const std::string& val ) const
{
    if( val == "true" || val == "yes" || val == "wahr" ) return std::make_pair(true,true);
    if( val == "false" || val == "no" || val == "falsch" ) return std::make_pair(false,true);

    return std::make_pair(false,false);
}/*}}}*/

template<class T>
T Configuration::get( const std::string& path ) const/*{{{*/
{
    const_getpair v = internal_get(path);
    if( v.second == value )
    {
//	d_dbg << "Trying to convert " << v.first.value->value << " to type <" << typenameof(T()) << ">" << std::endl;
	std::pair<T,bool> r = convert_to<T>(v.first.value->value);
	if( r.second )
	{
	    return r.first;
	}
	THROW(iwruntime_error,("Failed to convert the configuration value"));
    }
    THROW(iwruntime_error,("Could not find specified configuration value"));
}/*}}}*/

template<class T>
T Configuration::get( const std::string& path, const T& t, const std::string& com)/*{{{*/
{
    std::pair<std::string,bool> nv = convert_to(t);
    if( ! nv.second ) // conversion failed, don't even bother to insert.
    {
	return t;
    }

    getpair v = internal_insert(path,com,false);
    if( v.second == inserted ) // was inserted, so new one
    {
	v.first.value->value = nv.first;
	return t; // Return it
    }
    else if ( v.second == value ) // was already there, so get it from there
    {
	std::pair<T,bool> r = convert_to<T>(v.first.value->value);
	if( r.second ) // conversion just fine
	{
	    return r.first;
	}
	v.first.value->value = nv.first;
	// conversion failed, return the default
    } 
    // here some other failure was there...
    return t;
}/*}}}*/

template<class T>
T Configuration::get( const std::string& path, const T& t) const/*{{{*/
{
    const_getpair v = internal_get(path);
    if( v.second == value )
    {
	std::pair<T,bool> r = convert_to<T>(v.first.value->value);
	if( r.second )
	{
	    return r.first.value;
	}
    }
    return t;
}/*}}}*/

}

#endif

