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

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

    template<typename T, typename Allocator>
    class SmartPointer {
        ENABLE_CLASS_TESTS;

     public:
        using value_type = T;

        explicit SmartPointer(value_type *value = nullptr) {
            if (value)
                core = new Core(value);
            else
                core = nullptr;
        }

        SmartPointer(const SmartPointer &A) {
            core = A.core;
            if (*this)
                core->Increment();
        }

        SmartPointer(SmartPointer &&A) {
            core = A.core;
            A.core = nullptr;
        }

        SmartPointer &operator=(const SmartPointer &A) {
            this->~SmartPointer();
            core = A.core;
            if (*this)
                core->Increment();
            return *this;
        }

        SmartPointer &operator=(SmartPointer &&A) {
            this->~SmartPointer();
            core = A.core;
            A.core = nullptr;
            return *this;
        }

        SmartPointer &operator=(value_type *A) {
            this->~SmartPointer();
            if (A)
                core = new Core(A);
            else
                core = nullptr;
            return *this;
        }

        ~SmartPointer() {
            if (*this) {
                core->Decrement();
                if (core->CountOwners() == 0)
                    delete core;
            }
        }

        value_type &operator*() {
            if (!*this)
                throw smart_pointer::exception();
            return *(core->Value());
        }

        const value_type &operator*() const {
            if (!*this)
                throw smart_pointer::exception();
            return *(core->Value());
        }

        value_type *operator->() const {
            if (*this)
                return core->Value();
            return nullptr;
        }

        value_type *get() const {
            if (*this)
                return core->Value();
            return nullptr;
        }

        explicit operator bool() const {
            return !(core == nullptr || core->Value() == nullptr);
        }

        template<typename U, typename AnotherAllocator>
        bool operator==(const SmartPointer<U, AnotherAllocator> &A) const {
            if (!*this && !A)
                return true;
            if (!std::is_same<T, U>::value)
                return false;
            return reinterpret_cast<void *>(get()) ==
                   reinterpret_cast<void *>(A.get());
        }

        template<typename U, typename AnotherAllocator>
        bool operator!=(const SmartPointer<U, AnotherAllocator> &A) const {
            if (!*this && !A)
                return false;
            if (!std::is_same<T, U>::value)
                return true;
            return reinterpret_cast<void *>(get()) !=
                   reinterpret_cast<void *>(A.get());
        }

        [[nodiscard]] std::size_t count_owners() const {
            if (*this)
                return core->CountOwners();
            return 0;
        }

     private:
        class Core {
         public:
            explicit Core(value_type *value) : value_(value), count_owners_(0) {
                Increment();
            }

            ~Core() {
                delete value_;
                value_ = nullptr;
            }

            value_type *Value() {
                return value_;
            }

            value_type *Value() const {
                return value_;
            }

            void Increment() {
                count_owners_++;
            }

            void Decrement() {
                count_owners_--;
            }

            [[nodiscard]] std::size_t CountOwners() const {
                return count_owners_;
            }

         private:
            value_type *value_;
            std::size_t count_owners_;
        };

        Core *core;
    };
}  // namespace smart_pointer