#ifndef ALCONTEXT_H
#define ALCONTEXT_H

#include <mutex>
#include <atomic>
#include <memory>
#include <thread>

#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "inprogext.h"

#include "atomic.h"
#include "vector.h"
#include "threads.h"
#include "almalloc.h"
#include "alnumeric.h"

#include "alListener.h"


struct ALsource;
struct ALeffectslot;
struct ALcontextProps;
struct ALlistenerProps;
struct ALvoiceProps;
struct ALeffectslotProps;
struct ALvoice;
struct RingBuffer;

enum class DistanceModel {
    InverseClamped  = AL_INVERSE_DISTANCE_CLAMPED,
    LinearClamped   = AL_LINEAR_DISTANCE_CLAMPED,
    ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED,
    Inverse  = AL_INVERSE_DISTANCE,
    Linear   = AL_LINEAR_DISTANCE,
    Exponent = AL_EXPONENT_DISTANCE,
    Disable  = AL_NONE,

    Default = InverseClamped
};

struct SourceSubList {
    uint64_t FreeMask{~0_u64};
    ALsource *Sources{nullptr}; /* 64 */

    SourceSubList() noexcept = default;
    SourceSubList(const SourceSubList&) = delete;
    SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
    { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
    ~SourceSubList();

    SourceSubList& operator=(const SourceSubList&) = delete;
    SourceSubList& operator=(SourceSubList&& rhs) noexcept
    { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
};

struct EffectSlotSubList {
    uint64_t FreeMask{~0_u64};
    ALeffectslot *EffectSlots{nullptr}; /* 64 */

    EffectSlotSubList() noexcept = default;
    EffectSlotSubList(const EffectSlotSubList&) = delete;
    EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
      : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
    { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
    ~EffectSlotSubList();

    EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
    EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
    { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
};

struct ALCcontext {
    RefCount ref{1u};

    al::vector<SourceSubList> SourceList;
    ALuint NumSources{0};
    std::mutex SourceLock;

    al::vector<EffectSlotSubList> EffectSlotList;
    ALuint NumEffectSlots{0u};
    std::mutex EffectSlotLock;

    std::atomic<ALenum> LastError{AL_NO_ERROR};

    DistanceModel mDistanceModel{DistanceModel::Default};
    ALboolean SourceDistanceModel{AL_FALSE};

    ALfloat DopplerFactor{1.0f};
    ALfloat DopplerVelocity{1.0f};
    ALfloat SpeedOfSound{};
    ALfloat MetersPerUnit{1.0f};

    std::atomic_flag PropsClean;
    std::atomic<bool> DeferUpdates{false};

    std::mutex PropLock;

    /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
     * indicates if updates are currently happening).
     */
    RefCount UpdateCount{0u};
    std::atomic<bool> HoldUpdates{false};

    ALfloat GainBoost{1.0f};

    std::atomic<ALcontextProps*> Update{nullptr};

    /* Linked lists of unused property containers, free to use for future
     * updates.
     */
    std::atomic<ALcontextProps*> FreeContextProps{nullptr};
    std::atomic<ALlistenerProps*> FreeListenerProps{nullptr};
    std::atomic<ALvoiceProps*> FreeVoiceProps{nullptr};
    std::atomic<ALeffectslotProps*> FreeEffectslotProps{nullptr};

    ALvoice **Voices{nullptr};
    std::atomic<ALsizei> VoiceCount{0};
    ALsizei MaxVoices{0};

    using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
    std::atomic<ALeffectslotArray*> ActiveAuxSlots{nullptr};

    std::thread EventThread;
    al::semaphore EventSem;
    std::unique_ptr<RingBuffer> AsyncEvents;
    std::atomic<ALbitfieldSOFT> EnabledEvts{0u};
    std::mutex EventCbLock;
    ALEVENTPROCSOFT EventCb{};
    void *EventParam{nullptr};

    /* Default effect slot */
    std::unique_ptr<ALeffectslot> DefaultSlot;

    ALCdevice *const Device;
    const ALCchar *ExtensionList{nullptr};

    std::atomic<ALCcontext*> next{nullptr};

    ALlistener Listener{};


    ALCcontext(ALCdevice *device);
    ALCcontext(const ALCcontext&) = delete;
    ALCcontext& operator=(const ALCcontext&) = delete;
    ~ALCcontext();

    static constexpr inline const char *CurrentPrefix() noexcept { return "ALCcontext::"; }
    DEF_NEWDEL(ALCcontext)
};

void ALCcontext_DecRef(ALCcontext *context);

void UpdateContextProps(ALCcontext *context);

void ALCcontext_DeferUpdates(ALCcontext *context);
void ALCcontext_ProcessUpdates(ALCcontext *context);


/* Simple RAII context reference. Takes the reference of the provided
 * ALCcontext, and decrements it when leaving scope. Movable (transfer
 * reference) but not copyable (no new references).
 */
class ContextRef {
    ALCcontext *mCtx{nullptr};

    void reset() noexcept
    {
        if(mCtx)
            ALCcontext_DecRef(mCtx);
        mCtx = nullptr;
    }

public:
    ContextRef() noexcept = default;
    ContextRef(ContextRef&& rhs) noexcept : mCtx{rhs.mCtx}
    { rhs.mCtx = nullptr; }
    explicit ContextRef(ALCcontext *ctx) noexcept : mCtx(ctx) { }
    ~ContextRef() { reset(); }

    ContextRef& operator=(const ContextRef&) = delete;
    ContextRef& operator=(ContextRef&& rhs) noexcept
    { std::swap(mCtx, rhs.mCtx); return *this; }

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

    ALCcontext* operator->() noexcept { return mCtx; }
    ALCcontext* get() noexcept { return mCtx; }

    ALCcontext* release() noexcept
    {
        ALCcontext *ret{mCtx};
        mCtx = nullptr;
        return ret;
    }
};

ContextRef GetContextRef(void);


struct ALcontextProps {
    ALfloat DopplerFactor;
    ALfloat DopplerVelocity;
    ALfloat SpeedOfSound;
    ALboolean SourceDistanceModel;
    DistanceModel mDistanceModel;
    ALfloat MetersPerUnit;

    std::atomic<ALcontextProps*> next;
};

#endif /* ALCONTEXT_H */
