#pragma once

#include <atomic>
#include <cstddef>
#include <utility>

namespace kuiper
{

/// Atomically reference counted type
template<typename T>
class arc {
  public:
    /// Argument-forwarding constructor
    template<typename... Args>
    explicit arc(Args&&... args)
        : m_inner(std::forward<Args>(args)...) {}

    /// Default constructor (no args.)
    arc()
        : m_inner() {}

    /// Destructor
    ~arc() {
        if (m_ref_count.load() > 0)
            return;
    }

    /// Copy constructor
    arc(const arc& other)
        : m_inner(other.m_inner), m_ref_count(other.m_ref_count.load() + 1) {}

    /// Copy assignment constructor
    arc& operator=(const arc& other) {
        m_inner     = other.m_inner;
        m_ref_count = other.m_ref_count.load() + 1;
        return *this;
    }

    /// Move constructor
    arc(arc&& other) noexcept
        : m_inner(std::move(other.m_inner)), m_ref_count(other.m_ref_count.load()) {};

    /// Move assignment constructor
    arc& operator=(arc&& other) noexcept {
        *this = std::move(other);
        return *this;
    };

    // Inner type access

    /// Reference to the type contained in the `arc`
    T& inner() noexcept {
        return m_inner;
    }

    /// Read-only reference to the type contained in the `arc`
    const T& const_inner() const noexcept {
        return m_inner;
    }

    // Reference count

    std::size_t ref_count() const noexcept {
        return m_ref_count.load();
    }

  private:
    T m_inner;

    std::atomic_size_t m_ref_count = 0;
};

} // namespace kuiper
