#ifndef CRANY_HPP_
#define CRANY_HPP_


#include <memory>
#include <type_traits>
#include <cassert>


template<template<typename> class = std::is_void>
class Crany;

/// CranySelf
template<>
class Crany<>
{
private:
    template<template<typename> class> friend class Crany;
    template<typename T>
    struct CranySelf
    {
    protected:
        auto       & self()       { return *((T       *)this)->self_; }
        auto const & self() const { return *((T const *)this)->self_; }
        virtual ~CranySelf() = default;
    };
};

/// Crany
template<template<typename> class Concept>
class Crany final : public Concept<Crany<>::CranySelf<Crany<Concept>>>
{
public:

    /// Special member functions
    template<typename Self>
    Crany(Self self) : self_{new Model<Self>(std::move(self))} {}
    Crany(Crany const & other) : self_(other.self_->clone()) {}
    Crany & operator=(Crany const & other) { return *this = Crany(other); }
    Crany(Crany && other) = default;
    Crany & operator=(Crany && other) = default;

private:

    /// CranySelf
    friend Crany<>::CranySelf<Crany<Concept>>;

    /// ConceptSelf
    struct ConceptSelf
    {
    protected:
        auto       & self()       { assert(!"Crany Concept non-virtual member function called"); return *(Concept<ConceptSelf>       *)this; }
        auto const & self() const { assert(!"Crany Concept non-virtual member function called"); return *(Concept<ConceptSelf> const *)this; }
        virtual ~ConceptSelf() = default;
    };

    /// CloneConcept
    struct CloneConcept : Concept<ConceptSelf>
    {
        virtual CloneConcept * clone() const = 0;
    };

    /// ModelSelf
    template<typename T>
    struct ModelSelf : CloneConcept
    {
    protected:
        auto       & self()       { return ((T       *)this)->self_; }
        auto const & self() const { return ((T const *)this)->self_; }
    };

    /// Model
    template<typename Self>
    struct Model final : Concept<ModelSelf<Model<Self>>>
    {
        Model(Self self) : self_{std::move(self)} {}
        CloneConcept * clone() const { return new Model<Self>(*this); }
        Self self_;
    };

    /// Self
    std::unique_ptr<CloneConcept> self_;

    /// crany_cast
    template<typename T, typename Crany> friend T const * crany_cast(Crany const *  crany) noexcept;
    template<typename T, typename Crany> friend T       * crany_cast(Crany       *  crany) noexcept;
    template<typename T, typename Crany> friend T         crany_cast(Crany const &  crany);
    template<typename T, typename Crany> friend T         crany_cast(Crany       &  crany);
    template<typename T, typename Crany> friend T         crany_cast(Crany       && crany);
};

/// bad_crany_cast
class bad_crany_cast : public std::bad_cast
{
};

/// crany_cast
template<typename T, typename Crany>
T const * crany_cast(Crany const * crany) noexcept
{
    if (!crany)
        return nullptr;
    using Model = typename Crany::Model<T>;
    if (auto * model = dynamic_cast<Model *>(crany->self_.get()))
        return &model->self_;
    return nullptr;
}
template<typename T, typename Crany>
T * crany_cast(Crany * crany) noexcept
{
    return const_cast<T *>(*const_cast<T const *>(crany));
}
template<typename T, typename Crany>
T crany_cast(Crany const & crany)
{
    using U = std::remove_cv_t<std::remove_reference_t<T>>;
    if (auto p = crany_cast<U>(&crany))
        return static_cast<T>(*p);
    throw bad_crany_cast(); // TODO
}
template<typename T, typename Crany>
T crany_cast(Crany & crany)
{
    using U = std::remove_cv_t<std::remove_reference_t<T>>;
    if (auto p = crany_cast<U>(&crany))
        return static_cast<T>(*p);
    throw bad_crany_cast(); // TODO
}
template<typename T, typename Crany>
T crany_cast(Crany && crany)
{
    using U = std::remove_cv_t<std::remove_reference_t<T>>;
    if (auto p = crany_cast<U>(&crany))
        return static_cast<T>(std::move(*p));
    throw bad_crany_cast(); // TODO
}


#endif // CRANY_HPP_