//
// Created by Lenovo on 16.11.2021.
//



#ifndef SMARTPOINTER_HPP
#define SMARTPOINTER_HPP
#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 *x = nullptr) {
            if (x == nullptr) {
                this->core = nullptr;
            } else {
                this->core = new Core();
                core->info = x;
                core->cnt = 1;
            }
        }

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

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

        SmartPointer &operator=(const SmartPointer &x) {
            remove();
            this->core = x.core;
            if (x.core != nullptr) {
                core->cnt++;
            }
            return *this;
        }

        SmartPointer &operator=(SmartPointer &&x) {
            remove();
            this->core = x.core;
            x.core = nullptr;
            return *this;
        }

        SmartPointer &operator=(value_type *x) {
            remove();
            if (x != nullptr) {
                this->core = new Core();
                core->info = x;
                core->cnt = 1;
            } else {
                this->core = nullptr;
            }
            return *this;
        }

        void remove() {
            if (core != nullptr) {
                core->cnt -= 1;
                if (core->cnt == 0) {
                    delete (core);
                }
            }
        }

        ~SmartPointer() { remove(); }

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

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

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

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

        operator bool() const { return 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 (core != nullptr) {
                return core->cnt;
            } else {
                return 0;
            }
        }

    private:
        class Core {
        public:
            size_t cnt;
            value_type *info;

            ~Core() {
                Allocator allocator;
                allocator.deallocate(info, sizeof(value_type));
                info = nullptr;
            }
        };

        Core *core;
    };
}

#endif