#pragma once
#include <map>
#include <typeinfo>
#include <stdint.h>
#include <stddef.h>
#include <type_traits>

namespace panda {

namespace {
    typedef std::map<intptr_t, ptrdiff_t> DynCastCacheMap;
    template <class DERIVED, class BASE> struct DynCastCache {
        static DynCastCacheMap map;
    };
    template <class DERIVED, class BASE> DynCastCacheMap DynCastCache<DERIVED,BASE>::map;

    const ptrdiff_t INCORRECT_PTRDIFF = sizeof(ptrdiff_t) == 4 ? 2147483647 : 9223372036854775807LL;
}

template <class DERIVED_PTR, class BASE>
DERIVED_PTR dyn_cast (BASE* obj) {
    typedef typename std::remove_pointer<DERIVED_PTR>::type DERIVED;
    if (!obj) return NULL;
    intptr_t key = (intptr_t)typeid(*obj).name();
    DynCastCacheMap::iterator it = DynCastCache<DERIVED,BASE>::map.find(key);
    if (it != DynCastCache<DERIVED,BASE>::map.end())
        return it->second != INCORRECT_PTRDIFF ? reinterpret_cast<DERIVED*>((char*)obj - it->second) : NULL;
    DERIVED* ret = dynamic_cast<DERIVED*>(obj);
    if (ret) DynCastCache<DERIVED,BASE>::map[key] = (char*)obj - (char*)ret;
    else DynCastCache<DERIVED,BASE>::map[key] = INCORRECT_PTRDIFF;
    return ret;
}

template <class DERIVED_REF, class BASE>
DERIVED_REF dyn_cast (BASE& obj) {
    typedef typename std::remove_reference<DERIVED_REF>::type DERIVED;
    intptr_t key = (intptr_t)typeid(obj).name();
    DynCastCacheMap::iterator it = DynCastCache<DERIVED,BASE>::map.find(key);
    if (it != DynCastCache<DERIVED,BASE>::map.end() && it->second != INCORRECT_PTRDIFF)
        return *(reinterpret_cast<DERIVED*>((char*)&obj - it->second));
    // dont cache fails, as exceptions are much slower than dynamic_cast, let it always fall here
    DERIVED& ret = dynamic_cast<DERIVED&>(obj);
    DynCastCache<DERIVED,BASE>::map[key] = (char*)&obj - (char*)&ret;
    return ret;
}

}
