/**
 * @file   lltypeinfolookup.h
 * @author Nat Goodspeed
 * @date   2012-04-08
 * @brief  Template data structure like std::map<std::type_info*, T>
 * 
 * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 * Copyright (c) 2012, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_LLTYPEINFOLOOKUP_H)
#define LL_LLTYPEINFOLOOKUP_H

#include <boost/unordered_map.hpp>
#include <boost/functional/hash.hpp>
#include <boost/optional.hpp>
#include <functional>               // std::binary_function
#include <typeinfo>

/**
 * The following helper classes are based on the Boost.Unordered documentation:
 * http://www.boost.org/doc/libs/1_45_0/doc/html/unordered/hash_equality.html
 */

/**
 * Compute hash for a string passed as const char*
 */
struct const_char_star_hash: public std::unary_function<const char*, std::size_t>
{
    std::size_t operator()(const char* str) const
    {
        std::size_t seed = 0;
        for ( ; *str; ++str)
        {
            boost::hash_combine(seed, *str);
        }
        return seed;
    }
};

/**
 * Compute equality for strings passed as const char*
 *
 * I (nat) suspect that this is where the default behavior breaks for the
 * const char* values returned from std::type_info::name(). If you compare the
 * two const char* pointer values, as a naive, unspecialized implementation
 * will surely do, they'll compare unequal.
 */
struct const_char_star_equal: public std::binary_function<const char*, const char*, bool>
{
    bool operator()(const char* lhs, const char* rhs) const
    {
        return strcmp(lhs, rhs) == 0;
    }
};

/**
 * LLTypeInfoLookup is specifically designed for use cases for which you might
 * consider std::map<std::type_info*, VALUE>. We have several such data
 * structures in the viewer. The trouble with them is that at least on Linux,
 * you can't rely on always getting the same std::type_info* for a given type:
 * different load modules will produce different std::type_info*.
 * LLTypeInfoLookup contains a workaround to address this issue.
 *
 * The API deliberately diverges from std::map in several respects:
 * * It avoids iterators, not only begin()/end() but also as return values
 *   from insert() and find(). This bypasses transform_iterator overhead.
 * * Since we literally use compile-time types as keys, the essential insert()
 *   and find() methods accept the key type as a @em template parameter,
 *   accepting and returning value_type as a normal runtime value. This is to
 *   permit future optimization (e.g. compile-time type hashing) without
 *   changing the API.
 */
template <typename VALUE>
class LLTypeInfoLookup
{
    // Use this for our underlying implementation: lookup by
    // std::type_info::name() string. This is one of the rare cases in which I
    // dare use const char* directly, rather than std::string, because I'm
    // sure that every value returned by std::type_info::name() is static.
    // HOWEVER, specify our own hash + equality functors: naively comparing
    // distinct const char* values won't work.
    typedef boost::unordered_map<const char*, VALUE,
                                 const_char_star_hash, const_char_star_equal> impl_map_type;

public:
    typedef VALUE value_type;

    LLTypeInfoLookup() {}

    bool empty() const { return mMap.empty(); }
    std::size_t size() const { return mMap.size(); }

    template <typename KEY>
    bool insert(const value_type& value)
    {
        // Obtain and store the std::type_info::name() string as the key.
        // Return just the bool from std::map::insert()'s return pair.
        return mMap.insert(typename impl_map_type::value_type(typeid(KEY).name(), value)).second;
    }

    template <typename KEY>
    boost::optional<value_type> find() const
    {
        // Use the std::type_info::name() string as the key.
        typename impl_map_type::const_iterator found = mMap.find(typeid(KEY).name());
        if (found == mMap.end())
            return boost::optional<value_type>();
        return found->second;
    }

private:
    impl_map_type mMap;
};

#endif /* ! defined(LL_LLTYPEINFOLOOKUP_H) */