#pragma once

#include <types/ptr/ref_counted.h>
#include <utility>

namespace extra2d {

template <class T>
class Ptr {
public:
    using element_type = T;

    Ptr() : p_(nullptr) {}
    Ptr(T* p) : p_(p) { if (p_) p_->addRef(); }
    Ptr(const Ptr<T>& r) : p_(r.p_) { if (p_) p_->addRef(); }
    template <typename U> Ptr(const Ptr<U>& r) : p_(r.get()) { if (p_) p_->addRef(); }
    Ptr(Ptr<T>&& r) noexcept : p_(r.release()) {}

    ~Ptr() { if (p_) p_->release(); }

    T* get() const { return p_; }
    T& operator*() const { return *p_; }
    T* operator->() const { return p_; }
    operator T*() const { return p_; }

    Ptr<T>& operator=(T* p) { reset(p); return *this; }
    Ptr<T>& operator=(const Ptr<T>& r) { return *this = r.p_; }
    Ptr<T>& operator=(Ptr<T>&& r) noexcept { Ptr<T>(std::move(r)).swap(*this); return *this; }

    bool operator==(std::nullptr_t) const { return p_ == nullptr; }
    bool operator!=(std::nullptr_t) const { return p_ != nullptr; }

    void reset() noexcept { if (p_) p_->release(); p_ = nullptr; }
    void reset(T* p) { if (p) p->addRef(); if (p_) p_->release(); p_ = p; }
    T* release() { T* ret = p_; p_ = nullptr; return ret; }
    void swap(Ptr<T>& r) noexcept { T* tmp = p_; p_ = r.p_; r.p_ = tmp; }

private:
    T* p_;
};

template <typename T, typename... Args>
inline Ptr<T> makePtr(Args&&... args) {
    return Ptr<T>(new T(std::forward<Args>(args)...));
}

} // namespace extra2d
