#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) {
            core = new Core(value);
            core->countRefer++;
        } else {
            core = nullptr;
        }
    }
    SmartPointer(const SmartPointer& other) {
        core = other.core;
        if (core != nullptr) {
            core->countRefer++;
        }
    }
    SmartPointer(SmartPointer&& other) noexcept(false) {
        core = other.core;
        other.core = nullptr;
    }
    SmartPointer& operator=(const SmartPointer& other) {
        if (core != nullptr) {
            if (core->countRefer > 1) {
                core->countRefer--;
            } else {
                delete (core);
            }
        }
        core = other.core;
        if (core != nullptr) {
            core->countRefer++;
        }
        return *this;
    }
    SmartPointer& operator=(SmartPointer&& other) noexcept(false) {
        core = other.core;
        delete other.core->value;
        other.core = nullptr;
        return *this;
    }
    SmartPointer& operator=(value_type*other) {
        if (core != nullptr) {
            if (core->countRefer > 1) {
                core->countRefer--;
            } else {
                delete (core);
            }
        }
        if (other != nullptr) {
            core = new Core(other);
        } else {
            core = nullptr;
        }
        return *this;
    }
    ~SmartPointer() {
        if (core != nullptr) {
            core = nullptr;
        }
    }

    value_type& operator*() {
        if (core == nullptr) {
            throw smart_pointer::exception();
        }
        return *core->value;
    }
    const value_type& operator*() const {
        if (core == nullptr) {
            throw smart_pointer::exception();
        }
        return *core->value;
    }
    value_type* operator->() const {
        if (core == nullptr) {
            return nullptr;
        }
        return core->value;
    }
    value_type* get() const {
        if (core == nullptr) {
            return nullptr;
        }
        return core->value;
    }
    operator bool() const {
        return (core != nullptr);
    }

    template<typename U, typename AnotherAllocator>
    bool operator==(const SmartPointer<U, AnotherAllocator>& other) const {
        if (core != nullptr) {
            return (core->value == reinterpret_cast<value_type*>(other.get()));
        } else {
            return (other.core == nullptr);
        }
    }
    template<typename U, typename AnotherAllocator>
    bool operator!=(const SmartPointer<U, AnotherAllocator>&other) const {
        return !(*this == other);
    }
    std::size_t count_owners() const {
        if (core == nullptr) return 0;
        return core->countRefer;
    }

    class Core {
     public:
        explicit Core(value_type* value) : value(value) {}
        size_t countRefer = 0;
        value_type* value;
    };
    Core* core;
};
}  // namespace smart_pointer