//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#include "pxr/base/tf/type.h"

#include "pxr/base/arch/demangle.h"
#include "pxr/base/tf/hash.h"
#include "pxr/base/tf/hashmap.h"
#include "pxr/base/tf/instantiateSingleton.h"
#include "pxr/base/tf/iterator.h"
#include "pxr/base/tf/mallocTag.h"
#include "pxr/base/tf/singleton.h"
#include "pxr/base/tf/pyUtils.h"
#include "pxr/base/tf/stl.h"
#include "pxr/base/tf/typeInfoMap.h"
#include "pxr/base/tf/typeNotice.h"

#include <boost/foreach.hpp>
#include <boost/noncopyable.hpp>
#include <boost/optional.hpp>
#include <boost/utility/in_place_factory.hpp>

#include <tbb/spin_rw_mutex.h>

#include <atomic>
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>

using std::map;
using std::pair;
using std::string;
using std::vector;

typedef vector<TfType> TypeVector;

using RWMutex = tbb::spin_rw_mutex;
using ScopedLock = tbb::spin_rw_mutex::scoped_lock;

TfType::FactoryBase::~FactoryBase()
{
}

TfType::PyPolymorphicBase::~PyPolymorphicBase()
{
}

// Stored data for a TfType.
// A unique instance of _TypeInfo is allocated for every type declared.
//
struct TfType::_TypeInfo : boost::noncopyable
{
    typedef TfHashMap<string, TfType::_TypeInfo*, TfHash> NameToTypeMap;
    typedef TfHashMap<
        TfType::_TypeInfo*, vector<string>, TfHash> TypeToNamesMap;
    typedef TfHashMap<string, TfType, TfHash> DerivedByNameCache;

    // Unique TfType instance, for returning const references to.
    TfType canonicalTfType;
    
    // Unique type name.
    const string typeName;
    
    // Callback invoked to define this type when first required.
    TfType::DefinitionCallback definitionCallback;

    // C++ type_info.  NULL if no C++ type has been defined.
    std::atomic<std::type_info const *> typeInfo;

    // The size returned by sizeof(type).
    size_t sizeofType;

    // Python class handle.
    // We use handle<> rather than boost::python::object in case Python
    // has not yet been initialized.
    boost::python::handle<> pyClass;

    // Direct base types.
    TypeVector baseTypes;

    // Direct derived types.
    TypeVector derivedTypes;

    // Factory.
    std::unique_ptr<TfType::FactoryBase> factory;

    // Map of derived type aliases to derived types.
    boost::optional<NameToTypeMap> aliasToDerivedTypeMap;
    // Reverse map of derived types to their aliases.
    boost::optional<TypeToNamesMap> derivedTypeToAliasesMap;

    // Map of functions for converting to other types.
    // This map is keyed by type_info and not TfType because the TfTypes
    // may not have been defined yet at the time we are adding castFuncs.
    // It is expected that the entries here will ultimately have matching
    // entries in our baseTypes, although that is not enforced.
    vector<pair<std::type_info const *, TfType::_CastFunction> > castFuncs;

    boost::scoped_ptr<DerivedByNameCache> derivedByNameCache;

    // Traits about the static type.
    bool isPodType;
    bool isEnumType;

    // True if we have sent a TfTypeWasDeclaredNotice for this type.
    bool hasSentNotice;

    mutable RWMutex mutex;

    ////////////////////////////////////////////////////////////////////////

    // A type is "defined" as soon as it has either type_info or a
    // Python class object.
    inline bool IsDefined() {
        return typeInfo or pyClass.get();
    }

    // Caller must hold a write lock on mutex.
    void SetCastFunc(std::type_info const &baseType,
                     TfType::_CastFunction const &func) {
        // check for existing func.
        for (size_t i = 0; i < castFuncs.size(); ++i) {
            if (baseType == *castFuncs[i].first) {
                castFuncs[i].second = func;
                return;
            }
        }
        // need to add a new func.
        castFuncs.push_back(make_pair(&baseType, func));
    }

    // Caller must hold at least a read lock on mutex.
    TfType::_CastFunction *GetCastFunc(std::type_info const &baseType) {
        for (size_t i = 0; i < castFuncs.size(); ++i)
            if (TfSafeTypeCompare(baseType, *castFuncs[i].first))
                return &castFuncs[i].second;
        return 0;
    }

    // Caller must hold at least a read lock on mutex.
    _TypeInfo *FindByAlias(std::string const &alias) const {
        if (aliasToDerivedTypeMap) {
            auto it = aliasToDerivedTypeMap->find(alias);
            return it != aliasToDerivedTypeMap->end() ? it->second : nullptr;
        }
        return nullptr;
    }
    
    // Allocate an empty (undefined) _TypeInfo with the given typeName.
    _TypeInfo(const string &newTypeName) :
        canonicalTfType(this),
        typeName(newTypeName),
        typeInfo(nullptr),
        sizeofType(0),
        isPodType(false),
        isEnumType(false),
        hasSentNotice(false)
    {
    }
};

// Comparison for boost::python::handle.
struct Tf_PyHandleLess
{
    bool operator()(const boost::python::handle<> &lhs,
                    const boost::python::handle<> &rhs) const {
        return lhs.get() < rhs.get();
    }
};

// Registry for _TypeInfos.
//
class Tf_TypeRegistry : boost::noncopyable
{
public:
    static Tf_TypeRegistry& GetInstance() {
        return TfSingleton<Tf_TypeRegistry>::GetInstance();
    }

    RWMutex &GetMutex() const { return _mutex; }
    
    // Note, callers must hold the registry lock for writing, and base's lock
    // for writing, but need not hold derived's lock.
    void AddTypeAlias(TfType::_TypeInfo *base, TfType::_TypeInfo *derived,
                      const string &alias, string *errMsg) {
        // Aliases cannot conflict with other aliases under the same base.
        if (base->aliasToDerivedTypeMap) {
            TfType::_TypeInfo::NameToTypeMap::const_iterator it =
                base->aliasToDerivedTypeMap->find(alias);
            if (it != base->aliasToDerivedTypeMap->end()) {
                if (it->second == derived) {
                    // Alias already exists; no change.
                    return;
                } else {
                    *errMsg = TfStringPrintf(
                        "Cannot set alias '%s' under '%s', because "
                        "it is already set to '%s', not '%s'.",
                        alias.c_str(),
                        base->typeName.c_str(),
                        it->second->typeName.c_str(),
                        derived->typeName.c_str());
                    return;
                }
            }
        }
        // Aliases cannot conflict with typeNames, either.
        if (_typeNameToTypeMap.count(alias) != 0) {
            *errMsg = TfStringPrintf(
                "There already is a type named '%s'; cannot "
                "create an alias of the same name.",
                alias.c_str());
            return;
        }

        if (not base->aliasToDerivedTypeMap)
            base->aliasToDerivedTypeMap = boost::in_place(0);
        (*base->aliasToDerivedTypeMap)[alias] = derived;

        if (not base->derivedTypeToAliasesMap)
            base->derivedTypeToAliasesMap = boost::in_place(0);
        (*base->derivedTypeToAliasesMap)[derived].push_back(alias);
    }

    TfType::_TypeInfo *NewTypeInfo(const string &typeName) {
        TfType::_TypeInfo *info = new TfType::_TypeInfo(typeName);
        _typeNameToTypeMap[typeName] = info;
        return info;
    }

    void SetTypeInfo(TfType::_TypeInfo *info, const std::type_info & typeInfo,
                     size_t sizeofType, bool isPodType, bool isEnumType) {
        info->typeInfo = &typeInfo;
        info->sizeofType = sizeofType;
        info->isPodType = isPodType;
        info->isEnumType = isEnumType;
        _typeInfoMap.Set(typeInfo, info);
    }

    void SetPythonClass(TfType::_TypeInfo *info,
                        boost::python::object & classObj) {
        // Hold a reference to this PyObject in our map.
        boost::python::handle<> handle(
            boost::python::borrowed(classObj.ptr()));

        info->pyClass = handle;
        _pyClassMap[handle] = info;

        // Do not overwrite the size of a C++ type.
        if (not info->sizeofType) {
            info->sizeofType = TfSizeofType<boost::python::object>::value;
        }
    }

    TfType::_TypeInfo *GetUnknownType() const { return _unknownTypeInfo; }

    TfType::_TypeInfo *GetRoot() const { return _rootTypeInfo; }

    TfType::_TypeInfo *FindByName(const string &name) const {
        auto it = _typeNameToTypeMap.find(name);
        return it != _typeNameToTypeMap.end() ? it->second : nullptr;
    }

    TfType::_TypeInfo *FindByTypeid(const std::type_info &typeInfo) const {
        TfType::_TypeInfo **info = _typeInfoMap.Find(typeInfo);
        return info ? *info : nullptr;
    }

    TfType::_TypeInfo *
    FindByPythonClass(const boost::python::object &classObj) const {
        boost::python::handle<> handle(
            boost::python::borrowed(classObj.ptr()));
        auto it = _pyClassMap.find(handle);
        return it != _pyClassMap.end() ? it->second : nullptr;
    }

private:
    Tf_TypeRegistry();

    mutable RWMutex _mutex;

    // Map of typeName to _TypeInfo*.
    // This holds all declared types, by unique typename.
    TfType::_TypeInfo::NameToTypeMap _typeNameToTypeMap;
    
    // Map of type_info to _TypeInfo*.
    // This holds info for types that have been defined as C++ types.
    // XXX: change this to regular hash table?
    TfTypeInfoMap<TfType::_TypeInfo*> _typeInfoMap;

    // Map of python class handles to _TypeInfo*.
    typedef map<boost::python::handle<>,
                TfType::_TypeInfo *, Tf_PyHandleLess> PyClassMap;
    PyClassMap _pyClassMap;

    // _TypeInfo for Unknown type
    TfType::_TypeInfo *_unknownTypeInfo;
    // _TypeInfo for Root type
    TfType::_TypeInfo *_rootTypeInfo;

    // Set true if we should send notification.
    bool _sendDeclaredNotification;

    friend class TfSingleton<Tf_TypeRegistry>;
    friend class TfType;
};

TF_INSTANTIATE_SINGLETON(Tf_TypeRegistry);

// This type is used as the unknown type. Previously, 'void' was used for
// that purpose, but clients want to call TfType::Find<void>();.
struct _TfUnknownType {};

Tf_TypeRegistry::Tf_TypeRegistry() :
    _unknownTypeInfo(0),
    _rootTypeInfo(0),
    _sendDeclaredNotification(false)
{
    // Register root type
    _rootTypeInfo = NewTypeInfo("TfType::_Root");

    // Register unknown type
    _unknownTypeInfo = NewTypeInfo("TfType::_Unknown");
    SetTypeInfo(_unknownTypeInfo, typeid(_TfUnknownType),
                /*sizeofType=*/0, /*isPodType=*/false, /*isEnumType=*/false);

    TfSingleton<Tf_TypeRegistry>::SetInstanceConstructed(*this);

    // We send TfTypeWasDeclaredNotice() when a type is first declared
    // with bases.  Because TfNotice delivery uses TfType, we
    // first register both TfNotice and TfTypeWasDeclaredNotice --
    // without sending TfTypeWasDeclaredNotice for them -- before
    // subscribing to the TfType registry.
    TfType::Define<TfNotice>();
    TfType::Define<TfTypeWasDeclaredNotice, TfType::Bases<TfNotice> >();

    // From this point on, we'll send notices as new types are discovered.
    _sendDeclaredNotification = true;

    TfRegistryManager::GetInstance().SubscribeTo<TfType>();
}

////////////////////////////////////////////////////////////////////////

TfType::TfType() : _info(Tf_TypeRegistry::GetInstance().GetUnknownType())
{
}

TfType const&
TfType::GetRoot()
{
    return Tf_TypeRegistry::GetInstance().GetRoot()->canonicalTfType;
}

TfType const&
TfType::GetCanonicalType() const
{
    return _info->canonicalTfType;
}

TfType const&
TfType::FindByName(const string &name)
{
    return GetRoot().FindDerivedByName(name);
}

TfType const&
TfType::FindDerivedByName(const string &name) const
{
    if (IsUnknown())
        return GetUnknownType();

    TfType result;

    // Note that we cache results in derivedByNameCache, and we never invalidate
    // this cache.  This works because 1) we never remove types and type
    // information from TfType's data structures and 2) we only cache if we find
    // a valid type.
    ScopedLock thisInfoLock(_info->mutex, /*write=*/false);
    if (ARCH_LIKELY(_info->derivedByNameCache and
                    TfMapLookup(*(_info->derivedByNameCache), name, &result))) {
        // Cache hit.  We're done.
        return result._info->canonicalTfType;
    }
    // Look for a type derived from *this, and has the given name as an alias.
    if (TfType::_TypeInfo *foundInfo = _info->FindByAlias(name)) {
        result = TfType(foundInfo);
    }
    // Finished reading _info data.
    thisInfoLock.release();

    // If we didn't find an alias we now look in the registry.
    if (not result) {
        auto &r = Tf_TypeRegistry::GetInstance();
        ScopedLock regLock(r.GetMutex(), /*write=*/false);
        TfType::_TypeInfo *foundInfo = r.FindByName(name);
        regLock.release();
        if (foundInfo) {
            // Next look for a type with the given typename.  If a type was 
            // found, verify that it derives from *this. 
            result = TfType(foundInfo);
            if (not result.IsA(*this))
                result = TfType();
        }
    }

    // Populate cache.
    if (result) {
        // It's possible that some other thread has done this already, but it
        // will be the same result so it's okay to do redundantly in that case.
        thisInfoLock.acquire(_info->mutex, /*write=*/true);
        if (not _info->derivedByNameCache) {
            _info->derivedByNameCache.
                reset(new _TypeInfo::DerivedByNameCache(0));
        }
        _info->derivedByNameCache->insert(make_pair(name, result));
    }

    return result._info->canonicalTfType;
}

TfType const&
TfType::GetUnknownType()
{
    return Tf_TypeRegistry::GetInstance().GetUnknownType()->canonicalTfType;
}

TfType const&
TfType::_FindByTypeid(const std::type_info &typeInfo)
{
    auto &r = Tf_TypeRegistry::GetInstance();
    ScopedLock readLock(r.GetMutex(), /*write=*/false);
    TfType::_TypeInfo *info = r.FindByTypeid(typeInfo);

    return info ? info->canonicalTfType : GetUnknownType();
}

TfType const&
TfType::FindByPythonClass(const boost::python::object & classObj)
{
    auto &r = Tf_TypeRegistry::GetInstance();
    ScopedLock readLock(r.GetMutex(), /*write=*/false);
    TfType::_TypeInfo *info = r.FindByPythonClass(classObj);

    return info ? info->canonicalTfType : GetUnknownType();
}

const string &
TfType::GetTypeName() const
{
    return _info->typeName;
}

const std::type_info &
TfType::GetTypeid() const
{
    std::type_info const *typeInfo = _info->typeInfo;
    return typeInfo ? *typeInfo : typeid(void);
}

boost::python::object
TfType::GetPythonClass() const
{
    if (not TfPyIsInitialized())
        TF_CODING_ERROR("Python has not been initialized");

    ScopedLock lock(_info->mutex, /*write=*/false);
    if (_info->pyClass.get())
        return boost::python::object(_info->pyClass);
    return boost::python::object();
}

vector<string>
TfType::GetAliases(TfType derivedType) const
{
    ScopedLock lock(_info->mutex, /*write=*/false);
    if (_info->derivedTypeToAliasesMap) {
        auto i = _info->derivedTypeToAliasesMap->find(derivedType._info);
        if (i != _info->derivedTypeToAliasesMap->end())
            return i->second;
    }
    return vector<string>();
}

vector<TfType>
TfType::GetBaseTypes() const
{
    ScopedLock lock(_info->mutex, /*write=*/false);
    return _info->baseTypes;
}

vector<TfType>
TfType::GetDirectlyDerivedTypes() const
{
    ScopedLock lock(_info->mutex, /*write=*/false);
    return _info->derivedTypes;
}

void
TfType::GetAllDerivedTypes(std::set<TfType> *result) const
{
    ScopedLock lock(_info->mutex, /*write=*/false);
    for (auto derivedType: _info->derivedTypes) {
        result->insert(derivedType);
        derivedType.GetAllDerivedTypes(result);
    }
}

// Helper for resolving ancestor order in the case of multiple inheritance.
static bool
_MergeAncestors(vector<TypeVector> *seqs, TypeVector *result)
{
    while(true)
    {
        // Find a candidate for the next type.
        TfType cand;

        // Try the first element of each non-empty sequence, in order.
        bool anyLeft = false;
        TF_FOR_ALL(candSeq, *seqs)
        {
            if (candSeq->empty())
                continue;
                
            anyLeft = true;
            cand = candSeq->front();

            // Check that the candidate does not occur in the tail
            // ("cdr", in lisp terms) of any of the sequences.
            TF_FOR_ALL(checkSeq, *seqs)
            {
                if (checkSeq->size() <= 1)
                    continue;

                if (std::find( ++(checkSeq->begin()), checkSeq->end(), cand )
                    != checkSeq->end())
                {
                    // Reject this candidate.
                    cand = TfType();
                    break;
                }
            }

            if (not cand.IsUnknown()) {
                // Found a candidate
                break;
            }
        }


        if (cand.IsUnknown()) {
            // If we were unable to find a candidate, we're done.
            // If we've consumed all the inputs, then we've succeeded.
            // Otherwise, the inheritance hierarchy is inconsistent.
            return not anyLeft;
        }

        result->push_back(cand);

        // Remove candidate from input sequences.
        TF_FOR_ALL(seqIt, *seqs) {
            if (not seqIt->empty() and seqIt->front() == cand)
                seqIt->erase( seqIt->begin() );
        }
    }
}

void
TfType::GetAllAncestorTypes(vector<TfType> *result) const
{
    if (IsUnknown()) {
        TF_CODING_ERROR("Cannot ask for ancestor types of Unknown type");
        return;
    }

    const vector<TfType> &baseTypes = GetBaseTypes();
    const size_t numBaseTypes = baseTypes.size();

    // Simple case: single (or no) inheritance
    if (numBaseTypes <= 1) {
        result->push_back(*this);
        if (numBaseTypes == 1)
            baseTypes.front().GetAllAncestorTypes(result);
        return;
    }

    // Use the C3 algorithm for resolving multiple inheritance;
    // see motivating comments in header.  If this turns out to be a
    // performance problem, consider memoizing this algorithm.

    vector<TypeVector> seqs;
    seqs.reserve(2 + numBaseTypes);

    // 1st input sequence: This class.
    seqs.push_back( TypeVector() );
    seqs.back().push_back(*this);

    // 2nd input sequence: Direct bases, in order.
    seqs.push_back( baseTypes );

    // Remaining sequences: Inherited types for each direct base.
    TF_FOR_ALL(it, baseTypes) {
        // Populate the base's ancestor types directly into a new vector on
        // the back of seqs.
        seqs.push_back( TypeVector() );
        TypeVector &baseSeq = seqs.back();
        it->GetAllAncestorTypes(&baseSeq);
    }

    // Merge the input sequences to resolve final inheritance order.
    bool ok = _MergeAncestors( &seqs, result );

    if (not ok) {
        TF_CODING_ERROR("Cannot resolve ancestor classes for '%s' "
                        "because the inheritance hierarchy is "
                        "inconsistent.  Please check that multiply-"
                        "inherited types are inherited in the same order "
                        "throughout the inherited hierarchy.",
                        GetTypeName().c_str());
    }
}

TfType const &
TfType::_FindImplPyPolymorphic(PyPolymorphicBase const *ptr) {
    using namespace boost::python;
    TfType ret;
    if (TfPyIsInitialized()) {
        TfPyLock lock;
        // See if we can find a polymorphic python object...
        object pyObj = Tf_FindPythonObject(
            TfCastToMostDerivedType(ptr), typeid(*ptr));
        if (not TfPyIsNone(pyObj))
            ret = FindByPythonClass(pyObj.attr("__class__"));
    }
    return not ret.IsUnknown() ? ret.GetCanonicalType() : Find(typeid(*ptr));
}


bool
TfType::_IsAImpl(TfType queryType) const
{
    // Iterate until we reach more than one parent.
    for (TfType t = *this; ; ) {
        if (t == queryType)
            return true;

        ScopedLock lock(t._info->mutex, /*write=*/false);
        if (t._info->baseTypes.size() == 1) {
            t = t._info->baseTypes[0];
            continue;
        }
        for (size_t i = 0; i != t._info->baseTypes.size(); i++)
            if (t._info->baseTypes[i]._IsAImpl(queryType))
                return true;
        return false;
    }
}

bool
TfType::IsA(TfType queryType) const
{
    if (queryType.IsUnknown()) {
        // If queryType is unknown, it almost always means a previous
        // type lookup failed, and went unchecked.
        TF_RUNTIME_ERROR("IsA() was given an Unknown base type.  "
                         "This probably means the attempt to look up the "
                         "base type failed.  (Note: to explicitly check if a "
                         "type is unknown, use IsUnknown() instead.)");
        return false;
    }
    if (IsUnknown()) {
        return false;
    }

    if (*this == queryType or queryType.IsRoot()) {
        return true;
    }

    // If the query type doesn't have any child types, then iterating over all
    // our base types wastes time.
    ScopedLock queryLock(queryType._info->mutex, /*write=*/false);
    if (queryType._info->derivedTypes.empty()) {
        return false;
    }
    queryLock.release();

    // printf("--- %s IsA %s { ",
    //        GetTypeName().c_str(), queryType.GetTypeName().c_str());

    bool ret = _IsAImpl(queryType);

    // printf(" } = %d\n", ret);

    return ret;
}

TfType const &
TfType::Declare(const string &typeName)
{
    TfAutoMallocTag2 tag("Tf", "TfType::Declare");

    TfType t = FindByName(typeName);
    if (t.IsUnknown()) {
        auto &r = Tf_TypeRegistry::GetInstance();
        ScopedLock lock(r.GetMutex(), /*write=*/true);
        t = TfType(r.NewTypeInfo(typeName));
        TF_AXIOM(not t._info->IsDefined());
    }
    return t.GetCanonicalType();
}

TfType const&
TfType::Declare(const string &typeName,
                const vector<TfType> &newBases,
                DefinitionCallback definitionCallback)
{
    TfAutoMallocTag2 tag("Tf", "TfType::Declare");

    TfType const& t = Declare(typeName);

    bool sendNotice = false;
    vector<string> errorsToEmit;
    {
        auto &r = Tf_TypeRegistry::GetInstance();
        ScopedLock regLock(r.GetMutex(), /*write=*/true);
        ScopedLock typeLock(t._info->mutex, /*write=*/true);

        if (t.IsUnknown() or t.IsRoot()) {
            errorsToEmit.push_back(
                TfStringPrintf("Cannot declare the type '%s'",
                               t.GetTypeName().c_str()));
            goto errorOut;
        }

        // Update base types.
        vector<TfType> &bases = t._info->baseTypes;

        // If this type already directly inherits from root, then
        // prohibit adding any new bases.
        if (not newBases.empty() and bases == vector<TfType>(1, GetRoot())) {
            errorsToEmit.push_back(
                TfStringPrintf("Type '%s' has been declared to have 0 bases, "
                               "and therefore inherits directly from the root "
                               "type.  Cannot add bases.",
                               t.GetTypeName().c_str()));
            goto errorOut;
        }

        if (newBases.empty()) {
            if (bases.empty()) {
                // If we don't have any bases yet, add the root type.
                t._AddBase(GetRoot());
            }
        } else {
            // Otherwise, add the new bases.

            // First, check that all previously-declared bases are included.
            TF_FOR_ALL(it, bases) {
                if (std::find(newBases.begin(), newBases.end(), *it) ==
                    newBases.end())
                {
                    string newBasesStr;
                    TF_FOR_ALL(it2, newBases)
                        newBasesStr += it2->GetTypeName() + " ";

                    errorsToEmit.push_back(
                        TfStringPrintf("TfType '%s' was previously declared to "
                                       "have '%s' as a base, but subsequent "
                                       "declaration does not include this as a "
                                       "base.  The newly given bases were: "
                                       "(%s).  If this is a type declared in a "
                                       "plugin, check that the plugin metadata "
                                       "is correct.",
                                       t.GetTypeName().c_str(),
                                       it->GetTypeName().c_str(),
                                       newBasesStr.c_str()));
                }
            }

            TF_FOR_ALL(it, newBases)
                t._AddBase(*it);

        }

        if (definitionCallback) {
            // Prohibit re-declaration of definitionCallback.
            if (t._info->definitionCallback) {
                errorsToEmit.push_back(
                    TfStringPrintf("TfType '%s' has already had its "
                                   "definitionCallback set; ignoring 2nd "
                                   "declaration", typeName.c_str()));
                goto errorOut;
            }
            t._info->definitionCallback = definitionCallback;
        }

        // Send a notice about this type if we have not done so yet.
        if (r._sendDeclaredNotification and not t._info->hasSentNotice) {
            t._info->hasSentNotice = sendNotice = true;
        }
    }

    if (sendNotice)
        TfTypeWasDeclaredNotice(t).Send();

errorOut:

    // Emit any errors.
    for (auto const &msg: errorsToEmit)
        TF_CODING_ERROR(msg);

    return t;
}

void
TfType::DefinePythonClass( boost::python::object & classObj ) const
{
    if (IsUnknown() or IsRoot()) {
        TF_CODING_ERROR("cannot define Python class because type is unknown");
        return;
    }
    auto &r = Tf_TypeRegistry::GetInstance();
    ScopedLock infoLock(_info->mutex, /*write=*/true);
    ScopedLock regLock(r.GetMutex(), /*write=*/true);
    if (not TfPyIsNone(_info->pyClass)) {
        infoLock.release();
        regLock.release();
        TF_CODING_ERROR("TfType '%s' already has a defined Python type; "
                        "cannot redefine", GetTypeName().c_str());
        return;
    }
    r.SetPythonClass(_info, classObj);
}

void
TfType::_DefineCppType(const std::type_info & typeInfo,
                       size_t sizeofType, bool isPodType, bool isEnumType) const
{
    auto &r = Tf_TypeRegistry::GetInstance();
    ScopedLock infoLock(_info->mutex, /*write=*/true);
    ScopedLock regLock(r.GetMutex(), /*write=*/true);
    if (_info->typeInfo) {
        infoLock.release();
        regLock.release();
        TF_CODING_ERROR("TfType '%s' already has a defined C++ type; "
                        "cannot redefine", GetTypeName().c_str());
        return;
    }
    r.SetTypeInfo(_info, typeInfo, sizeofType, isPodType, isEnumType);
}

void
TfType::_AddBase(TfType base) const
{
    // Callers must hold _info write lock.
    if (base.IsUnknown()) {
        TF_CODING_ERROR("Specified base type is unknown, skipping");
        return;
    }
    vector<TfType> & bases = _info->baseTypes;
    if (std::find(bases.begin(), bases.end(), base) == bases.end()) {
        // Add the new base.
        bases.push_back(base);
        // Tell the new base that it has a new derived type.
        ScopedLock baseLock(base._info->mutex, /*write=*/true);
        base._info->derivedTypes.push_back(*this);
    }
}

void
TfType::_AddCppCastFunc( const std::type_info & baseTypeInfo,
                         _CastFunction func ) const
{
    ScopedLock infoLock(_info->mutex, /*write=*/true);
    _info->SetCastFunc(baseTypeInfo, func);
}

void*
TfType::CastToAncestor(TfType ancestor, void* addr) const
{
    if (IsUnknown() or ancestor.IsUnknown())
        return 0;
        
    // Iterate until we reach more than one parent.
    for (TfType t = *this; ; ) {
        if (t == ancestor)
            return addr;
        ScopedLock lock(t._info->mutex, /*write=*/false);
        if (t._info->baseTypes.size() == 1) {
            _CastFunction *castFunc =
                t._info->GetCastFunc(t._info->baseTypes[0].GetTypeid());
            if (castFunc) {
                addr = (*castFunc)(addr, true);
                t = t._info->baseTypes[0];
                continue;
            } else {
                return nullptr;
            }
        }
        for (size_t i = 0; i < t._info->baseTypes.size(); i++) {
            _CastFunction *castFunc =
                t._info->GetCastFunc(t._info->baseTypes[i].GetTypeid());
            if (castFunc) {
                void *pAddr = (*castFunc)(addr, true);
                if (void *final =
                    t._info->baseTypes[i].CastToAncestor(ancestor, pAddr))
                    return final;
            }
        }
        return nullptr;
    }
}

void*
TfType::CastFromAncestor(TfType ancestor, void* addr) const
{
    if (IsUnknown() or ancestor.IsUnknown())
        return 0;

    // No iteration: we have to do the purely recursively, because
    // each cast has to happen on the way back *down* the type tree.
    if (*this == ancestor)
        return addr;

    ScopedLock lock(_info->mutex, /*write=*/false);
    TF_FOR_ALL(it, _info->baseTypes) { 
        if (void* tmp = it->CastFromAncestor(ancestor, addr)) {
            if (_CastFunction *castFunc = _info->GetCastFunc(it->GetTypeid()))
                return (*castFunc)(tmp, false);
        }
    }

    return nullptr;
}

void
TfType::SetFactory(std::unique_ptr<FactoryBase> factory) const
{
    if (IsUnknown() or IsRoot()) {
        TF_CODING_ERROR("Cannot set factory of %s\n",
                        GetTypeName().c_str());
        return;
    }

    ScopedLock infoLock(_info->mutex, /*write=*/true);
    if (_info->factory) {
        infoLock.release();
        TF_CODING_ERROR("Cannot change the factory of %s\n",
                        GetTypeName().c_str());
        return;
    }

    _info->factory = std::move(factory);
}

TfType::FactoryBase*
TfType::_GetFactory() const
{
    if (IsUnknown() or IsRoot()) {
        TF_CODING_ERROR("Cannot manufacture type %s", GetTypeName().c_str());
        return NULL;
    }

    _ExecuteDefinitionCallback();

    ScopedLock infoLock(_info->mutex, /*write=*/false);
    return _info->factory.get();
}

void
TfType::_ExecuteDefinitionCallback() const
{
    // We don't want to call the definition callback while holding the
    // registry's lock, so first copy it with the lock held then
    // execute it.
    ScopedLock infoLock(_info->mutex, /*write=*/false);
    if (DefinitionCallback definitionCallback = _info->definitionCallback) {
        infoLock.release();
        definitionCallback(*this);
    }
}

string
TfType::GetCanonicalTypeName(const std::type_info &t)
{
    return ArchGetDemangled(t);
}

void
TfType::AddAlias(TfType base, const string & name) const
{
    std::string errMsg;
    {
        auto &r = Tf_TypeRegistry::GetInstance();
        ScopedLock infoLock(base._info->mutex, /*write=*/true);
        ScopedLock regLock(r.GetMutex(), /*write=*/true);
        // We do not need to hold our own lock here.
        r.AddTypeAlias(base._info, this->_info, name, &errMsg);
    }
    
    if (not errMsg.empty())
        TF_CODING_ERROR(errMsg.c_str());
}

bool
TfType::IsEnumType() const
{
    ScopedLock lock(_info->mutex, /*write=*/false);
    return _info->isEnumType;
}

bool
TfType::IsPlainOldDataType() const
{
    ScopedLock lock(_info->mutex, /*write=*/false);
    return _info->isPodType;
}

size_t
TfType::GetSizeof() const
{
    ScopedLock lock(_info->mutex, /*write=*/false);
    return _info->sizeofType;
}


TF_REGISTRY_FUNCTION(TfType)
{
    TfType::Define<void>();
    TfType::Define<bool>();
    TfType::Define<char>();
    TfType::Define<signed char>();
    TfType::Define<unsigned char>();
    TfType::Define<short>();
    TfType::Define<unsigned short>();
    TfType::Define<int>();
    TfType::Define<unsigned int>();
    TfType::Define<long>();
    TfType::Define<unsigned long>().AddAlias( TfType::GetRoot(), "size_t" );
    TfType::Define<long long>();
    TfType::Define<unsigned long long>();    
    TfType::Define<float>();
    TfType::Define<double>();
    TfType::Define<string>();

    TfType::Define< vector<bool> >()
        .AddAlias( TfType::GetRoot(), "vector<bool>" );
    TfType::Define< vector<char> >()
        .AddAlias( TfType::GetRoot(), "vector<char>" );
    TfType::Define< vector<unsigned char> >()
        .AddAlias( TfType::GetRoot(), "vector<unsigned char>" );
    TfType::Define< vector<short> >()
        .AddAlias( TfType::GetRoot(), "vector<short>" );
    TfType::Define< vector<unsigned short> >()
        .AddAlias( TfType::GetRoot(), "vector<unsigned short>" );
    TfType::Define< vector<int> >()
        .AddAlias( TfType::GetRoot(), "vector<int>" );
    TfType::Define< vector<unsigned int> >()
        .AddAlias( TfType::GetRoot(), "vector<unsigned int>" );
    TfType::Define< vector<long> >()
        .AddAlias( TfType::GetRoot(), "vector<long>" );

    TfType ulvec = TfType::Define< vector<unsigned long> >();
    ulvec.AddAlias( TfType::GetRoot(), "vector<unsigned long>" );
    ulvec.AddAlias( TfType::GetRoot(), "vector<size_t>" );

    TfType::Define< vector<long long> >()
        .AddAlias( TfType::GetRoot(), "vector<long long>" );
    TfType::Define< vector<unsigned long long> >()
        .AddAlias( TfType::GetRoot(), "vector<unsigned long long>" );

    TfType::Define< vector<float> >()
        .AddAlias( TfType::GetRoot(), "vector<float>" );
    TfType::Define< vector<double> >()
        .AddAlias( TfType::GetRoot(), "vector<double>" );
    TfType::Define< vector<string> >()
        .AddAlias( TfType::GetRoot(), "vector<string>" );

    // Register TfType itself.
    TfType::Define<TfType>();
}

std::ostream &
operator<<(std::ostream& out, const TfType& t)
{
    return out << t.GetTypeName();
}
