#pragma once
#include <luna/Alloc.h>
#include <luna/Result.h>

template <typename T> class SharedPtr;

template <typename T> class OwnedPtr
{
  public:
    OwnedPtr()
    {
        m_ptr = nullptr;
    }

    OwnedPtr(T* ptr)
    {
        m_ptr = ptr;
    }

    ~OwnedPtr()
    {
        if (m_ptr) delete m_ptr;
    }

    OwnedPtr(const OwnedPtr<T>& other) = delete;

    OwnedPtr(OwnedPtr<T>&& other)
    {
        m_ptr = other.m_ptr;
        other.m_ptr = nullptr;
    }

    OwnedPtr<T>& operator=(const OwnedPtr<T>& other) = delete;

    OwnedPtr<T>& operator=(OwnedPtr<T>&& other)
    {
        if (&other == this) return *this;

        if (m_ptr) delete m_ptr;

        m_ptr = other.m_ptr;
        other.m_ptr = nullptr;

        return *this;
    }

    T* ptr() const
    {
        return m_ptr;
    }

    T* operator->() const
    {
        return m_ptr;
    }

    T& operator*() const
    {
        return *m_ptr;
    }

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

    template <typename Type> friend Result<SharedPtr<Type>> adopt_shared_from_owned(OwnedPtr<Type>&&);

  private:
    T* m_ptr;
};

template <typename T, class... Args> Result<OwnedPtr<T>> make_owned(Args... args)
{
    T* raw = TRY(make<T>(args...));
    return OwnedPtr<T> { raw };
}

template <typename T> OwnedPtr<T> adopt_owned(T* ptr)
{
    return OwnedPtr<T> { ptr };
}

template <typename T> Result<OwnedPtr<T>> adopt_owned_if_nonnull(T* ptr)
{
    if (ptr) return OwnedPtr<T> { ptr };
    else
        return err(ENOMEM);
}