#pragma once

#include<string>
#include<iostream>
#include<fstream>
#include<vector>
#include<iomanip>
#include<sstream>
#include<cmath>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <functional>
#include <set>
#include <queue>
#include <memory>
#include <map>
#include <cassert>
#include <utility>

#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* xPtr = nullptr) :
    core((xPtr == nullptr ? nullptr : new Core(xPtr))) {
}

SmartPointer(const SmartPointer& other) :
    core(other.core != nullptr ? other.core : nullptr) {
    if (core != nullptr) {
    this->core->c++;
    }
}

SmartPointer(SmartPointer&& other) :
    core(std::exchange(other.core, nullptr)) { }

SmartPointer& operator=(const SmartPointer& other) {
    this->~SmartPointer();
    this->core = other.core;
    if (this->core != nullptr) {
        this->core->c++;
    }
        return *this;
    }

SmartPointer& operator=(SmartPointer&& other) {
    this->~SmartPointer();
    this->core = std::move(other.core);
    other.core = nullptr;
    return *this;
    }

SmartPointer& operator=(value_type* p) {
    this->~SmartPointer();
    if (p == nullptr) {
        this->core = nullptr;
    } else {
        this->core = new Core(p);
    }
    return *this;
}

~SmartPointer() {
    if (this->core != nullptr) {
        if (this->core->c <= 1) {
            delete this->core;
        } else {
            this->core->c--;
        }
    }
}

value_type& operator*() {
    if (this->core == nullptr || this->core->val == nullptr) {
        throw smart_pointer::exception();
    } else {
        return *(this->core->val);
    }
}

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

value_type* operator->() const {
    return this->get();
}

value_type* get() const {
    if (this->core == nullptr) {
        return nullptr;
    } else {
        return this->core->val;
    }
}

operator bool() const {
    if (this->core == nullptr || this->core->val == nullptr) {
        return false;
    } else {
        return true;
    }
}

decltype(auto) getCore() const {
    return static_cast<void*>(this->core);
}

template<typename U, typename AnotherAllocator>
bool operator==(const SmartPointer<U, AnotherAllocator>& other)
const {
    return this->getCore() == other.getCore();
}

template<typename U, typename AnotherAllocator>
bool operator!=(const SmartPointer<U, AnotherAllocator>& other)
const {
    return !(SmartPointer::operator==(other));
}

std::size_t count_owners() const {
    if (this->core == nullptr) {
        return 0;
    } else {
        return this->core->c;
    }
}

 private:
    class Core {
     public:
        explicit Core(value_type* x) : val(x), c(1) {}
        ~Core() {
            if (val != nullptr) delete val;
        }
        value_type* val;
        std::size_t c;
    };
    Core* core;
};
};  // namespace smart_pointer