#ifndef PARAMSET_PARTS_TEMPLATE
#define PARAMSET_PARTS_TEMPLATE

#include <map>
#include <string>
#include <vector>

#include <cstdlib>
#include <stdexcept>

#include <algorithm>
#include <functional>

#include <ios>
#include <iostream>
#include <iomanip>
#include <typeinfo>

#include "Header.hh"

#include "Message.hh"
#include "ContainerDumpTool.hh"

/** function object that extract the key from a entry
 *
 *  @author TANIMORI Souichirou, AdvanceSoft Corp.
 *  @version 0.0
 *  @since   0.9
 */
template<typename _SCALER_TYPE >
struct ExtractKey {
    std::string operator() ( std::pair<const std::string, _SCALER_TYPE >& item) { return item.first; };
};

/** compare  the length of two keys.
 *  @return true when the key (the first element) of item1 is less than the key of item2.
 *          less type functional
 *
 *  @author TANIMORI Souichirou, AdvanceSoft Corp.
 *  @version 0.0
 *  @since   0.9
 */
template<typename _SCALER_TYPE >
struct KeyLengthComp : binary_function< std::pair<const std::string, _SCALER_TYPE >&, std::pair<const std::string, _SCALER_TYPE >&, Bool > {
    Bool operator() (std::pair<const std::string, _SCALER_TYPE >& item1, std::pair<const std::string, _SCALER_TYPE >& item2) {
        return item1.first.size() < item2.first.size();
    };
};

/** template for a container of parameters for user specified type.
 *     - empty   check whether the container has no entry or not
 *     - size    the number of entries that container has
 *     - contain check whether the container has the specified key or not
 *     - add     add the value with the specified key to the container
 *     - get     get the value with the specified key. the method throw std::exceptions when fail to get a value.
 *     - replace replace the value with the specified key to a new value
 *     - erase   erase the entry with the specified key
 *     - clear   clear all entries
 *     - dump    print all entries to the standard output
 *
 *  @author TANIMORI souichirou, Advancesoft Corp.
 *  @version 0.0
 *  @sice    0.9
 */
template<typename _SCALER_TYPE >
class ContainerForScaler {

    protected:
        std::map<std::string, _SCALER_TYPE > cstd::map;
        
    public:
        /** constructor */
        ContainerForScaler< _SCALER_TYPE >() { this->cstd::map.clear(); };

        /** destructor */
        ~ContainerForScaler< _SCALER_TYPE >() { this->cstd::map.clear(); };

        /** return true if the container has no entry.
         */
        Bool empty() const { return this->cstd::map.empty(); };

        /** return the number of entries.
         */
        UInt4 size() const { return this->cstd::map.size(); };

        /** return true when the container contains the entry with the sepcified key.
         *  @param[in] key
         */
        Bool contain(const std::string& key) const { return this->cstd::map.find(key) != this->cstd::map.end(); };

        /** clear all entries.
         */
        void clear() { this->cstd::map.clear(); };

        /** add the value with the specified key to the container.
         *  the method do nothing when the container already has the entry with the specified key.
         *  @param[in] key
         *  @param[in] value
         */
        void add(const std::string& key, _SCALER_TYPE value);

        /** get the value of the entry with the sepcified key.
         *  @param[in] key
         *  @std::exception std::invalid_argument
         */
        _SCALER_TYPE get(const std::string& key) throw(std::invalid_argument);


        /** erase the entry with the specified key.
         *  the method do nothing when the container does not have the entry with the specified key.
         *  @param[in] key
         */
        void erase(const std::string& key);

        /** replace old value with key to new value.
         *  the method do nothing when the container does not have the entry with the specified key.
         *  @param[in] key
         *  @param[in] newValue
         */
        void replace(const std::string& key, _SCALER_TYPE value);

        /** get a list of keys for values.
         *  return an empty list when the container has no entry.
         */
        std::vector<std::string> getKeys() ;
       
        /** print all entry to the standard output
         *  @param[in] indent
         */
        void dump(const UInt4 indent=0, const UInt4 indentDepth=4, const std::string& keyTitile="key", const std::string& sizeTitle="size", const std::string& valueTitle="value");

};

template<typename _SCALER_TYPE>
void ContainerForScaler< _SCALER_TYPE>::add(const std::string& key, _SCALER_TYPE value) {

    if ( this->cstd::map.find(key) != this->cstd::map.end() ) {
        std::cerr << "Warning: ContainerForScale::add: " << __FILE__ << ":" << __LINE__ << ": already found the value with key \"" << key << "\"" << std::endl;
    } else {
        this->cstd::map.insert(std::pair<std::string, _SCALER_TYPE >(key, value));
    }
};

template<typename _SCALER_TYPE >
_SCALER_TYPE ContainerForScaler< _SCALER_TYPE >::get(const std::string& key) throw(std::invalid_argument) {
    if (this->cstd::map.find(key) == this->cstd::map.end()) {
        std::cerr << "Error: ContainerForScaler::get: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"" << std::endl;
        throw std::invalid_argument(std::string("ContainerForScaler::get: not found the entry with the specified key \"") + key + "\"\n");
    }
    return this->cstd::map[key];
}


template<typename _SCALER_TYPE >
void ContainerForScaler< _SCALER_TYPE >::erase(const std::string& key) {

    if ( this->cstd::map.find(key) == this->cstd::map.end() ) {
        std::cerr << "Warning: ContainerForScaler::erase: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"" << std::endl;
    } else {
        this->cstd::map.erase(this->cstd::map.find(key));
    }
};

template<typename _SCALER_TYPE >
void ContainerForScaler< _SCALER_TYPE >::replace(const std::string& key, _SCALER_TYPE newValue) {

    if ( this->cstd::map.find(key) == this->cstd::map.end() ) {
        std::cerr << "Warning: ContainerForScaler::replace: " << __FILE__ << ":" << __LINE__ << ": not found the value with the key \"" << key << "\"" << std::endl;
    } else {
        this->cstd::map[key]=newValue;
    }
};

template<typename _SCALER_TYPE >
std::vector<std::string> ContainerForScaler< _SCALER_TYPE >::getKeys() {

    std::vector<std::string> *retval = new std::vector<std::string>();
    if (! this->cstd::map.empty() ) {
        retval->resize(this->cstd::map.size());
        transform(this->cstd::map.begin(), this->cstd::map.end(), retval->begin(), ExtractKey< _SCALER_TYPE >());
    }
    return *retval;
    
};

template<typename _SCALER_TYPE>
void ContainerForScaler< _SCALER_TYPE>::dump(const UInt4 indent, const UInt4 indentDepth,
                                             const std::string& keyTitle, const std::string& sizeTitle, const std::string& valueTitle) {

    if (! this->cstd::map.empty() ) {
        size_t maxKeyWidth=0;
        maxKeyWidth = std::max(keyTitle.size(), max_element(this->cstd::map.begin(), this->cstd::map.end(), KeyLengthComp< _SCALER_TYPE >())->first.size());

        OutputTypeTitle ott; // size column is 0
        ott.title(indentDepth*indent, typeid( _SCALER_TYPE));  // subtitle
        ott.header(indentDepth*(indent+1), maxKeyWidth, keyTitle, 0, sizeTitle, valueTitle); // header

        for_each (this->cstd::map.begin(), this->cstd::map.end(), OutputScalerEntry< _SCALER_TYPE >(indentDepth*(indent+1), maxKeyWidth)); // key and values
        std::cout << std::endl;
    }
};

#endif // PARAMSET_PARTS_TEMPLATE
