/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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 
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSGINTROSPECTION_EXTENDEDTYPEINFO_
#define OSGINTROSPECTION_EXTENDEDTYPEINFO_

#include <typeinfo>
#include <string>
#include <osgIntrospection/type_traits>

namespace osgIntrospection
{
    /// This class is a wrapper for std::type_info that also records whether
    /// a type is a reference or const reference.  It permits osgIntrospection
    /// to make use of reference information that is ignored by typeid(), and
    /// to generate reference types based on it.  The ExtendedTypeInfo for a
    /// class can be produced via the extended_typeid() functions, analogous
    /// to typeid().
    ///
    class ExtendedTypeInfo
    {
    public:
        ExtendedTypeInfo(const std::type_info &ti, bool isReference, bool isConstReference) :
            _ti(&ti), _is_reference(isReference), _is_const_reference(isConstReference)
        {
        }

        /// Orders ExtendedTypeInfo based first on std::type_info, then on
        /// reference, then on const reference.  Note that we can't rely on
        /// default pointer comparison for std::type_info because it is not
        /// guaranteed that &typeid(T) always returns the same pointer for a
        /// given T (thanks Andrew Koenig).
        bool operator<(const ExtendedTypeInfo &other) const
        {
            if (_ti->before(*other._ti))
                return true;
            else if (other._ti->before(*_ti))
                return false;
            else if (_is_reference < other._is_reference)
                return true;
            else if (other._is_reference < _is_reference)
                return false;
            else
                return _is_const_reference < other._is_const_reference;
        }

        /// Returns true if *this and other are the same type.
        bool operator==(const ExtendedTypeInfo &other) const
        {
            return (*_ti == *other._ti &&
                    _is_reference == other._is_reference &&
                    _is_const_reference == other._is_const_reference);
        }

        /// Gets the std::type_info object for this type.
        const std::type_info &getStdTypeInfo() const
        {
            return *_ti;
        }

        /// Returns true if this type is a reference or const reference.
        bool isReference() const
        {
            return _is_reference;
        }

        /// Returns true if this type is a const reference.
        bool isConstReference() const
        {
            return _is_const_reference;
        }

        /// Returns the name of this type, based on the std::type_info name.
        std::string name() const
        {
            if (_is_const_reference)
                return std::string("const ") + _ti->name() + " &";
            else if (_is_reference)
                return std::string(_ti->name()) + " &";
            else
                return _ti->name();
        }

    private:
        const std::type_info *_ti;
        bool _is_reference;
        bool _is_const_reference;
    };
}

/// extended_typeid works like typeid, but returns an ExtendedTypeInfo.  This
/// version operates on expressions.
template <typename T>
osgIntrospection::ExtendedTypeInfo
extended_typeid(T)
{
  return osgIntrospection::ExtendedTypeInfo(typeid(T),
                                            is_reference<T>::value,
                                            is_const_reference<T>::value);
}

/// extended_typeid works like typeid, but returns an ExtendedTypeInfo.  This
/// version operates on types, which must be specified as a template parameter.
template <typename T>
osgIntrospection::ExtendedTypeInfo
extended_typeid()
{
  return osgIntrospection::ExtendedTypeInfo(typeid(T),
                                            is_reference<T>::value,
                                            is_const_reference<T>::value);
}

#endif
