/*
 *       File name:  common/config-validate.hh
 *         Project:  Aghermann
 *          Author:  Andrei Zavada <johnhommer@gmail.com>
 * Initial version:  2011-06-30
 *
 *         Purpose:  libconfig-bound validator
 *
 *         License:  GPL
 */

#ifndef AGH_COMMON_CONFIG_VALIDATE_H_
#define AGH_COMMON_CONFIG_VALIDATE_H_

#include <limits.h>

#include <list>
#include <array>
#include <functional>
#include <stdexcept>

#include <libconfig.h++>

#include "string.hh"
#include "log-facility.hh"
#include "lang.hh"

#if HAVE_CONFIG_H && !defined(VERSION)
#  include "config.h"
#endif

using namespace std;

namespace agh {
namespace confval {

template <typename T> libconfig::Setting::Type libconfig_type_id();

template <> libconfig::Setting::Type inline libconfig_type_id<bool>             () { return libconfig::Setting::Type::TypeBoolean; }
template <> libconfig::Setting::Type inline libconfig_type_id<int>              () { return libconfig::Setting::Type::TypeInt;     }
template <> libconfig::Setting::Type inline libconfig_type_id<size_t>           () { return libconfig::Setting::Type::TypeInt64;   }
template <> libconfig::Setting::Type inline libconfig_type_id<const char*>      () { return libconfig::Setting::Type::TypeString;  }
template <> libconfig::Setting::Type inline libconfig_type_id<string>           () { return libconfig::Setting::Type::TypeString;  }
template <> libconfig::Setting::Type inline libconfig_type_id<double>           () { return libconfig::Setting::Type::TypeFloat;   }
template <> libconfig::Setting::Type inline libconfig_type_id<float>            () { return libconfig::Setting::Type::TypeFloat;   }



inline
libconfig::Setting&
ensure_path( libconfig::Setting& S, libconfig::Setting::Type type, const string& key)
{
        auto pe = agh::str::tokens( key, ".");
        auto Si = &S;
        // ensure path
        for ( auto K = pe.begin(); K != pe.end(); ++K )
                if ( not Si->exists(*K) ) {
                        Si = &Si->add( *K, (next(K) == pe.end()) ? type : libconfig::Setting::TypeGroup);
                } else
                        Si = &(*Si)[K->c_str()];
        return *Si;
}


template <typename T>
void
put( libconfig::Config& C, const string& key, const T& value, agh::log::CLogFacility* lo)
{
        ensure_path( C.getRoot(), libconfig_type_id<T>(), key) = value;
}
template <> // specialise for size_t
inline void
put( libconfig::Config& C, const string& key, const size_t& value, agh::log::CLogFacility* lo)
{
        if ( value > INT_MAX && lo )
                lo->msg( agh::log::TLevel::warning, LOG_SOURCE_ISSUER, "Value being saved is way too big for any practical purpose (unintialized?): %zu", value);
        ensure_path( C.getRoot(), libconfig_type_id<int>(), key) = (int)value;
}

template <typename T>
void
put( libconfig::Config& C, const string& key, const list<T>& vl)
{
        auto& S = ensure_path( C.getRoot(), libconfig::Setting::Type::TypeList, key);
        for ( auto& V : vl )
                S.add( libconfig_type_id<T>()) = V;
}

template <typename T, size_t N>
void
put( libconfig::Config& C, const string& key, const array<T, N>& vl)
{
        auto& S = ensure_path( C.getRoot(), libconfig::Setting::Type::TypeList, key);
        for ( auto& V : vl )
                S.add( libconfig_type_id<T>()) = V;
}




struct IValidator {
        virtual void get( const libconfig::Config&, agh::log::CLogFacility*, agh::TThrowOption) const = 0;
        virtual void put(       libconfig::Config&, agh::log::CLogFacility*)                    const = 0;
        virtual ~IValidator() {}  // -Wdelete-non-virtual-dtor
};


template <typename T>
struct SValidator : IValidator {
        string key;
        T* rcp;
        struct SVFTrue {
                bool operator() ( const T&) const { return true; }
        };
        struct SVFRangeEx {
                T lo, hi;
                SVFRangeEx( const T& _lo, const T& _hi) : lo(_lo), hi(_hi) {};
                bool operator() ( const T& v) const { return v > lo && v < hi; }
        };
        struct SVFRangeIn {
                T lo, hi;
                SVFRangeIn( const T& _lo, const T& _hi) : lo(_lo), hi(_hi) {};
                bool operator() ( const T& v) const { return v >= lo && v <= hi; }
        };
        // using TValidator function<bool(const T&)>;
        function<bool(const T&)> valf;

        template <typename K>
        SValidator( const K& _key, T* _rcp)
              : key (_key), rcp (_rcp), valf {SVFTrue()}
                {}
        template <typename K>
        SValidator( const K& _key, T* _rcp, function<bool (const T&)> _valf)
              : key (_key), rcp (_rcp), valf (_valf)
                {}

        void
        get( const libconfig::Config& C,
             agh::log::CLogFacility* lo,
             agh::TThrowOption throw_option) const
                {
                        T tmp;
                        if ( not C.lookupValue( key, tmp) ) {
                                if ( lo )
                                        lo->msg( agh::log::TLevel::warning, LOG_SOURCE_ISSUER, "Key \"%s\" not found", key.c_str());
                                return; // leave at default
                        }
                        if ( not valf(tmp) ) {
                                lo->msg( agh::log::TLevel::warning, LOG_SOURCE_ISSUER, "Bad value for key \"%s\"", key.c_str());
                                if ( throw_option == TThrowOption::do_throw )
                                        throw invalid_argument( string("Bad value for \"") + key + "\"");
                        } else
                                *rcp = tmp;
                }
        void
        put( libconfig::Config& C, agh::log::CLogFacility* lo) const
                {
                        confval::put( C, key, *rcp, lo);
                }
};


// // specialise for FP types to have an additional isfinite check
// // is it obviously redundant?
// template <> inline bool SValidator<float>::SVFRangeIn::
// operator()( const float& v)
// { return isfinite(v) and v > lo && v < hi; }

// template <> inline bool SValidator<double>::SVFRangeIn::
// operator()( const float& v)
// { return isfinite(v) and v > lo && v < hi; }

// template <> inline bool SValidator<float>::SVFRangeEx::
// operator()( const float& v)
// { return isfinite(v) and v > lo && v < hi; }

// template <> inline bool SValidator<double>::SVFRangeEx::
// operator()( const float& v)
// { return isfinite(v) and v > lo && v < hi; }


template <>
inline void
SValidator<size_t>::
get( const libconfig::Config& C,
     agh::log::CLogFacility* lo,
     agh::TThrowOption throw_option) const
{
        int tmp; // libconfig doesn't deal in unsigned values
        if ( not C.lookupValue( key, tmp) ) {
                if (lo)
                        lo->msg( agh::log::TLevel::warning, LOG_SOURCE_ISSUER, "Key \"%s\" not found", key.c_str());
                return; // leave at default
        }
        if ( tmp < 0 || not valf((size_t)tmp) ) {
                lo->msg( agh::log::TLevel::warning, LOG_SOURCE_ISSUER, "Bad value for key \"%s\"", key.c_str());
                if ( throw_option == TThrowOption::do_throw )
                        throw invalid_argument( string("Bad value for \"") + key + "\"");
        } else
                *rcp = tmp;
}


template <typename T>
void
get( list<SValidator<T>>& vl,
     libconfig::Config& conf,
     agh::log::CLogFacility* lo,
     agh::TThrowOption throw_option)
{
        for ( auto& V : vl )
                if ( throw_option == TThrowOption::no_throw )
                        try {
                                V.get( conf, lo);
                        } catch ( exception& ex) {
                                if ( lo )
                                        lo->msg( agh::log::TLevel::warning, LOG_SOURCE_ISSUER, "get(list): %s", ex.what());
                        }
                else
                        V.get( conf, lo);
}

template <typename T>
void
put( list<SValidator<T>>& vl,
     libconfig::Config& conf,
     agh::log::CLogFacility* lo)
{
        for ( auto& V : vl )
                V.put( conf, lo);
}




// collections of SValidator

class CConfigKeys {
    public:
        list<IValidator*> S;
       ~CConfigKeys ()
                {
                        for ( auto& K : S )
                                delete K;
                }

        template <class K, class V>
        CConfigKeys&
        operator()( const K& k, V* v)
                { return S.push_back( new SValidator<V>(k, v)), *this; }

        template <class K, class V, class F>
        CConfigKeys&
        operator()( const K& k, V* v, const F& f)
                { return S.push_back( new SValidator<V>(k, v, f)), *this; }

        void
        get( libconfig::Config& C, agh::log::CLogFacility* lo, agh::TThrowOption throw_option)
                {
                        for ( auto& K : S )
                                K->get( C, lo, throw_option);
                }
        void
        put( libconfig::Config& C, agh::log::CLogFacility* lo)
                {
                        for ( auto& K : S )
                                K->put( C, lo);
                }
};


} // namespace confval
} // namespace agh

#endif

// Local Variables:
// Mode: c++
// indent-tabs-mode: nil
// tab-width: 8
// c-basic-offset: 8
// End:
