#pragma once

#include <memory>
#include "Test.hpp"

namespace smart_pointer {
// `exception` class definition
  class exception : std::exception {
    using base_class = std::exception;
    using base_class::base_class;
  };

// `SmartPointer` class declaration
  template<typename T, typename Allocator>
  class SmartPointer {
    // don't remove this macro
    ENABLE_CLASS_TESTS;
   public:
    using value_type = T;

    SmartPointer(value_type *new_core = nullptr)
      : Core(new_core) {}

    // copy constructor
    SmartPointer(const SmartPointer &other_core)
      : Core(other_core) {}

    // move constructor
    SmartPointer(SmartPointer &&other_core)
      : Core(other_core) {}

    // copy assigment
    SmartPointer &operator=(const SmartPointer &other_core) {
      core = other_core;
      return *core;
    }

    // move assigment
    SmartPointer &operator=(SmartPointer &&other_core) {
      core = other_core;
      return *core;
    }

    //
    SmartPointer &operator=(value_type *other_core) {
      core = other_core;
      return *core;
    }

    ~SmartPointer();

    // return reference to the object of class/type T
    // if SmartPointer contains nullptr throw `SmartPointer::exception`
    value_type &operator*() {
      if (core == nullptr) {
        throw;
      }
      return *core;
    }

    const value_type &operator*() const {
      if (core == nullptr) {
        throw;
      }
      return *core;
    }

    // return pointer to the object of class/type T
    value_type *operator->() const {
      return core;
    }

    value_type *get() const {
      return *core;
    }

    // if pointer == nullptr => return false
    operator bool() const {
      return core == nullptr;
    }

    // if pointers points to the same address or both null => true
    template<typename U, typename AnotherAllocator>
    bool operator==(const SmartPointer<U, AnotherAllocator> &rhs) const {
      return (core == rhs) || ((core == nullptr) && (rhs == nullptr));
    }

    // if pointers points to the same address or both null => false
    template<typename U, typename AnotherAllocator>
    bool operator!=(const SmartPointer<U, AnotherAllocator> &rhs) const {
      return (core != rhs) || ((core != nullptr) && (rhs != nullptr));
    }

    // if smart pointer contains non-nullptr => return count owners
    // if smart pointer contains nullptr => return 0
    std::size_t count_owners() const {
      return core->count_owners();
    }

   private:
    class Core {
     public:
      Core(value_type *new_value) {
        if (new_value == nullptr) {
          count_owners_ = 0;
        } else {
          value = *new_value;
          ++count_owners_;
        }
      }

      size_t count_owners() const {
        return count_owners_;
      }
      
     private:
      size_t count_owners_;
      value_type value;
    };
    Core *core;
  };
}