#pragma once

#include <typeinfo>
#include <cassert>
#include <algorithm>

class type_safe_ptr
{
public:
    type_safe_ptr() : m_Ptr(nullptr), m_TypeInfoHashCode(0) { }
    type_safe_ptr(nullptr_t) : m_Ptr(nullptr), m_TypeInfoHashCode(0) { }
    template<typename T> explicit type_safe_ptr(T* ptr) : m_Ptr(ptr), m_TypeInfoHashCode(typeid(T).hash_code()) { }

    void* get() const { return m_Ptr; }

    explicit operator bool() const { return m_Ptr != nullptr; }

    template<typename T>
    T* get_typed() const
    {
        if(m_Ptr && typeid(T).hash_code() != m_TypeInfoHashCode)
        {
            assert(0);
            return nullptr;
        }
        return (T*)m_Ptr;
    }

    void reset() { m_Ptr = nullptr; m_TypeInfoHashCode = 0; }
    void reset(nullptr_t) { m_Ptr = nullptr; m_TypeInfoHashCode = 0; }
    template<typename T> void reset(T* ptr) { m_Ptr = ptr; m_TypeInfoHashCode = typeid(T).hash_code(); }

    type_safe_ptr& operator=(nullptr_t) { reset(); return *this; }
    template<typename T> type_safe_ptr& operator=(T *ptr) { reset<T>(ptr); return *this; }

    void swap(type_safe_ptr& rhs) { std::swap(m_Ptr, rhs.m_Ptr); std::swap(m_TypeInfoHashCode, rhs.m_TypeInfoHashCode); }

private:
    friend class type_safe_const_ptr;

    void* m_Ptr;
    size_t m_TypeInfoHashCode;
};

class type_safe_const_ptr
{
public:
    type_safe_const_ptr() : m_Ptr(nullptr), m_TypeInfoHashCode(0) { }
    type_safe_const_ptr(nullptr_t) : m_Ptr(nullptr), m_TypeInfoHashCode(0) { }
    type_safe_const_ptr(const type_safe_ptr& src) : m_Ptr(src.m_Ptr), m_TypeInfoHashCode(src.m_TypeInfoHashCode) { }
    template<typename T> explicit type_safe_const_ptr(const T* ptr) : m_Ptr(ptr), m_TypeInfoHashCode(typeid(T).hash_code()) { }

    const void* get() const { return m_Ptr; }

    explicit operator bool() const { return m_Ptr != nullptr; }

    template<typename T>
    const T* get_typed() const
    {
        if(m_Ptr && typeid(T).hash_code() != m_TypeInfoHashCode)
        {
            assert(0);
            return nullptr;
        }
        return (const T*)m_Ptr;
    }

    void reset() { m_Ptr = nullptr; m_TypeInfoHashCode = 0; }
    void reset(nullptr_t) { m_Ptr = nullptr; m_TypeInfoHashCode = 0; }
    template<typename T> void reset(const T* ptr) { m_Ptr = ptr; m_TypeInfoHashCode = typeid(T).hash_code(); }

    type_safe_const_ptr& operator=(nullptr_t) { reset(); return *this; }
    template<typename T> type_safe_const_ptr& operator=(const T *ptr) { reset<T>(ptr); return *this; }
    type_safe_const_ptr& operator=(const type_safe_ptr& src) { m_Ptr = src.m_Ptr; m_TypeInfoHashCode = src.m_TypeInfoHashCode; return *this; }

    void swap(type_safe_const_ptr& rhs) { std::swap(m_Ptr, rhs.m_Ptr); std::swap(m_TypeInfoHashCode, rhs.m_TypeInfoHashCode); }

private:
    const void* m_Ptr;
    size_t m_TypeInfoHashCode;
};
