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

#ifndef SMARTPOINTER_HPP
#define SMARTPOINTER_HPP

using std::size_t;

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());
     }

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

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

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