#pragma once

enum class MemoryOrder
{
    Relaxed = __ATOMIC_RELAXED,
    Consume = __ATOMIC_CONSUME,
    Acquire = __ATOMIC_ACQUIRE,
    Release = __ATOMIC_RELEASE,
    AcqRel = __ATOMIC_ACQ_REL,
    SeqCst = __ATOMIC_SEQ_CST,
};

template <typename T> class Atomic
{
  public:
    Atomic() : m_value()
    {
    }

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

    T operator=(T other)
    {
        store(other);
        return other;
    }

    T load(MemoryOrder order = MemoryOrder::SeqCst) const
    {
        return __atomic_load_n(&m_value, (int)order);
    }

    operator T() const
    {
        return load();
    }

    void store(T value, MemoryOrder order = MemoryOrder::SeqCst)
    {
        return __atomic_store_n(&m_value, value, (int)order);
    }

    T exchange(T value, MemoryOrder order = MemoryOrder::SeqCst)
    {
        return __atomic_exchange_n(&m_value, value, (int)order);
    }

    bool compare_exchange_strong(T& expected, T desired, MemoryOrder success, MemoryOrder failure)
    {
        return __atomic_compare_exchange_n(&m_value, &expected, desired, false, (int)success, (int)failure);
    }

    bool compare_exchange_strong(T& expected, T desired, MemoryOrder order = MemoryOrder::SeqCst)
    {
        MemoryOrder failure = (order == MemoryOrder::AcqRel)    ? MemoryOrder::Acquire
                              : (order == MemoryOrder::Release) ? MemoryOrder::Relaxed
                                                                : order;
        return __atomic_compare_exchange_n(&m_value, &expected, desired, false, (int)order, (int)failure);
    }

    bool compare_exchange_weak(T& expected, T desired, MemoryOrder success, MemoryOrder failure)
    {
        return __atomic_compare_exchange_n(&m_value, &expected, desired, true, (int)success, (int)failure);
    }

    bool compare_exchange_weak(T& expected, T desired, MemoryOrder order = MemoryOrder::SeqCst)
    {
        MemoryOrder failure = (order == MemoryOrder::AcqRel)    ? MemoryOrder::Acquire
                              : (order == MemoryOrder::Release) ? MemoryOrder::Relaxed
                                                                : order;
        return __atomic_compare_exchange_n(&m_value, &expected, desired, true, (int)order, (int)failure);
    }

    T fetch_add(T other, MemoryOrder order = MemoryOrder::SeqCst)
    {
        return __atomic_fetch_add(&m_value, other, (int)order);
    }

    T fetch_sub(T other, MemoryOrder order = MemoryOrder::SeqCst)
    {
        return __atomic_fetch_sub(&m_value, other, (int)order);
    }

    T operator++()
    {
        return fetch_add(1) + 1;
    }

    T operator++(int)
    {
        return fetch_add(1);
    }

    T operator--()
    {
        return fetch_sub(1) - 1;
    }

    T operator--(int)
    {
        return fetch_sub(1);
    }

    T operator+=(const T& other)
    {
        return fetch_add(other) + other;
    }

    T operator-=(const T& other)
    {
        return fetch_sub(other) - other;
    }

  private:
    T m_value;
};