#pragma once
 
#include <memory>
#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 *val = nullptr):
      core(val == nullptr ? nullptr :new Core(val, 1)) {}


    SmartPointer(const SmartPointer &val) {
      core = val.core;
      if (core != nullptr)
        core->ptr_count++;
    }


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


    SmartPointer &operator=(const SmartPointer &val) {
      if (core != nullptr) {
        core->ptr_count--;
        if (core->ptr_count == 0) {
          delete core->ptr;
          delete core;
        }
      }
      core = val.core;
      if (core != nullptr)
        core->ptr_count++;

      return *this;
    }


    SmartPointer &operator=(SmartPointer &&val) {
      if (core != nullptr) {
        core->ptr_count--;
        if (core->ptr_count == 0) {
          delete core->ptr;
          delete core;
        }
      }
      core = val.core;
      val.core = nullptr;

      return *this;
    }


    SmartPointer &operator=(value_type *val) {
      if (core != nullptr) {
        core->ptr_count--;
        if (core->ptr_count == 0) {
          delete core->ptr;
          delete core;
        }
      }
      if (val == nullptr) {
        core = nullptr;
        return *this;
      }
      core = new Core(val, 1);

      return *this;
    }


    ~SmartPointer() {
      if (core != nullptr) {
        core->ptr_count--;
        if (core->ptr_count == 0) {
          delete core->ptr;
          delete core;
        }
      }
    }


    value_type &operator*() {
      if (core == nullptr || core->ptr == nullptr)
        throw smart_pointer::exception();

      return *(core->ptr);
    }


    const value_type &operator*() const {
      if (core == nullptr || core->ptr == nullptr)
        throw smart_pointer::exception();

      return *(core->ptr);
    }


    value_type *operator->() const {
      if (core != nullptr)
        return core->ptr;

      return nullptr;
    }


    value_type *get() const {
      if (core != nullptr)
        return core->ptr;

      return nullptr;
    }


    operator bool() const {
      if (core == nullptr)
        return false;

      return core->ptr != nullptr;
    }


    template<typename U, typename AnotherAllocator>
    bool operator==(const SmartPointer<U, AnotherAllocator> &val) const {
      auto exp1 = get() == nullptr && val.get() == nullptr;
      auto exp2 = static_cast<void*>(get()) == static_cast<void*>(val.get())

      return exp1 || exp2;
    }


    template<typename U, typename AnotherAllocator>
    bool operator!=(const SmartPointer<U, AnotherAllocator> &val) const {
      auto exp1 = get() == nullptr && val.get() == nullptr;
      auto exp2 = static_cast<void*>(get()) == static_cast<void*>(val.get())

      return !(exp1 || exp2);
    }


    std::size_t count_owners() const {
      if (core != nullptr)
        return core->ptr_count;

      return 0;
    }

   private:
    class Core {
     public:
      Core(value_type *val, size_t count) : ptr(val), ptr_count(count) {}
      value_type *ptr;
      size_t ptr_count;
    };
    Core *core;
  };
}  // namespace smart_pointer