#pragma once
#include <luna/Atomic.h>
#include <luna/Option.h>

class Spinlock
{
  public:
    void lock();
    void unlock();

    bool try_lock();

    bool is_locked() const
    {
        return m_lock.load() != 0;
    }

  private:
    Atomic<int> m_lock { 0 };
};

class ScopeLock
{
  public:
    ScopeLock(Spinlock& lock);
    ~ScopeLock();

    ScopeLock(const ScopeLock&) = delete;
    ScopeLock(ScopeLock&&) = delete;

    Spinlock& take_over()
    {
        m_taken_over = true;
        return m_lock;
    }

  private:
    Spinlock& m_lock;
    bool m_taken_over { false };
};

class SafeScopeLock
{
  public:
    SafeScopeLock(Spinlock& lock);
    ~SafeScopeLock();

    SafeScopeLock(const SafeScopeLock&) = delete;
    SafeScopeLock(SafeScopeLock&&) = delete;

    bool did_succeed() const
    {
        return m_success;
    }

  private:
    Spinlock& m_lock;
    bool m_success { false };
};

template <typename T> class LockedValue
{
    struct LockedValueGuard
    {
        LockedValueGuard(LockedValue& value_ref) : m_value_ref(&value_ref)
        {
        }

        LockedValueGuard(const LockedValueGuard& other) = delete;
        LockedValueGuard(LockedValueGuard&& other)
        {
            m_value_ref = other.m_value_ref;
            other.m_value_ref = nullptr;
        }

        ~LockedValueGuard()
        {
            if (m_value_ref) m_value_ref->m_lock.unlock();
        }

        T& ref()
        {
            expect(m_value_ref, "LockedValueGuard::ref() called on a moved LockedValueGuard");
            return m_value_ref->m_value;
        }

        void set(const T& other)
        {
            ref() = other;
        }

        T* operator->()
        {
            return &ref();
        }

        T& operator*()
        {
            return ref();
        }

      private:
        LockedValue* m_value_ref;
    };

  public:
    LockedValue() : m_value()
    {
    }

    LockedValue(T value) : m_value(value)
    {
    }

    LockedValueGuard lock()
    {
        m_lock.lock();
        return { *this };
    }

    Option<LockedValueGuard> try_lock()
    {
        if (m_lock.try_lock()) { return { *this }; }
        return {};
    }

  private:
    T m_value;
    Spinlock m_lock;
};