// Bindings
#include "capi.h"
#include "cpp_cppyy.h"
#include "callcontext.h"

// ROOT
#include "TBaseClass.h"
#include "TClass.h"
#include "TClassRef.h"
#include "TClassTable.h"
#include "TClassEdit.h"
#include "TCollection.h"
#include "TDataMember.h"
#include "TDataType.h"
#include "TError.h"
#include "TFunction.h"
#include "TGlobal.h"
#include "TInterpreter.h"
#include "TList.h"
#include "TMethod.h"
#include "TMethodArg.h"
#include "TROOT.h"
#include "TSystem.h"

// Standard
#include <assert.h>
#include <algorithm>     // for std::count
#include <dlfcn.h>
#include <map>
#include <set>
#include <sstream>
#include <stdlib.h>      // for getenv

// temp
#include <iostream>
typedef PyROOT::TParameter TParameter;
// --temp


// small number that allows use of stack for argument passing
const int SMALL_ARGS_N = 8;

// data for life time management ---------------------------------------------
typedef std::vector<TClassRef> ClassRefs_t;
static ClassRefs_t g_classrefs(1);
static const ClassRefs_t::size_type GLOBAL_HANDLE = 1;

typedef std::map<std::string, ClassRefs_t::size_type> Name2ClassRefIndex_t;
static Name2ClassRefIndex_t g_name2classrefidx;

typedef std::map<Cppyy::TCppMethod_t, CallFunc_t*> Method2CallFunc_t;
static Method2CallFunc_t g_method2callfunc;

typedef std::vector<TGlobal*> GlobalVars_t;
static GlobalVars_t g_globalvars;

// data ----------------------------------------------------------------------
Cppyy::TCppScope_t Cppyy::gGlobalScope = GLOBAL_HANDLE;

// smart pointer types
static std::set< std::string > gSmartPtrTypes =
    {"auto_ptr", "shared_ptr", "weak_ptr", "unique_ptr"};

// configuration
static bool gEnableFastPath = true;


// global initialization -----------------------------------------------------
namespace {

class ApplicationStarter {
public:
    ApplicationStarter() {
    // setup dummy holders for global and std namespaces
        assert(g_classrefs.size() == GLOBAL_HANDLE);
        g_name2classrefidx[""]      = GLOBAL_HANDLE;
        g_classrefs.push_back(TClassRef(""));

    // aliases for std (setup already in pythonify)
        g_name2classrefidx["std"]   = GLOBAL_HANDLE+1;
        g_name2classrefidx["::std"] = g_name2classrefidx["std"];
        g_classrefs.push_back(TClassRef("std"));

    // add a dummy global to refer to as null at index 0
        g_globalvars.push_back(nullptr);

    // disable fast path if requested
        if (getenv("CPPYY_DISABLE_FASTPATH")) gEnableFastPath = false;
    }

    ~ApplicationStarter() {
        for (auto ifunc : g_method2callfunc)
            gInterpreter->CallFunc_Delete(ifunc.second);
    }
} _applicationStarter;

} // unnamed namespace


// local helpers -------------------------------------------------------------
static inline
TClassRef& type_from_handle(Cppyy::TCppScope_t scope)
{
    assert((ClassRefs_t::size_type)scope < g_classrefs.size());
    return g_classrefs[(ClassRefs_t::size_type)scope];
}

// type_from_handle to go here
static inline
TFunction* type_get_method(Cppyy::TCppType_t klass, Cppyy::TCppIndex_t idx)
{
    TClassRef& cr = type_from_handle(klass);
    if (cr.GetClass())
        return (TFunction*)cr->GetListOfMethods()->At(idx);
    assert(klass == (Cppyy::TCppType_t)GLOBAL_HANDLE);
    return (TFunction*)idx;
}

static inline
Cppyy::TCppScope_t declaring_scope(Cppyy::TCppMethod_t method)
{
    TMethod* m = dynamic_cast<TMethod*>((TFunction*)method);
    if (m) return Cppyy::GetScope(m->GetClass()->GetName());
    return (Cppyy::TCppScope_t)GLOBAL_HANDLE;
}

static inline
char* cppstring_to_cstring(const std::string& cppstr) {
    char* cstr = (char*)malloc(cppstr.size()+1);
    memcpy(cstr, cppstr.c_str(), cppstr.size()+1);
    return cstr;
}

static inline 
bool match_name(const std::string& tname, const std::string fname) {
// either match exactly, or match the name as template
    if (fname.rfind(tname, 0) == 0) {
        if ( (tname.size() == fname.size()) ||
              (tname.size() < fname.size() && fname[tname.size()] == '<') )
           return true;
    }
    return false;
}


// name to opaque C++ scope representation -----------------------------------
Cppyy::TCppIndex_t Cppyy::GetNumScopes(TCppScope_t scope)
{
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass()) {
    // this is expensive, but this function is only ever called for __dir__
    // TODO: rewrite __dir__ on the C++ side for a single loop
        std::string s = GetFinalName(scope); s += "::";
        gClassTable->Init(); 
        const int N = gClassTable->Classes();
        int total = 0;
        for (int i = 0; i < N; ++i) {
            if (strncmp(gClassTable->Next(), s.c_str(), s.size()) == 0)
                total += 1;
        }
        return total;
    }
    assert(scope == (TCppScope_t)GLOBAL_HANDLE);
    return gClassTable->Classes();
}

std::string Cppyy::GetScopeName(TCppScope_t parent, TCppIndex_t iscope)
{
// Retrieve the scope name of the scope indexed with iscope in parent.
    TClassRef& cr = type_from_handle(parent);
    if (cr.GetClass()) {
    // this is expensive (quadratic in number of classes), but only ever called for __dir__
    // TODO: rewrite __dir__ on the C++ side for a single loop
        std::string s = GetFinalName(parent); s += "::";
        gClassTable->Init();
        const int N = gClassTable->Classes();
        int match = 0;
        for (int i = 0; i < N; ++i) {
            char* cname = gClassTable->Next();
            if (strncmp(cname, s.c_str(), s.size()) == 0 && match++ == iscope) {
                std::string ret( cname+ s.size() );
                return ret.substr(0, ret.find( "::" ) ); // TODO: may mean duplicates
            }
        }
        // should never get here ... fall through will fail on assert below
    }
    assert(parent == (TCppScope_t)GLOBAL_HANDLE);
    std::string name = gClassTable->At(iscope);
    if (name.find("::") == std::string::npos)
        return name;
    return "";
}

std::string Cppyy::ResolveName(const std::string& cppitem_name)
{
// Fully resolve the given name to the final type name.
    std::string tclean = TClassEdit::CleanType(cppitem_name.c_str());
    if (tclean.empty() /* unknown, eg. an operator */) return cppitem_name;

    TDataType* dt = gROOT->GetType(tclean.c_str());
    if (dt) return dt->GetFullTypeName();
    return TClassEdit::ResolveTypedef(tclean.c_str(), true);
}

Cppyy::TCppScope_t Cppyy::GetScope(const std::string& sname)
{
// TODO: is stripping of std:: still necessary?
    std::string scope_name;
    if (sname.find("std::", 0, 5) == 0)
        scope_name = sname.substr(5, std::string::npos);
    else
        scope_name = sname;

    scope_name = ResolveName(scope_name);
    auto icr = g_name2classrefidx.find(scope_name);
    if (icr != g_name2classrefidx.end())
        return (TCppType_t)icr->second;

// use TClass directly, to enable auto-loading; class may be stubbed (eg. for
// function returns) leading to a non-null TClass that is otherwise invalid
    TClassRef cr(TClass::GetClass(scope_name.c_str(), true, true));
    if (!cr.GetClass() || !cr->Property())
        return (TCppScope_t)NULL;

   // no check for ClassInfo as forward declared classes are okay (fragile)

    ClassRefs_t::size_type sz = g_classrefs.size();
    g_name2classrefidx[scope_name] = sz;
    g_classrefs.push_back(TClassRef(scope_name.c_str()));
    return (TCppScope_t)sz;
}

bool Cppyy::IsTemplate(const std::string& template_name)
{
    return (bool)gInterpreter->CheckClassTemplate(template_name.c_str());
}

Cppyy::TCppType_t Cppyy::GetActualClass(TCppType_t klass, TCppObject_t obj)
{
    TClassRef& cr = type_from_handle(klass);
    TClass* clActual = cr->GetActualClass((void*)obj);
    if (clActual && clActual != cr.GetClass()) {
        // TODO: lookup through name should not be needed
        return (TCppType_t)GetScope(clActual->GetName());
    }
    return klass;
}

size_t Cppyy::SizeOf(TCppType_t klass)
{
    TClassRef& cr = type_from_handle(klass);
    if (cr.GetClass()) return (size_t)cr->Size();
    return (size_t)0;
}

bool Cppyy::IsBuiltin(const std::string& type_name)
{
    TDataType* dt = gROOT->GetType(TClassEdit::CleanType(type_name.c_str(), 1).c_str());
    if (dt) return dt->GetType() != kOther_t;
    return false;
}

bool Cppyy::IsComplete(const std::string& type_name)
{
// verify whether the dictionary of this class is fully available
    bool b = false;

    Int_t oldEIL = gErrorIgnoreLevel;
    gErrorIgnoreLevel = 3000;
    TClass* klass = TClass::GetClass(TClassEdit::ShortType(type_name.c_str(), 1).c_str());
    if (klass && klass->GetClassInfo())     // works for normal case w/ dict
        b = gInterpreter->ClassInfo_IsLoaded( klass->GetClassInfo() );
    else {    // special case for forward declared classes
        ClassInfo_t* ci = gInterpreter->ClassInfo_Factory(type_name.c_str());
        if (ci) {
            b = gInterpreter->ClassInfo_IsLoaded(ci);
            gInterpreter->ClassInfo_Delete(ci);  // we own the fresh class info
        }
    }
    gErrorIgnoreLevel = oldEIL;
    return b;
}

// memory management ---------------------------------------------------------
Cppyy::TCppObject_t Cppyy::Allocate(TCppType_t type)
{
    TClassRef& cr = type_from_handle(type);
    return (TCppObject_t)malloc(cr->Size());
}

void Cppyy::Deallocate(TCppType_t /* type */, TCppObject_t instance)
{
    free(instance);
}

Cppyy::TCppObject_t Cppyy::Construct(TCppType_t type)
{
    TClassRef& cr = type_from_handle(type);
    return (TCppObject_t)cr->New();
}

void Cppyy::Destruct(TCppType_t type, TCppObject_t instance)
{
    TClassRef& cr = type_from_handle(type);
    cr->Destructor((void*)instance);
}


// method/function dispatching -----------------------------------------------
static inline ClassInfo_t* GetGlobalNamespaceInfo()
{
    static ClassInfo_t* gcl = gInterpreter->ClassInfo_Factory();
    return gcl;
}

static CallFunc_t* GetCallFunc(Cppyy::TCppMethod_t method)
{
    auto icf = g_method2callfunc.find(method);
    if (icf != g_method2callfunc.end())
        return icf->second;

    CallFunc_t* callf = nullptr;
    TFunction* func = (TFunction*)method;
    std::string callString = "";

// create, if not cached
    Cppyy::TCppScope_t scope = declaring_scope(method);
    const TClassRef& klass = type_from_handle(scope);
    if (klass.GetClass() || (func && scope == GLOBAL_HANDLE)) {
        ClassInfo_t* gcl = klass.GetClass() ? klass->GetClassInfo() : nullptr;
        if (!gcl)
            gcl = GetGlobalNamespaceInfo();

        TCollection* method_args = func->GetListOfMethodArgs();
        TIter iarg(method_args);

        TMethodArg* method_arg = 0;
        while ((method_arg = (TMethodArg*)iarg.Next())) {
            std::string fullType = method_arg->GetTypeNormalizedName();
            if (callString.empty())
                callString = fullType;
            else
                callString += ", " + fullType;
        }

        Long_t offset = 0;
        callf = gInterpreter->CallFunc_Factory();

        gInterpreter->CallFunc_SetFuncProto(
            callf,
            gcl,
            func ? func->GetName() : klass->GetName(),
            callString.c_str(),
            func ? (func->Property() & kIsConstMethod) : false,
            &offset,
            ROOT::kExactMatch);

// CLING WORKAROUND -- The number of arguments is not always correct (e.g. when there
//                     are default parameters, causing the callString to be wrong and
//                     the exact match to fail); or the method may have been inline or
//                     be compiler generated. In all those cases the exact match fails,
//                     whereas the conversion match sometimes works.
        if (!gInterpreter->CallFunc_IsValid(callf)) {
            gInterpreter->CallFunc_SetFuncProto(
                callf,
                gcl,
                func ? func->GetName() : klass->GetName(),
                callString.c_str(),
                func ? (func->Property() & kIsConstMethod) : false,
                &offset);    // <- no kExactMatch as that will fail
        }
// -- CLING WORKAROUND

    }

    if (!(callf && gInterpreter->CallFunc_IsValid(callf))) {
    // TODO: propagate this error to caller w/o use of Python C-API
    /*
        PyErr_Format( PyExc_RuntimeError, "could not resolve %s::%s(%s)",
            const_cast<TClassRef&>(klass).GetClassName(),
            func ? func->GetName() : const_cast<TClassRef&>(klass).GetClassName(),
            callString.c_str() ); */
        std::cerr << "TODO: report unresolved function error to Python\n";
        if (callf) gInterpreter->CallFunc_Delete(callf);
        return nullptr;
    }

    g_method2callfunc[ method ] = callf;
    return callf;
}

static inline
void copy_args(void* args_, void** vargs) {
    std::vector<TParameter>& args = *(std::vector<TParameter>*)args_;
    for (std::vector<TParameter>::size_type i = 0; i < args.size(); ++i) {
        switch (args[i].fTypeCode) {
        case 'b':       /* bool */
            vargs[i] = (void*)&args[i].fValue.fBool;
            break;
        case 'h':       /* short */
            vargs[i] = (void*)&args[i].fValue.fShort;
            break;
        case 'H':       /* unsigned short */
            vargs[i] = (void*)&args[i].fValue.fUShort;
           break;
        case 'i':       /* int */
            vargs[i] = (void*)&args[i].fValue.fInt;
           break;
        case 'I':       /* unsigned int */
            vargs[i] = (void*)&args[i].fValue.fUInt;
            break;
        case 'l':       /* long */
            vargs[i] = (void*)&args[i].fValue.fLong;
            break;
        case 'L':       /* unsigned long */
            vargs[i] = (void*)&args[i].fValue.fULong;
            break;
        case 'q':       /* long long */
            vargs[i] = (void*)&args[i].fValue.fLongLong;
            break;
        case 'Q':       /* unsigned long long */
            vargs[i] = (void*)&args[i].fValue.fULongLong;
            break;
        case 'f':       /* float */
            vargs[i] = (void*)&args[i].fValue.fFloat;
            break;
        case 'd':       /* double */
            vargs[i] = (void*)&args[i].fValue.fDouble;
            break;
        case 'g':       /* long double */
            vargs[i] = (void*)&args[i].fValue.fLongDouble;
            break;
        case 'a':
        case 'o':
        case 'p':       /* void* */
            vargs[i] = (void*)&args[i].fValue.fVoidp;
            break;
        case 'V':       /* (void*)type& */
            vargs[i] = args[i].fValue.fVoidp;
            break;
        case 'r':       /* const type& */
            vargs[i] = args[i].fRef;
            break;
        default:
            std::cerr << "unknown type code: " << args[i].fTypeCode << std::endl;
            break;
        }
    }
}

bool FastCall(Cppyy::TCppMethod_t method, void* args_, void* self, void* result)
{
    const std::vector<TParameter>& args = *(std::vector<TParameter>*)args_;

    CallFunc_t* callf = GetCallFunc( method );
    if (!callf)
        return false;

    TInterpreter::CallFuncIFacePtr_t faceptr = gCling->CallFunc_IFacePtr(callf);
    if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kGeneric) {
        if (args.size() <= SMALL_ARGS_N) {
            void* smallbuf[SMALL_ARGS_N];
            copy_args(args_, smallbuf);
            faceptr.fGeneric(self, args.size(), smallbuf, result);
        } else {
            std::vector<void*> buf(args.size());
            copy_args(args_, buf.data());
            faceptr.fGeneric(self, args.size(), buf.data(), result);
        }
        return true;
    }

    if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kCtor) {
        if (args.size() <= SMALL_ARGS_N) {
            void* smallbuf[SMALL_ARGS_N];
            copy_args(args_, (void**)smallbuf);
            faceptr.fCtor((void**)smallbuf, result, args.size());
        } else {
            std::vector<void*> buf(args.size());
            copy_args(args_, buf.data());
            faceptr.fCtor(buf.data(), result, args.size());
        }
       return true;
    }

    if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kDtor) {
        std::cerr << " DESTRUCTOR NOT IMPLEMENTED YET! " << std::endl;
        return false;
    }

    return false;
}

template< typename T >
static inline
T CallT(Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, void* args)
{
    T t{};
    if (FastCall(method, args, (void*)self, &t))
        return t;
    return (T)-1;
}

#define CPPYY_IMP_CALL(typecode, rtype)                                      \
rtype Cppyy::Call##typecode(TCppMethod_t method, TCppObject_t self, void* args)\
{                                                                            \
    return CallT< rtype >(method, self, args);                               \
}

void Cppyy::CallV(TCppMethod_t method, TCppObject_t self, void* args)
{
   if (!FastCall(method, args, (void*)self, nullptr))
       return /* TODO ... report error */;
}

CPPYY_IMP_CALL(B,  UChar_t     )
CPPYY_IMP_CALL(C,  Char_t      )
CPPYY_IMP_CALL(H,  Short_t     )
CPPYY_IMP_CALL(I,  Int_t       )
CPPYY_IMP_CALL(L,  Long_t      )
CPPYY_IMP_CALL(LL, Long64_t    )
CPPYY_IMP_CALL(F,  Float_t     )
CPPYY_IMP_CALL(D,  Double_t    )
CPPYY_IMP_CALL(LD, LongDouble_t)

void* Cppyy::CallR(TCppMethod_t method, TCppObject_t self, void* args)
{
    void* r = nullptr;
    if (FastCall(method, args, (void*)self, &r))
        return r;
    return nullptr;
}

Char_t* Cppyy::CallS(
       TCppMethod_t method, TCppObject_t self, void* args, size_t* length)
{
    char* cstr = nullptr;
    TClassRef cr("std::string");
    std::string* cppresult = (std::string*)malloc(sizeof(std::string));
    if (FastCall( method, args, self, (void*)cppresult)) {
        cstr = cppstring_to_cstring(*cppresult);
        *length = cppresult->size();
        cppresult->std::string::~basic_string();
    } else
        *length = 0;
    free((void*)cppresult); 
    return cstr;
}

Cppyy::TCppObject_t Cppyy::CallConstructor(
        TCppMethod_t method, TCppType_t /* klass */, void* args) {
    void* obj = nullptr;
    if (FastCall(method, args, nullptr, &obj))
        return (TCppObject_t)obj;
    return (TCppObject_t)0;
}

void Cppyy::CallDestructor(TCppType_t type, TCppObject_t self)
{
    TClassRef& cr = type_from_handle(type);
    cr->Destructor((void*)self, true);
}

Cppyy::TCppObject_t Cppyy::CallO(TCppMethod_t method,
        TCppObject_t self, void* args, TCppType_t result_type)
{
    TClassRef& cr = type_from_handle(result_type);
    void* obj = malloc(cr->Size());
    if (FastCall(method, args, self, obj))
        return (TCppObject_t)obj;
    return (TCppObject_t)0;
}

Cppyy::TCppFuncAddr_t Cppyy::GetFunctionAddress(TCppScope_t scope, TCppIndex_t imeth)
{
    if (!gEnableFastPath) return (TCppFuncAddr_t)nullptr;
    TFunction* f = type_get_method(scope, imeth);
    return (TCppFuncAddr_t)dlsym(RTLD_DEFAULT, f->GetMangledName());
}


// handling of function argument buffer --------------------------------------
void* Cppyy::AllocateFunctionArgs(size_t nargs)
{
    return new TParameter[nargs];
}

void Cppyy::DeallocateFunctionArgs(void* args)
{
    delete [] (TParameter*)args;
}

size_t Cppyy::GetFunctionArgSizeof()
{
    return sizeof( TParameter );
}

size_t Cppyy::GetFunctionArgTypeoffset()
{
    return offsetof(TParameter, fTypeCode);
}


// scope reflection information ----------------------------------------------
bool Cppyy::IsNamespace(TCppScope_t scope) {
// Test if this scope represents a namespace.
    if (scope == GLOBAL_HANDLE)
        return true;
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass())
        return cr->Property() & kIsNamespace;
    return false;
}

bool Cppyy::IsAbstract(TCppType_t klass) {
// Test if this type may not be instantiated.
    TClassRef& cr = type_from_handle(klass);
    if (cr.GetClass())
        return cr->Property() & kIsAbstract;
    return false;
}

bool Cppyy::IsEnum( const std::string& type_name ) {
    if (type_name.empty()) return false;
    return gInterpreter->ClassInfo_IsEnum(type_name.c_str());
}


// class reflection information ----------------------------------------------
std::string Cppyy::GetFinalName(TCppType_t klass)
{
    if (klass == GLOBAL_HANDLE)
        return "";
    TClassRef& cr = type_from_handle(klass);
    std::string clName = cr->GetName();
    std::string::size_type pos = clName.substr(0, clName.find('<')).rfind("::");
    if (pos != std::string::npos)
        return clName.substr(pos+2, std::string::npos);
    return clName;
}

std::string Cppyy::GetScopedFinalName(TCppType_t klass)
{
    TClassRef& cr = type_from_handle(klass);
    if (cr.GetClass())
        return cr->GetName();
    return "";
}

bool Cppyy::HasComplexHierarchy(TCppType_t klass)
{
    int is_complex = 1;
    size_t nbases = 0;

    TClassRef& cr = type_from_handle(klass);
    if (cr.GetClass() && cr->GetListOfBases() != 0)
        nbases = GetNumBases( klass );

    if (1 < nbases)
        is_complex = 1;
    else if (nbases == 0)
        is_complex = 0;
    else {         // one base class only
        TBaseClass* base = (TBaseClass*)cr->GetListOfBases()->At(0);
        if (base->Property() & kIsVirtualBase)
            is_complex = 1;       // TODO: verify; can be complex, need not be.
        else
            is_complex = HasComplexHierarchy(GetScope(base->GetName()));
    }

    return is_complex;
}

Cppyy::TCppIndex_t Cppyy::GetNumBases(TCppType_t klass)
{
// Get the total number of base classes that this class has.
    TClassRef& cr = type_from_handle(klass);
    if (cr.GetClass() && cr->GetListOfBases() != 0)
        return cr->GetListOfBases()->GetSize();
    return 0;
}

std::string Cppyy::GetBaseName(TCppType_t klass, TCppIndex_t ibase)
{
    TClassRef& cr = type_from_handle(klass);
    return ((TBaseClass*)cr->GetListOfBases()->At(ibase))->GetName();
}

bool Cppyy::IsSubtype(TCppType_t derived, TCppType_t base)
{
    if (derived == base)
        return true;
    TClassRef& derived_type = type_from_handle(derived);
    TClassRef& base_type = type_from_handle(base);
    return derived_type->GetBaseClass(base_type) != 0;
}

void Cppyy::AddSmartPtrType(const std::string& type_name) {
    gSmartPtrTypes.insert(ResolveName(type_name));
}

bool Cppyy::IsSmartPtr(const std::string& type_name) {
// checks if typename denotes a smart pointer
// TODO: perhaps make this stricter?
    const std::string& real_name = ResolveName(type_name);
    return gSmartPtrTypes.find(
        real_name.substr(0, real_name.find("<"))) != gSmartPtrTypes.end();
}

// type offsets --------------------------------------------------------------
ptrdiff_t Cppyy::GetBaseOffset(TCppType_t derived, TCppType_t base,
        TCppObject_t address, int direction, bool rerror)
{
// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0
    if (derived == base || !(base && derived))
        return (ptrdiff_t)0;

    TClassRef& cd = type_from_handle(derived);
    TClassRef& cb = type_from_handle(base);

    if (!cd.GetClass() || !cb.GetClass())
        return (ptrdiff_t)0;

    Long_t offset = -1;
    if (!(cd->GetClassInfo() && cb->GetClassInfo())) {     // gInterpreter requirement
    // would like to warn, but can't quite determine error from intentional
    // hiding by developers, so only cover the case where we really should have
    // had a class info, but apparently don't:
        if (cd->IsLoaded()) {
        // warn to allow diagnostics
            std::ostringstream msg;
            msg << "failed offset calculation between " << cb->GetName() << " and " << cd->GetName();
            // TODO: propagate this warning to caller w/o use of Python C-API
            // PyErr_Warn( PyExc_RuntimeWarning, const_cast<char*>( msg.str().c_str() ) );
            std::cerr << "Warning: " << msg.str() << '\n';
        }

    // return -1 to signal caller NOT to apply offset
        return rerror ? (ptrdiff_t)offset : 0;
    }

    offset = gInterpreter->ClassInfo_GetBaseOffset(
        cd->GetClassInfo(), cb->GetClassInfo(), (void*)address, direction > 0);
    if (offset == -1)   // Cling error, treat silently
        return rerror ? (ptrdiff_t)offset : 0;

    return (ptrdiff_t)(direction < 0 ? -offset : offset);
}


// method/function reflection information ------------------------------------
Cppyy::TCppIndex_t Cppyy::GetNumMethods(TCppScope_t scope)
{
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass() && cr->GetListOfMethods()) {
        Cppyy::TCppIndex_t nMethods = (TCppIndex_t)cr->GetListOfMethods()->GetSize();
        if (nMethods == (TCppIndex_t)0) {
            std::string clName = GetScopedFinalName(scope);
            if (clName.find('<') != std::string::npos) {
            // chicken-and-egg problem: TClass does not know about methods until instantiation: force it
                if (TClass::GetClass( ("std::"+clName).c_str()))
                    clName = "std::" + clName;
                std::ostringstream stmt;
                stmt << "template class " << clName << ";";
                gInterpreter->Declare(stmt.str().c_str());

            // now reload the methods
                return (TCppIndex_t)cr->GetListOfMethods( true )->GetSize();
            }
        }
        return nMethods;
    } else if (scope == (TCppScope_t)GLOBAL_HANDLE) {
    // enforce lazines by denying the existence of methods
        return (TCppIndex_t)0;
    }
    return (TCppIndex_t)0;
}

Cppyy::TCppIndex_t Cppyy::GetMethodIndexAt(TCppScope_t scope, TCppIndex_t imeth)
{
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass())
        return (TCppIndex_t)imeth;
    assert(scope == (TCppType_t)GLOBAL_HANDLE);
    return imeth;
}

std::vector<Cppyy::TCppIndex_t> Cppyy::GetMethodIndicesFromName(
      TCppScope_t scope, const std::string& name)
{
    std::vector<TCppIndex_t> indices;
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass()) {
        gInterpreter->UpdateListOfMethods(cr.GetClass());
        int imeth = 0;
        TFunction* func;
        TIter next(cr->GetListOfMethods()); 
        while ((func = (TFunction*)next())) {
            if (match_name(name, func->GetName())) {
                if (Cppyy::IsPublicMethod((TCppMethod_t)func))
                    indices.push_back((TCppIndex_t)imeth);
            }
            ++imeth;
        }
    } else if (scope == (TCppScope_t)GLOBAL_HANDLE) {
        TCollection* funcs = gROOT->GetListOfGlobalFunctions(true);
        
        // tickle deserialization
        if (!funcs->FindObject(name.c_str()))
            return indices;

        TFunction* func = 0;
        TIter ifunc(funcs);
        while ((func = (TFunction*)ifunc.Next())) {
            if (match_name(name, func->GetName()))
                indices.push_back((TCppIndex_t)func);
        }
    }

    return indices;
}

Cppyy::TCppMethod_t Cppyy::GetMethod(TCppScope_t scope, TCppIndex_t imeth)
{
    TFunction* f = type_get_method(scope, imeth);
    return (Cppyy::TCppMethod_t)f;
}

std::string Cppyy::GetMethodName(TCppMethod_t method)
{
    if (method) {
        std::string name = ((TFunction*)method)->GetName();
    // strip template instantiation part, if any
        return name.substr(0, name.find('<'));
    }
    return "<unknown>";
}

std::string Cppyy::GetMethodMangledName(TCppMethod_t method)
{
    if (method)
        return ((TFunction*)method)->GetMangledName();
    return "<unknown>";
}

std::string Cppyy::GetMethodResultType(TCppMethod_t method)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        if (f->ExtraProperty() & kIsConstructor)
            return "constructor";
        return f->GetReturnTypeNormalizedName();
     }
     return "<unknown>";
}

Cppyy::TCppIndex_t Cppyy::GetMethodNumArgs(TCppMethod_t method)
{
    if (method)
        return ((TFunction*)method)->GetNargs();
    return 0;
}

Cppyy::TCppIndex_t Cppyy::GetMethodReqArgs(TCppMethod_t method)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        return (TCppIndex_t)(f->GetNargs() - f->GetNargsOpt());
    }
    return (TCppIndex_t)0;
}

std::string Cppyy::GetMethodArgName(TCppMethod_t method, int iarg)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At(iarg);
        return arg->GetName();
    }
    return "<unknown>";
}

std::string Cppyy::GetMethodArgType(TCppMethod_t method, int iarg)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At(iarg);
        return arg->GetTypeNormalizedName();
    }
    return "<unknown>";
}

std::string Cppyy::GetMethodArgDefault(TCppMethod_t method, int iarg)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At(iarg);
        const char* def = arg->GetDefault();
        if (def)
            return def;
    }

    return "";
}

std::string Cppyy::GetMethodSignature(TCppScope_t scope, TCppIndex_t imeth)
{
    TClassRef& cr = type_from_handle(scope);
    TFunction* f = type_get_method(scope, imeth);
    if (cr.GetClass() && cr->GetClassInfo()) {
        std::ostringstream sig;
        sig << f->GetReturnTypeName() << " "
            << cr.GetClassName() << "::" << f->GetName() << "(";
        int nArgs = f->GetNargs();
        for (int iarg = 0; iarg < nArgs; ++iarg) {
            sig << ((TMethodArg*)f->GetListOfMethodArgs()->At(iarg))->GetFullTypeName();
            if (iarg != nArgs-1)
               sig << ", ";
        }
        sig << ")" << std::ends;
        return cppstring_to_cstring(sig.str());
    }
    return "<unknown>";
}

bool Cppyy::IsConstMethod(TCppMethod_t method)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        return f->Property() & kIsConstMethod;
    }
    return false;
}

bool Cppyy::ExistsMethodTemplate(TCppScope_t scope, const std::string& name)
{
    if (scope == (cppyy_scope_t)GLOBAL_HANDLE)
        return (bool)gROOT->GetFunctionTemplate(name.c_str());
    else {
        TClassRef& cr = type_from_handle(scope);
        if (cr.GetClass())
            return (bool)cr->GetFunctionTemplate(name.c_str());
    }

// failure ...
    return false;
}

bool Cppyy::IsMethodTemplate(TCppScope_t scope, TCppIndex_t imeth)
{
    TFunction* f = type_get_method(scope, imeth);
    if (!f) return false;

    if (scope == (Cppyy::TCppType_t)GLOBAL_HANDLE) {
    // TODO: figure this one out ...
        return false;
    } else {
        TClassRef& cr = type_from_handle(scope);
        if (cr.GetClass()) {
            return (bool)cr->GetFunctionTemplate(f->GetName());
        }
    }
    return false;
}

Cppyy::TCppIndex_t Cppyy::GetMethodNumTemplateArgs(
      TCppScope_t scope, TCppIndex_t imeth)
{
// this is dumb, but the fact that Cling can instantiate template
// methods on-the-fly means that there is some vast reworking TODO
// in interp_cppyy.py, so this is just to make the original tests
// pass that worked in the Reflex era ...
    const std::string name = GetMethodName(GetMethod(scope, imeth));
    return (TCppIndex_t)(std::count(name.begin(), name.end(), ',')+1);
}

std::string Cppyy::GetMethodTemplateArgName(
        TCppScope_t scope, TCppIndex_t imeth, TCppIndex_t /* iarg */)
{
// TODO: like above, given Cling's instantiation capability, this
// is just dumb ...
    TFunction* f = type_get_method(scope, imeth);
    std::string name = f->GetName();
    std::string::size_type pos = name.find('<');
// TODO: left as-is, this should loop over arguments, but what is here
// suffices to pass the Reflex-based tests (need more tests :) )
    return cppstring_to_cstring(
        ResolveName( name.substr(pos+1, name.size()-pos-2)));
}

Cppyy::TCppMethod_t Cppyy::GetMethodTemplate(
        TCppScope_t scope, const std::string& name, const std::string& proto)
{
    TFunction* func = nullptr;
    if (scope == (cppyy_scope_t)GLOBAL_HANDLE) {
        func = gROOT->GetGlobalFunctionWithPrototype(name.c_str(), proto.c_str());
    } else {
        TClassRef& cr = type_from_handle(scope);
        if (cr.GetClass())
            func = cr->GetMethodWithPrototype(name.c_str(), proto.c_str());
    }

// failure ...
    return (TCppMethod_t)func;
}

Cppyy::TCppIndex_t Cppyy::GetGlobalOperator(
        TCppScope_t scope, TCppType_t lc, TCppType_t rc, const std::string& opname)
{
// Find a global operator function with a matching signature
    std::string proto = GetScopedFinalName(lc) + ", " + GetScopedFinalName(rc);
    if (scope == (cppyy_scope_t)GLOBAL_HANDLE) {
        TFunction* func = gROOT->GetGlobalFunctionWithPrototype(opname.c_str(), proto.c_str());
        if (func) return (TCppIndex_t)func;
    } else {
        TClassRef& cr = type_from_handle(scope);
        if (cr.GetClass()) {
            TFunction* func = cr->GetMethodWithPrototype(opname.c_str(), proto.c_str());
            if (func) return (TCppIndex_t)cr->GetListOfMethods()->IndexOf(func);
        }
    }

// failure ...
    return (TCppIndex_t)-1;
}

// method properties ---------------------------------------------------------
bool Cppyy::IsPublicMethod(TCppMethod_t method)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        return f->Property() & kIsPublic;
    }
    return false;
}

bool Cppyy::IsConstructor(TCppMethod_t method)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        return f->ExtraProperty() & kIsConstructor;
    }
    return false;
}

bool Cppyy::IsDestructor(TCppMethod_t method)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        return f->ExtraProperty() & kIsDestructor;
    }
    return false;
}

bool Cppyy::IsStaticMethod(TCppMethod_t method)
{
    if (method) {
        TFunction* f = (TFunction*)method;
        return f->Property() & kIsStatic;
    }
    return false;
}

// data member reflection information ----------------------------------------
Cppyy::TCppIndex_t Cppyy::GetNumDatamembers(TCppScope_t scope)
{
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass() && cr->GetListOfDataMembers())
        return cr->GetListOfDataMembers()->GetSize();

// global vars (and unknown classes) are always resolved lazily, so report as '0'
    return (TCppIndex_t)0;
}

std::string Cppyy::GetDatamemberName(TCppScope_t scope, TCppIndex_t idata)
{
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass()) {
        TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
        return m->GetName();
    }
    assert(scope == (TCppScope_t)GLOBAL_HANDLE);
    TGlobal* gbl = g_globalvars[idata];
    return gbl->GetName();
}

std::string Cppyy::GetDatamemberType(TCppScope_t scope, TCppIndex_t idata)
{
    if (scope == GLOBAL_HANDLE) {
        TGlobal* gbl = g_globalvars[idata];
        std::string fullType = gbl->GetFullTypeName();
        if (fullType[fullType.size()-1] == '*' && \
              !dynamic_cast<TGlobalMappedFunction*>(gbl) && \
              fullType.find("char", 0, 4) == std::string::npos)
            fullType.append("*");
        else if ((int)gbl->GetArrayDim() > 1)
            fullType.append("*");
        else if ((int)gbl->GetArrayDim() == 1) {
            std::ostringstream s;
            s << '[' << gbl->GetMaxIndex( 0 ) << ']' << std::ends;
            fullType.append(s.str());
        }
        return fullType;
    }

    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass())  {
        TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
        std::string fullType = m->GetTrueTypeName();
        if ((int)m->GetArrayDim() > 1 || (!m->IsBasic() && m->IsaPointer()))
            fullType.append("*");
        else if ((int)m->GetArrayDim() == 1) {
            std::ostringstream s;
            s << '[' << m->GetMaxIndex(0) << ']' << std::ends;
            fullType.append(s.str());
        }
        return fullType;
    }

    return "<unknown>";
}

ptrdiff_t Cppyy::GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata)
{
    if (scope == GLOBAL_HANDLE) {
        TGlobal* gbl = g_globalvars[ idata ];
        return (ptrdiff_t)gbl->GetAddress();
    }

    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass()) {
        TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
        return (ptrdiff_t)m->GetOffsetCint();    // yes, CINT (GetOffset() is both wrong
                                                 // and caches that wrong result!
    }

    return (ptrdiff_t)-1;
}

Cppyy::TCppIndex_t Cppyy::GetDatamemberIndex(TCppScope_t scope, const std::string& name)
{
    if (scope == GLOBAL_HANDLE) {
        TGlobal* gb = (TGlobal*)gROOT->GetListOfGlobals(true)->FindObject(name.c_str());
        if (gb && gb->GetAddress() && gb->GetAddress() != (void*)-1) {
            g_globalvars.push_back( gb );
            return g_globalvars.size() - 1;
        }

    } else {
        TClassRef& cr = type_from_handle(scope);
        if (cr.GetClass()) {
            TDataMember* dm =
                (TDataMember*)cr->GetListOfDataMembers()->FindObject(name.c_str());
            // TODO: turning this into an index is silly ...
            if (dm) return (TCppIndex_t)cr->GetListOfDataMembers()->IndexOf(dm);
        }
    }

    return (TCppIndex_t)-1;
}


// data member properties ----------------------------------------------------
bool Cppyy::IsPublicData(TCppScope_t scope, TCppIndex_t idata)
{
    if (scope == GLOBAL_HANDLE)
        return true;
    TClassRef& cr = type_from_handle(scope);
    if (cr->Property() & kIsNamespace)
        return true;
    TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
    return m->Property() & kIsPublic;
}

bool Cppyy::IsStaticData(TCppScope_t scope, TCppIndex_t idata)
{
    if (scope == GLOBAL_HANDLE)
        return true;
    TClassRef& cr = type_from_handle(scope);
    if (cr->Property() & kIsNamespace)
        return true;
    TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
    return m->Property() & kIsStatic;
}

bool Cppyy::IsConstData(TCppScope_t scope, TCppIndex_t idata)
{
    if (scope == GLOBAL_HANDLE) {
        TGlobal* gbl = g_globalvars[idata];
        return gbl->Property() & kIsConstant;
    }
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass()) {
        TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
        return m->Property() & kIsConstant;
    }
    return false;
}

bool Cppyy::IsEnumData(TCppScope_t scope, TCppIndex_t idata)
{
    if (scope == GLOBAL_HANDLE) {
        TGlobal* gbl = g_globalvars[idata];
        return gbl->Property() & kIsEnum;
    }
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass()) {
        TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
        return m->Property() & kIsEnum;
    }
    return false;
}

Int_t Cppyy::GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension)
{
    if (scope == GLOBAL_HANDLE) {
        TGlobal* gbl = g_globalvars[idata];
        return gbl->GetMaxIndex(dimension);
    }
    TClassRef& cr = type_from_handle(scope);
    if (cr.GetClass()) {
        TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(idata);
        return m->GetMaxIndex(dimension);
    }
    return (Int_t)-1;
}


//- C-linkage wrappers -------------------------------------------------------
static inline
std::vector<TParameter> vsargs_to_parvec(void* args, int nargs)
{
    std::vector<TParameter> v;
    v.reserve(nargs);
    for (int i=0; i<nargs; ++i)
        v.push_back(((TParameter*)args)[i]);
    return v;
}

extern "C" {
/* name to opaque C++ scope representation -------------------------------- */
int cppyy_num_scopes(cppyy_scope_t parent) {
    return (int)Cppyy::GetNumScopes(parent);
}

char* cppyy_scope_name(cppyy_scope_t parent, cppyy_index_t iscope) {
    return cppstring_to_cstring(Cppyy::GetScopeName(parent, iscope));
}

char* cppyy_resolve_name(const char* cppitem_name) {
    std::string str = cppstring_to_cstring(Cppyy::ResolveName(cppitem_name));
    if (Cppyy::IsEnum(str))
        return cppstring_to_cstring("internal_enum_type_t");
    return cppstring_to_cstring(str);
}

cppyy_scope_t cppyy_get_scope(const char* scope_name) {
    return cppyy_scope_t(Cppyy::GetScope(scope_name));
}

cppyy_type_t cppyy_actual_class(cppyy_type_t klass, cppyy_object_t obj) {
    return cppyy_type_t(Cppyy::GetActualClass(klass, (void*)obj));
}

size_t cppyy_size_of(cppyy_type_t klass) {
    return Cppyy::SizeOf(klass);
}


/* memory management ------------------------------------------------------ */
cppyy_object_t cppyy_allocate(cppyy_type_t type) {
    return cppyy_object_t(Cppyy::Allocate(type));
}

void cppyy_deallocate(cppyy_type_t type, cppyy_object_t self) {
    Cppyy::Deallocate(type, (void*)self);
}

void cppyy_destruct(cppyy_type_t type, cppyy_object_t self) {
    Cppyy::Destruct(type, (void*)self);
}


/* method/function dispatching -------------------------------------------- */
void cppyy_call_v(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    Cppyy::CallV(method, (void*)self, &parvec);
}

unsigned char cppyy_call_b(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (unsigned char)Cppyy::CallB(method, (void*)self, &parvec);
}

char cppyy_call_c(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (char)Cppyy::CallC(method, (void*)self, &parvec);
}

short cppyy_call_h(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (short)Cppyy::CallH(method, (void*)self, &parvec);
}

int cppyy_call_i(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (int)Cppyy::CallI(method, (void*)self, &parvec);
}

long cppyy_call_l(cppyy_method_t method, cppyy_object_t self, int nargs, void* args){
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (long)Cppyy::CallL(method, (void*)self, &parvec);
}

long long cppyy_call_ll(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (long long)Cppyy::CallLL(method, (void*)self, &parvec);
}

float cppyy_call_f(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (float)Cppyy::CallF(method, (void*)self, &parvec);
}

double cppyy_call_d(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (double)Cppyy::CallD(method, (void*)self, &parvec);
}

long double cppyy_call_ld(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (long double)Cppyy::CallLD(method, (void*)self, &parvec);
}

void* cppyy_call_r(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return (void*)Cppyy::CallR(method, (void*)self, &parvec);
}

char* cppyy_call_s(
        cppyy_method_t method, cppyy_object_t self, int nargs, void* args, size_t* lsz) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return Cppyy::CallS(method, (void*)self, &parvec, lsz);
}

cppyy_object_t cppyy_constructor(cppyy_method_t method, cppyy_type_t klass, int nargs, void* args) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return cppyy_object_t(Cppyy::CallConstructor(method, klass, &parvec));
}

cppyy_object_t cppyy_call_o(cppyy_method_t method, cppyy_object_t self, int nargs, void* args, cppyy_type_t result_type) {
    std::vector<TParameter> parvec = vsargs_to_parvec(args, nargs);
    return cppyy_object_t(Cppyy::CallO(method, (void*)self, &parvec, result_type));
}

cppyy_funcaddr_t cppyy_get_function_address(cppyy_scope_t scope, cppyy_index_t idx) {
    return cppyy_funcaddr_t(Cppyy::GetFunctionAddress(scope, idx));
}


/* handling of function argument buffer ----------------------------------- */
void* cppyy_allocate_function_args(int nargs){
    return (void*)Cppyy::AllocateFunctionArgs(nargs);
}

void cppyy_deallocate_function_args(void* args){
    Cppyy::DeallocateFunctionArgs(args);
}

size_t cppyy_function_arg_sizeof(){
    return (size_t)Cppyy::GetFunctionArgSizeof();
}

size_t cppyy_function_arg_typeoffset(){
    return (size_t)Cppyy::GetFunctionArgTypeoffset();
}


/* scope reflection information ------------------------------------------- */
int cppyy_is_namespace(cppyy_scope_t scope) {
    return (int)Cppyy::IsNamespace(scope);
}

int cppyy_is_template(const char* template_name) {
    return (int)Cppyy::IsTemplate(template_name);
}

int cppyy_is_abstract(cppyy_type_t type){
    return (int)Cppyy::IsAbstract(type);
}

int cppyy_is_enum(const char* type_name){
    return (int)Cppyy::IsEnum(type_name);
}


/* class reflection information ------------------------------------------- */
char* cppyy_final_name(cppyy_type_t type) {
    return cppstring_to_cstring(Cppyy::GetFinalName(type));
}

char* cppyy_scoped_final_name(cppyy_type_t type) {
    return cppstring_to_cstring(Cppyy::GetScopedFinalName(type));
}

int cppyy_has_complex_hierarchy(cppyy_type_t type) {
    return (int)Cppyy::HasComplexHierarchy(type);
}

int cppyy_num_bases(cppyy_type_t type) {
    return (int)Cppyy::GetNumBases(type);
}

char* cppyy_base_name(cppyy_type_t type, int base_index){
    return cppstring_to_cstring(Cppyy::GetBaseName (type, base_index));
}

int cppyy_is_subtype(cppyy_type_t derived, cppyy_type_t base){
    return (int)Cppyy::IsSubtype(derived, base);
}


/* calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 */
ptrdiff_t cppyy_base_offset(cppyy_type_t derived, cppyy_type_t base, cppyy_object_t address, int direction) {
    return (ptrdiff_t)Cppyy::GetBaseOffset(derived, base, (void*)address, direction, 0);
}


/* method/function reflection information --------------------------------- */
int cppyy_num_methods(cppyy_scope_t scope) {
    return (int)Cppyy::GetNumMethods(scope);
}

cppyy_index_t cppyy_method_index_at(cppyy_scope_t scope, int imeth) {
    return cppyy_index_t(Cppyy::GetMethodIndexAt(scope, imeth));
}

cppyy_index_t* cppyy_method_indices_from_name(cppyy_scope_t scope, const char* name) {
    std::vector<cppyy_index_t> result = Cppyy::GetMethodIndicesFromName(scope, name);

    if (result.empty())
        return (cppyy_index_t*)nullptr;

    cppyy_index_t* llresult = (cppyy_index_t*)malloc(sizeof(cppyy_index_t)*(result.size()+1));
    for (int i = 0; i < (int)result.size(); ++i) llresult[i] = result[i];
    llresult[result.size()] = -1;
    return llresult;
}

char* cppyy_method_name(cppyy_scope_t scope, cppyy_index_t idx) {
    TFunction* f = type_get_method(scope, idx);
    return cppstring_to_cstring(Cppyy::GetMethodName((Cppyy::TCppMethod_t)f));
}

char* cppyy_method_mangled_name(cppyy_scope_t scope, cppyy_index_t idx) {
    TFunction* f = type_get_method(scope, idx);
    return cppstring_to_cstring(Cppyy::GetMethodMangledName((Cppyy::TCppMethod_t)f));
}

char* cppyy_method_result_type(cppyy_scope_t scope, cppyy_index_t idx) {
    TFunction* f = type_get_method(scope, idx);
    return cppstring_to_cstring(Cppyy::GetMethodResultType((Cppyy::TCppMethod_t)f));
}

int cppyy_method_num_args(cppyy_scope_t scope, cppyy_index_t idx) {
    TFunction* f = type_get_method(scope, idx);
    return (int)Cppyy::GetMethodNumArgs((Cppyy::TCppMethod_t)f);
}

int cppyy_method_req_args(cppyy_scope_t scope, cppyy_index_t idx) {
    TFunction* f = type_get_method(scope, idx);
    return (int)Cppyy::GetMethodReqArgs((Cppyy::TCppMethod_t)f);
}

char* cppyy_method_arg_type(cppyy_scope_t scope, cppyy_index_t idx, int arg_index) {
    TFunction* f = type_get_method(scope, idx);
    return cppstring_to_cstring(Cppyy::GetMethodArgType((Cppyy::TCppMethod_t)f, arg_index));
}

char* cppyy_method_arg_default(cppyy_scope_t scope, cppyy_index_t idx, int arg_index) {
    TFunction* f = type_get_method(scope, idx);
    return cppstring_to_cstring(Cppyy::GetMethodArgDefault((Cppyy::TCppMethod_t)f, arg_index));
}

char* cppyy_method_signature(cppyy_scope_t scope, cppyy_index_t idx) {
    return cppstring_to_cstring(Cppyy::GetMethodSignature(scope, idx));
}

int cppyy_method_is_template(cppyy_scope_t scope, cppyy_index_t idx) {
    return (int)Cppyy::IsMethodTemplate(scope, idx);
}

int cppyy_method_num_template_args(cppyy_scope_t scope, cppyy_index_t idx) {
    return (int)Cppyy::GetMethodNumTemplateArgs(scope, idx);
}

char* cppyy_method_template_arg_name(cppyy_scope_t scope, cppyy_index_t idx, cppyy_index_t iarg) {
    return cppstring_to_cstring(Cppyy::GetMethodTemplateArgName(scope, idx, iarg));
}

cppyy_method_t cppyy_get_method(cppyy_scope_t scope, cppyy_index_t idx) {
    return cppyy_method_t(Cppyy::GetMethod(scope, idx));
}

cppyy_index_t cppyy_get_global_operator(cppyy_scope_t scope, cppyy_scope_t lc, cppyy_scope_t rc, const char* op) {
    return cppyy_index_t(Cppyy::GetGlobalOperator(scope, lc, rc, op));
}


/* method properties ------------------------------------------------------ */
int cppyy_is_publicmethod(cppyy_type_t type, cppyy_index_t idx) {
    TFunction* f = type_get_method(type, idx);
    return (int)Cppyy::IsPublicMethod((Cppyy::TCppMethod_t)f);
}

int cppyy_is_constructor(cppyy_type_t type, cppyy_index_t idx) {
    TFunction* f = type_get_method(type, idx);
    return (int)Cppyy::IsConstructor((Cppyy::TCppMethod_t)f);
}

int cppyy_is_destructor(cppyy_type_t type, cppyy_index_t idx) {
    TFunction* f = type_get_method(type, idx);
    return (int)Cppyy::IsDestructor((Cppyy::TCppMethod_t)f);
}

int cppyy_is_staticmethod(cppyy_type_t type, cppyy_index_t idx) {
    TFunction* f = type_get_method(type, idx);
    return (int)Cppyy::IsStaticMethod((Cppyy::TCppMethod_t)f);
}


/* data member reflection information ------------------------------------- */
int cppyy_num_datamembers(cppyy_scope_t scope) {
    return (int)Cppyy::GetNumDatamembers(scope);
}

char* cppyy_datamember_name(cppyy_scope_t scope, int datamember_index) {
    return cppstring_to_cstring(Cppyy::GetDatamemberName(scope, datamember_index));
}

char* cppyy_datamember_type(cppyy_scope_t scope, int datamember_index) {
    return cppstring_to_cstring(Cppyy::GetDatamemberType(scope, datamember_index));
}

ptrdiff_t cppyy_datamember_offset(cppyy_scope_t scope, int datamember_index) {
    return ptrdiff_t(Cppyy::GetDatamemberOffset(scope, datamember_index));
}

int cppyy_datamember_index(cppyy_scope_t scope, const char* name) {
    return (int)Cppyy::GetDatamemberIndex(scope, name);
}



/* data member properties ------------------------------------------------- */
int cppyy_is_publicdata(cppyy_type_t type, int datamember_index) {
    return (int)Cppyy::IsPublicData(type, datamember_index);
}

int cppyy_is_staticdata(cppyy_type_t type, int datamember_index) {
    return (int)Cppyy::IsStaticData(type, datamember_index);
}


/* misc helpers ----------------------------------------------------------- */
RPY_EXTERN
void* cppyy_load_dictionary(const char* lib_name) {
    int result = gSystem->Load(lib_name);
    return (void*)(result == 0 /* success */ || result == 1 /* already loaded */);
}

long long cppyy_strtoll(const char* str) {
    return strtoll(str, NULL, 0);
}

unsigned long long cppyy_strtoull(const char* str) {
    return strtoull(str, NULL, 0);
}

void cppyy_free(void* ptr) {
    free(ptr);
}

cppyy_object_t cppyy_charp2stdstring(const char* str, size_t sz) {
    return (cppyy_object_t)new std::string(str, sz);
}

const char* cppyy_stdstring2charp(cppyy_object_t ptr, size_t* lsz) {
    *lsz = ((std::string*)ptr)->size();
    return ((std::string*)ptr)->data();
}

cppyy_object_t cppyy_stdstring2stdstring(cppyy_object_t ptr){
    return (cppyy_object_t)new std::string(*(std::string*)ptr);
}

const char* cppyy_stdvector_valuetype(const char* clname) {
    const char* result = nullptr;
    std::string name = clname;
    TypedefInfo_t* ti = gInterpreter->TypedefInfo_Factory((name+"::value_type").c_str());
    if (gInterpreter->TypedefInfo_IsValid(ti))
        result = cppstring_to_cstring(gInterpreter->TypedefInfo_TrueName(ti));
    gInterpreter->TypedefInfo_Delete(ti);
    return result;
}

size_t cppyy_stdvector_valuesize(const char* clname) {
    size_t result = 0;
    std::string name = clname;
    TypedefInfo_t* ti = gInterpreter->TypedefInfo_Factory((name+"::value_type").c_str());
    if (gInterpreter->TypedefInfo_IsValid(ti))
       result = (size_t)gInterpreter->TypedefInfo_Size(ti);
    gInterpreter->TypedefInfo_Delete(ti);
    return result;
}
   
} // end C-linkage wrappers
