#include <memory>
#include "Test.hpp"

class exception : std::exception {
    using base_class = std::exception;
    using base_class::base_class;
};

template<typename T, typename Allocator>
class SmartPointer {
 public:
    ENABLE_CLASS_TESTS;
    using value_type = T;

    explicit SmartPointer(value_type *p = nullptr) {
        if (p == nullptr) { core = nullptr; }
        else {
            core = &Core(p);
            core->AddRef();
        }
    }

    SmartPointer(const SmartPointer &optr)
    : core(optr.core) { core->AddRef(); }

    SmartPointer(SmartPointer &&) = default;

    SmartPointer &operator=(const SmartPointer &) = default;

    SmartPointer &operator=(SmartPointer &&sp) {
        if (this != &sp) {
            if (core->Release() == 0) {
                delete core;
            }

            core = sp.core;
            core->AddRef();
        }
        return *this;
    }

    SmartPointer &operator=(value_type *other) {
        core->p = other;
        core->AddRef();
        return *this;
    }

    ~SmartPointer() {
        if (core->Release() == 0) delete core;
    }

    value_type &operator*() {
        if (core->p == nullptr) throw exception();
        return *core->p;
    }

    const value_type &operator*() const {
        if (core->p == nullptr) throw exception();
        auto const f = *core->p;
        return f;
    }

    value_type *operator->() const { return core->p; }

    value_type *get() const { return core->p; }

    operator bool() const { return core->p != nullptr; }

    template<typename U, typename AnotherAllocator>
    bool operator == (const SmartPointer<U, AnotherAllocator> &pp) const {
        return static_cast<void *>(core->p)
        == static_cast<void *>(pp.core->p);
    }

    template<typename U, typename AnotherAllocator>
    bool operator != (const SmartPointer<U, AnotherAllocator> &other) const {
        return static_cast<void *>(other.core->p)
        != static_cast<void *>(core->p);
    }

    std::size_t count_owners() const {
        if (core->p == nullptr) return 0;
        return core->c;
    }

 public:
    class Core {
     public:
        value_type *p;
        size_t c;

        explicit Core(value_type *_p = nullptr) : p(_p), c(0) {}

        void AddRef() { ++c; }

        int Release() { return --c; }

        ~Core() {
            if (p != nullptr) delete p;
        }
    };

    Core *core;
};