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


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


SmartPointer(SmartPointer &&val) {
  core = val.core;
  val.core = nullptr;
}


SmartPointer &operator=(const SmartPointer &val) {
  if (core != nullptr) {
    core->ptr_count--;
    if (core->ptr_count == 0) {
      delete core->ptr;
      delete core;
    }
  }
  core = val.core;
  if (core != nullptr)
    core->ptr_count++;

  return *this;
}


SmartPointer &operator=(SmartPointer &&val) {
  if (core != nullptr) {
    core->ptr_count--;
    if (core->ptr_count == 0) {
      delete core->ptr;
      delete core;
    }
  }
  core = val.core;
  val.core = nullptr;

  return *this;
}


SmartPointer &operator=(value_type *val) {
  if (core != nullptr) {
    core->ptr_count--;
    if (core->ptr_count == 0) {
      delete core->ptr;
      delete core;
    }
  }
  if (val == nullptr) {
    core = nullptr;
    return *this;
  }
  core = new Core(val, 1);

  return *this;
}


~SmartPointer() {
  if (core != nullptr) {
    core->ptr_count--;
    if (core->ptr_count == 0) {
      delete core->ptr;
      delete core;
    }
  }
}


value_type &operator*() {
  if (core == nullptr || core->ptr == nullptr)
    throw smart_pointer::exception();

  return *(core->ptr);
}


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

  return *(core->ptr);
}


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

  return nullptr;
}


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

  return nullptr;
}


operator bool() const {
  if (core == nullptr)
    return false;

  return core->ptr != nullptr;
}


template<typename U, typename AnotherAllocator>
bool operator==(const SmartPointer<U, AnotherAllocator> &val) const {
  auto exp1 = get() == nullptr && val.get() == nullptr;
  auto exp2 = static_cast<void*>(get()) == static_cast<void*>(val.get())

  return exp1 || exp2;
}


template<typename U, typename AnotherAllocator>
bool operator!=(const SmartPointer<U, AnotherAllocator> &val) const {
  auto exp1 = get() == nullptr && val.get() == nullptr;
  auto exp2 = static_cast<void*>(get()) == static_cast<void*>(val.get())

  return !(exp1 || exp2);
}


std::size_t count_owners() const {
  if (core != nullptr)
    return core->ptr_count;

  return 0;
}

 private:
  class Core {
   public:
    Core(value_type *val, size_t count) : ptr(val), ptr_count(count) {}
    value_type *ptr;
    size_t ptr_count;
  };
  Core *core;
};
}  // namespace smart_pointer