#pragma once

namespace BAN
{

	enum MemoryOrder
	{
		memory_order_relaxed = __ATOMIC_RELAXED,
		memory_order_consume = __ATOMIC_CONSUME,
		memory_order_acquire = __ATOMIC_ACQUIRE,
		memory_order_release = __ATOMIC_RELEASE,
		memory_order_acq_rel = __ATOMIC_ACQ_REL,
		memory_order_seq_cst = __ATOMIC_SEQ_CST,
	};

	template<typename T, MemoryOrder MEM_ORDER = MemoryOrder::memory_order_seq_cst>
	requires requires { __atomic_always_lock_free(sizeof(T), 0); }
	class Atomic
	{
		Atomic(const Atomic&) = delete;
		Atomic(Atomic&&) = delete;
		Atomic& operator=(const Atomic&) volatile = delete;
		Atomic& operator=(Atomic&&) volatile = delete;

	public:
		constexpr Atomic() : m_value(0) {}
		constexpr Atomic(T val) : m_value(val) {}

		inline T load(MemoryOrder mem_order = MEM_ORDER) const volatile			{ return __atomic_load_n(&m_value, mem_order); }
		inline void store(T val, MemoryOrder mem_order = MEM_ORDER) volatile	{ __atomic_store_n(&m_value, val, mem_order); }

		inline T operator=(T val) volatile { store(val); return val; }

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

		inline T operator+=(T val) volatile { return __atomic_add_fetch(&m_value, val, MEM_ORDER); }
		inline T operator-=(T val) volatile { return __atomic_sub_fetch(&m_value, val, MEM_ORDER); }
		inline T operator&=(T val) volatile { return __atomic_and_fetch(&m_value, val, MEM_ORDER); }
		inline T operator^=(T val) volatile { return __atomic_xor_fetch(&m_value, val, MEM_ORDER); }
		inline T operator|=(T val) volatile { return __atomic_or_fetch(&m_value, val, MEM_ORDER); }

		inline T operator--() volatile { return __atomic_sub_fetch(&m_value, 1, MEM_ORDER); }
		inline T operator++() volatile { return __atomic_add_fetch(&m_value, 1, MEM_ORDER); }

		inline T operator--(int) volatile { return __atomic_fetch_sub(&m_value, 1, MEM_ORDER); }
		inline T operator++(int) volatile { return __atomic_fetch_add(&m_value, 1, MEM_ORDER); }

		inline bool compare_exchange(T& expected, T desired, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_compare_exchange_n(&m_value, &expected, desired, false, mem_order, mem_order); }
		inline T exchange(T desired, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_exchange_n(&m_value, desired, mem_order); };

		inline T add_fetch (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_add_fetch (&m_value, val, mem_order); }
		inline T sub_fetch (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_sub_fetch (&m_value, val, mem_order); }
		inline T and_fetch (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_and_fetch (&m_value, val, mem_order); }
		inline T xor_fetch (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_xor_fetch (&m_value, val, mem_order); }
		inline T or_fetch  (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_or_fetch  (&m_value, val, mem_order); }
		inline T nand_fetch(T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_nand_fetch(&m_value, val, mem_order); }

		inline T fetch_add (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_fetch_add (&m_value, val, mem_order); }
		inline T fetch_sub (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_fetch_sub (&m_value, val, mem_order); }
		inline T fetch_and (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_fetch_and (&m_value, val, mem_order); }
		inline T fetch_xor (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_fetch_xor (&m_value, val, mem_order); }
		inline T fetch_or  (T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_fetch__or  (&m_value, val, mem_order); }
		inline T fetch_nand(T val, MemoryOrder mem_order = MEM_ORDER) volatile { return __atomic_nfetch_and(&m_value, val, mem_order); }

	private:
		T m_value;
	};

}
