#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;

SmartPointer(value_type* x = nullptr) {
if (x == nullptr) {
this->core = nullptr;
} else {
this->core = new Core(x, 1);
}
}

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

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

SmartPointer& operator=(const SmartPointer& x) {
if (this->core != nullptr) {
this->core->ref_count--;
if (this->core->ref_count == 0) {
delete (this->core);
}
}
this->core = x.core;
if (x.core != nullptr) {
this->core->ref_count++;
}
return *this;
}
SmartPointer& operator=(SmartPointer&& x) {
if (this->core != nullptr) {
this->core->ref_count--;
if (this->core->ref_count == 0) {
delete (this->core);
}
}
this->core = x.core;
x.core = nullptr;
return *this;
}
SmartPointer& operator=(value_type* x) {
if (this->core != nullptr) {
this->core->ref_count--;
if (this->core->ref_count == 0) {
delete (this->core);
}
}
if (x != nullptr) {
this->core = new Core(x, 1);
} else {
this->core = nullptr;
}
return *this;
}
~SmartPointer() {
if (this->core != nullptr) {
this->core->ref_count--;
if (this->core->ref_count == 0) {
delete (this->core);
}
}
}
value_type& operator*() {
if (this->core == nullptr) {
throw smart_pointer::exception();
} else {
return *(this->core->ptr);
}
}
const value_type& operator*() const {
if (this->core == nullptr) {
throw smart_pointer::exception();
} else {
return *(this->core->ptr);
}
}
value_type* operator->() const {
if (this->core == nullptr) {
return nullptr;
} else {
return core->ptr;
}
}
value_type* get() const {
if (this->core == nullptr) {
return nullptr;
} else {
return this->core->ptr;
}
}
operator bool() const { return this->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 (this->core != nullptr) {
return this->core->ref_count;
} else {
return 0;
}
}

 private:
class Core {
 public:
size_t ref_count;
value_type* ptr;

Core() {
}

Core(value_type* ptr, size_t ref_count) : ptr(ptr), ref_count(ref_count) {
}

~Core() {
Allocator allocator;
allocator.deallocate(ptr, sizeof(value_type));
this->ptr = nullptr;
}
};
Core* core;
};
}  // namespace smart_pointer