#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 "Test.hpp"

using std::size_t;

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* x = nullptr) {
        if (x != nullptr) {
            core = new Core(x);
            core->c++;
        } else {
            core = nullptr;
        }
    }

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

    SmartPointer(SmartPointer&& oth) noexcept {
        core = oth.core;
        oth.core = nullptr;
    }

    SmartPointer &operator=(const SmartPointer& oth) {
         if (core != nullptr) {
             if (core->c > 1) {
                core->c--;
             } else {
                delete (core);
             }
         }
         core = oth.core;
         if (core != nullptr) {
             oth.core->c++;
         }
         return *this;
    }

    SmartPointer &operator=(SmartPointer&& oth) {
         core = oth.core;
         delete oth.core->p;
         oth.core = nullptr;
         return *this;
    }

    SmartPointer &operator=(value_type* oth) {
         if (core != nullptr) {
             if (core->c > 1) {
                 core->c--;
             } else {
                delete(core);
             }
         }
         if (oth != nullptr) {
             core = new Core(oth);
         } else {
             core = nullptr;
         }
         return *this;
    }

    ~SmartPointer() {
        if (core != nullptr) {
            core = nullptr;
        }
    }

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

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

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

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

    explicit operator bool() const {
        return (core != nullptr);
    }

    template <typename U, typename AnotherAllocator>
    bool operator==(const SmartPointer<U, AnotherAllocator>& oth) const {
        if (core != nullptr) {
            return (core->p == reinterpret_cast<value_type*>(oth.get()));
        } else {
            return (oth.get() == nullptr);
        }
    }

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

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

 private:
    class Core {
     public:
        explicit Core(value_type* x) : p(x) {}
        size_t c = 0;
        value_type* p;
    };
    Core* core;
};
}  // namespace smart_pointer