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

#ifndef SMARTPOINTER_HPP
#define SMARTPOINTER_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* x = nullptr) {
                if (x == nullptr) {
                    this->core = nullptr;
                }
                else {
                    this->core = new Core();
                    core->info = x;
                    core->cnt = 1;
                }
            }

            SmartPointer(const SmartPointer& x) {
                this->core = x.core;
                if (x.core != nullptr) {
                    core->cnt++;
                }
            }

            SmartPointer(SmartPointer&& x) {
                this->core = x.core;
                x.core = nullptr;
            }

            void remove() {
                if (core != nullptr) {
                    core->cnt -= 1;
                    if (core->cnt == 0) {
                        delete (core);
                    }
                }
            }

            SmartPointer& operator=(const SmartPointer& x) {
                remove();
                this->core = x.core;
                if (x.core != nullptr) {
                    core->cnt++;
                }
                return *this;
            }

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

            SmartPointer& operator=(value_type* x) {
                remove();
                if (x != nullptr) {
                    this->core = new Core();
                    core->info = x;
                    core->cnt = 1;
                }
                else {
                    this->core = nullptr;
                }
                return *this;
            }

            ~SmartPointer() {
                remove();
            }

            value_type& operator*() {
                if (core == nullptr) {
                    throw smart_pointer::exception();
                }
                else {
                    return *(core->info);
                }
            }

            const value_type& operator*() const {
                if (core == nullptr) {
                    throw smart_pointer::exception();
                }
                else {
                    return *(core->info);
                }
            }

            value_type* operator->() const {
                if (core == nullptr) {
                    return nullptr;
                }
                else {
                    return core->info;
                }
            }

            value_type* get() const {
                if (core == nullptr) {
                    return nullptr;
                }
                else {
                    return core->info;
                }
            }

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

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

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

            std::size_t count_owners() const {
                if (core != nullptr) {
                    return core->cnt;
                }
                else {
                    return 0;
                }
            }

         private:
            class Core {
             public:
                std::size_t cnt;
                value_type* info;

                ~Core() {
                    Allocator allocator;
                    allocator.deallocate(info, sizeof(value_type));
                    info = nullptr;
                }
            };
            Core* core;
    };
} 
#endif