#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* value = nullptr) {
if (value == nullptr) {
this->core = nullptr;
} else {
this->core = new Core();
this->core->pointer = value;
this->core->count = 1;
}
}
SmartPointer(const SmartPointer & obj) {
this->core = obj.core;
if (obj.core != nullptr) {
this->core->count++;
}
}
SmartPointer(SmartPointer && obj) {
this->core = obj.core;
obj.core = nullptr;
}
SmartPointer &operator=(const SmartPointer & obj) {
if (this->core != nullptr) {
this->core->count--;
if(this->core->count == 0) {
delete this->core;
}
}
this->core = obj.core;
if (obj.core != nullptr) {
this->core->count++;
}
return *this;
}
SmartPointer &operator=(SmartPointer && obj) {
if (this->core != nullptr) {
this->core->count--;
if(this->core->count == 0) {
delete this->core;
}
}    
this->core = obj.core;
obj.core = nullptr;
return *this;
}
SmartPointer &operator=(value_type *obj) {
if (this->core != nullptr) {
this->core->count--;
if(this->core->count == 0) {
delete this->core;
}
}
if (obj != nullptr) {
this->core = new Core();
this->core->pointer = obj;
this->core->count = 1;
} else {
this->core = nullptr;
}
return *this;
}
}
~SmartPointer() {
if (this->core != nullptr) {
this->core->count--;
if(this->core->count == 0) {
delete this->core;
}
}
}
value_type &operator*() {
if(this->core == nullptr) {
throw smart_pointer::exception();
} else {
return *(this->core->pointer);
}
}
const value_type &operator*() const {
if(this->core == nullptr) {
throw smart_pointer::exception();
} else {
return *(this->core->pointer);
}
}
value_type *operator->() const {
if (this->core == nullptr) {
return nullptr;
} else {
return this->core->pointer;
}
}
value_type *get() const {
if (this->core == nullptr) {
return nullptr;
} else {
return this->core->pointer;
}
}
operator bool() const {
if(this->core->pointer == nullptr) {
return false;
} else {
return true;
}
}
template<typename U, typename AnotherAllocator>
bool operator==(const SmartPointer<U, AnotherAllocator> & ptr) const {
return static_cast<void*>(this->get()) == static_cast<void*>(ptr.get());
}
template<typename U, typename AnotherAllocator>
bool operator!=(const SmartPointer<U, AnotherAllocator> & ptr) const {
return static_cast<void*>(this->get()) == static_cast<void*>(ptr.get());
}
std::size_t count_owners() const {
if (this->core != nullptr) {
return this->core->count;
} else {
return 0;
}
}

 private:
class Core {
 public:
size_t count;
value_type* pointer;
~Core() {
Allocator allocator;
allocator.deallocate(pointer, sizeof(value_type));
this->pointer = nullptr;
}
};
Core *core;
};
}  // namespace smart_pointer