/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

/// @author Tiago Quintino
/// @author Florian Rathgeber
/// @date July 2014

#ifndef eckit_value_Params_H
#define eckit_value_Params_H

#include <list>

#include "eckit/serialisation/Stream.h"
#include "eckit/value/Value.h"
#include "eckit/config/Parametrisation.h"

//----------------------------------------------------------------------------

namespace eckit {

//----------------------------------------------------------------------------

/// Params provides a value-based behaviour for parametrisations of objects.

/// A class `MyParams` to be wrapped into Params needs to implement:
///
/// 1. A constructor from `Stream&`
///
/// 2. A static method `className` returning a `const char*`
///
/// 3. A `get` function taking a `Params::key_t` and returning a `Params::value_t`:
///
///        Params::value_t get( const MyParams&, const Params::key_t& )
///
///    This function performs the lookup of a parameter by key.
///
/// 4. A `print` function taking a `std::ostream&`:
///
///        void print( const MyParams&, std::ostream& )
///
///   This function prints a representation of the parameters to the ostream.
///
/// 5. An `encode` function taking a `Stream&`:
///
///        void encode( const MyParams&, Stream& )
///
///    This function encodes the parameters to the Stream.
///
/// If the free functions `get`, `print` and `encode` need access to private
/// members of `MyParams`, they need to be declared as friend functions.
///
/// In addition a `Params::Factory<MyParams>` needs to be initialised.

class Params {

    struct Concept;

public: // types

    typedef std::list<Params> List;
    typedef std::string  key_t;
    typedef Value value_t;

    struct BaseFactory {
        virtual ~BaseFactory() {}
        virtual Concept* build( Stream& s ) = 0;
    };

    typedef BaseFactory* factory_t;

    template <typename T>
    struct Factory : BaseFactory {
        Factory()
        {
            Params::registerFactory(T::className(), this);
        }

        Concept* build( Stream& s );
    };

public: // methods

    template <typename T>
    explicit Params( const T& x ) : self_(new Model<T>(x)) {}

    Params( const Params& x ) : self_(x.self_->copy_()) {}

    static void registerFactory( const std::string& name, factory_t f )
    {
        factories()[name] = f;
    }

    static factory_t& getFactory(const std::string& name )
    {
        return factories()[name];
    }

    static Params build(const std::string& name, Stream& s)
    {
         return Params( getFactory(name)->build(s) ); // returns Concept*
    }

    static Params decode( Stream& s )
    {
        std::string name;
        s >> name;
        return build(name,s);
    }

    ~Params() { delete self_; }

    Params& operator=( Params x )
    {
        std::swap(x.self_, this->self_);
        return *this;
    }

    bool has(const std::string& name ) const;

    value_t operator[] ( const key_t& key ) const;

    friend void print( const Params& p, std::ostream& s );

    friend void encode( const Params& p, Stream& s );

    friend value_t getValue( const Params& p, const key_t& key );

private: // internal classes

    typedef std::map<std::string, factory_t> factory_map;
    static factory_map& factories();

    Params(Concept* concept) : self_(concept) {}

    struct Concept {
        virtual ~Concept() {}
        virtual Concept* copy_() const = 0;
        virtual value_t get_( const key_t& key ) const = 0;
        virtual void print_( std::ostream& s ) const = 0;
        virtual void encode_( Stream& s ) const = 0;
    };

    template <typename T>
    struct Model : Concept
    {
        Model( T x ) : data_(x) {}
        Model( Stream& s ) : data_(s) {}

        virtual Concept* copy_() const {
            return new Model(data_);
        }

        virtual value_t get_( const key_t& key ) const {
            return getValue( data_, key );
        }

        virtual void print_( std::ostream& s ) const {
            print( data_, s );
        }

        virtual void encode_( Stream& s ) const {
            s << T::className();
            encode( data_, s );
        }

        T data_;
    };

private: // methods

    friend std::ostream& operator<<(std::ostream& s, const Params& p);

    friend Stream& operator<<(Stream& s, const Params& p);

private: // members

    const Concept* self_;
};

//----------------------------------------------------------------------------

template <typename T>
Params::Concept* Params::Factory<T>::build(Stream & s)
{
    return new Model<T>(s);
}

//----------------------------------------------------------------------------

} // namespace eckit

#endif
