/**
 * @file audren.h
 * @brief Audio renderer service.
 * @author fincs
 * @copyright libnx Authors
 */
#pragma once

#include "../audio/audio.h"

#if __cplusplus >= 201402L
#define AUDREN_CONSTEXPR constexpr
#else
#define AUDREN_CONSTEXPR static inline
#endif

#define AUDREN_TIMER_FREQ_HZ           200.0f
#define AUDREN_TIMER_PERIOD_MS         5.0f
#define AUDREN_SAMPLES_PER_FRAME_32KHZ 160
#define AUDREN_SAMPLES_PER_FRAME_48KHZ 240

#define AUDREN_INPUT_PARAM_ALIGNMENT  0x1000
#define AUDREN_OUTPUT_PARAM_ALIGNMENT 0x10
#define AUDREN_MEMPOOL_ALIGNMENT      0x1000
#define AUDREN_BUFFER_ALIGNMENT       0x40

#define AUDREN_REVISION_1 0x31564552 // REV1 [1.0.0+]
#define AUDREN_REVISION_2 0x32564552 // REV2 [2.0.0+]
#define AUDREN_REVISION_3 0x33564552 // REV3 [3.0.0+]
#define AUDREN_REVISION_4 0x34564552 // REV4 [4.0.0+]
#define AUDREN_REVISION_5 0x35564552 // REV5 [6.0.0+]
#define AUDREN_REVISION_6 0x36564552 // REV6 [6.1.0+]

#define AUDREN_NODEID(_a,_b,_c)   ((((u32)(_a) & 0xF) << 28) | (((u32)(_b) & 0xFFF) << 16) | ((u32)(_c) & 0xFFFF))
#define AUDREN_FINAL_MIX_ID       0
#define AUDREN_UNUSED_MIX_ID      0x7FFFFFFF
#define AUDREN_UNUSED_SPLITTER_ID 0xFFFFFFFF

#define AUDREN_DEFAULT_DEVICE_NAME "MainAudioOut"

typedef enum {
    AudioRendererOutputRate_32kHz,
    AudioRendererOutputRate_48kHz,
} AudioRendererOutputRate;

typedef struct {
    AudioRendererOutputRate output_rate;
    int num_voices;
    int num_effects;
    int num_sinks;
    int num_mix_objs;
    int num_mix_buffers;
} AudioRendererConfig;

/*
Input buffer layout:

AudioRendererUpdateDataHeader
AudioRendererBehaviorInfoIn
AudioRendererMemPoolInfoIn * mempool_count
AudioRendererChannelInfoIn * channel_count
AudioRendererVoiceInfoIn * voice_count
(effects would go here)
(splitters would go here)
AudioRendererMixInfoIn * mix_count (i.e. submix_count+1)
AudioRendererSinkInfoIn * sink_count
AudioRendererPerformanceBufferInfoIn
*/

/*
Output buffer layout:

AudioRendererUpdateDataHeader
AudioRendererMemPoolInfoOut * mempool_count
AudioRendererVoiceInfoOut * voice_count
(effects would go here)
AudioRendererSinkInfoOut * sink_count
AudioRendererPerformanceBufferInfoOut
AudioRendererBehaviorInfoOut
*/

typedef struct {
    u32 revision;
    u32 behavior_sz;
    u32 mempools_sz;
    u32 voices_sz;
    u32 channels_sz;
    u32 effects_sz;
    u32 mixes_sz;
    u32 sinks_sz;
    u32 perfmgr_sz;
    u32 _padding[6];
    u32 total_sz;
} AudioRendererUpdateDataHeader;

typedef struct {
    u32 revision;
    u32 _padding1;
    u64 flags;
} AudioRendererBehaviorInfoIn;

typedef struct {
    u64 unknown[20];
    u64 _padding1[2];
} AudioRendererBehaviorInfoOut;

typedef enum {
    AudioRendererMemPoolState_Invalid,
    AudioRendererMemPoolState_New,
    AudioRendererMemPoolState_RequestDetach,
    AudioRendererMemPoolState_Detached,
    AudioRendererMemPoolState_RequestAttach,
    AudioRendererMemPoolState_Attached,
    AudioRendererMemPoolState_Released,
} AudioRendererMemPoolState;

typedef struct {
    const void* address;
    u64 size;
    AudioRendererMemPoolState state;
    u32 _padding2[3];
} AudioRendererMemPoolInfoIn;

typedef struct
{
    AudioRendererMemPoolState new_state;
    u32 _padding2[3];
} AudioRendererMemPoolInfoOut;

typedef struct {
    u32 id;
    float mix[24];
    bool is_used;
    u8 _padding1[11];
} AudioRendererChannelInfoIn;

typedef struct {
    bool enable;
    u8 _padding;
    s16 numerator[3];
    s16 denominator[2];
} AudioRendererBiquadFilter;

typedef struct {
    u16 coefficients[16];
} AudioRendererAdpcmParameters;

typedef struct {
    u16 index;
    s16 history0;
    s16 history1;
} AudioRendererAdpcmContext;

typedef struct {
    const void* address;
    u64 size;
    s32 start_sample_offset;
    s32 end_sample_offset;
    bool is_looping;
    bool end_of_stream;
    bool sent_to_server;
    u8 _padding1[5];
    const void* context_addr;
    u64 context_sz;
    u64 _padding2;
} AudioRendererWaveBuf;

typedef enum {
    AudioRendererVoicePlayState_Started,
    AudioRendererVoicePlayState_Stopped,
    AudioRendererVoicePlayState_Paused,
} AudioRendererVoicePlayState;

typedef struct {
    u32 id;
    u32 node_id;
    bool is_new;
    bool is_used;
    AudioRendererVoicePlayState state : 8;
    PcmFormat sample_format : 8;
    u32 sample_rate;
    u32 priority;
    u32 sorting_order;
    u32 channel_count;
    float pitch;
    float volume;
    AudioRendererBiquadFilter biquads[2];
    u32 wavebuf_count;
    s16 wavebuf_head;
    u16 _padding1;
    u32 _padding2;
    const void* extra_params_ptr;
    u64 extra_params_sz;
    u32 dest_mix_id;
    u32 dest_splitter_id;
    AudioRendererWaveBuf wavebufs[4];
    u32 channel_ids[6];
    u8 _padding3[24];
} AudioRendererVoiceInfoIn;

typedef struct {
    u64 played_sample_count;
    u32 num_wavebufs_consumed;
    u32 voice_drops_count;
} AudioRendererVoiceInfoOut;

typedef struct {
    float volume;
    u32 sample_rate;
    u32 buffer_count;
    bool is_used;
    u8 _padding1[3];
    u32 mix_id;
    u32 _padding2;
    u32 node_id;
    u32 _padding3[2];
    float mix[24][24]; // [src_index][dest_index]
    u32 dest_mix_id;
    u32 dest_splitter_id;
    u32 _padding4;
} AudioRendererMixInfoIn;

typedef struct {
    u8 coefficients[16];
} AudioRendererDownMixParameters;

typedef enum {
    AudioRendererSinkType_Invalid,
    AudioRendererSinkType_Device,
    AudioRendererSinkType_CircularBuffer,
} AudioRendererSinkType;

typedef struct {
    char name[255];
    u8 _padding1;
    u32 input_count;
    u8 inputs[6];
    u8 _padding2;
    bool downmix_params_enabled;
    AudioRendererDownMixParameters downmix_params;
} AudioRendererDeviceSinkInfoIn;

typedef struct {
    void* buffer_ptr;
    u32 buffer_sz;
    u32 input_count;
    u32 sample_count;
    u32 last_read_offset;
    PcmFormat sample_format;
    u8 inputs[6];
    u8 _padding2[6];
} AudioRendererCircularBufferSinkInfoIn;

typedef struct {
    AudioRendererSinkType type : 8;
    bool is_used;
    u8 _padding1[2];
    u32 node_id;
    u64 _padding2[3];
    union {
        AudioRendererDeviceSinkInfoIn device_sink;
        AudioRendererCircularBufferSinkInfoIn circular_buffer_sink;
    };
} AudioRendererSinkInfoIn;

typedef struct {
    u32 last_written_offset;
    u32 unk1;
    u64 unk2;
    u64 _padding1[2];
} AudioRendererSinkInfoOut;

typedef struct {
    u32 detail_target;
    u32 _padding1[3];
} AudioRendererPerformanceBufferInfoIn;

typedef struct {
    u32 written_sz;
    u32 _padding1[3];
} AudioRendererPerformanceBufferInfoOut;

static inline u32 audrenGetRevision(void)
{
    extern u32 g_audrenRevision;
    return g_audrenRevision;
}

AUDREN_CONSTEXPR int audrenGetMemPoolCount(const AudioRendererConfig* config)
{
    return config->num_effects + 4 * config->num_voices;
}

AUDREN_CONSTEXPR size_t audrenGetInputParamSize(const AudioRendererConfig* config)
{
    size_t size = 0;
    size += sizeof(AudioRendererUpdateDataHeader);
    size += sizeof(AudioRendererBehaviorInfoIn);
    size += sizeof(AudioRendererMemPoolInfoIn) * audrenGetMemPoolCount(config);
    size += sizeof(AudioRendererChannelInfoIn) * config->num_voices;
    size += sizeof(AudioRendererVoiceInfoIn) * config->num_voices;
    // todo: effects, splitters
    size += sizeof(AudioRendererMixInfoIn) * config->num_mix_objs;
    size += sizeof(AudioRendererSinkInfoIn) * config->num_sinks;
    size += sizeof(AudioRendererPerformanceBufferInfoIn);
    return size;
}

AUDREN_CONSTEXPR size_t audrenGetOutputParamSize(const AudioRendererConfig* config)
{
    size_t size = 0;
    size += sizeof(AudioRendererUpdateDataHeader);
    size += sizeof(AudioRendererMemPoolInfoOut) * audrenGetMemPoolCount(config);
    size += sizeof(AudioRendererVoiceInfoOut) * config->num_voices;
    // todo: effects
    size += sizeof(AudioRendererSinkInfoOut) * config->num_sinks;
    size += sizeof(AudioRendererPerformanceBufferInfoOut);
    size += sizeof(AudioRendererBehaviorInfoOut);
    return size;
}

Result audrenInitialize(const AudioRendererConfig* config);
void audrenExit(void);
void audrenWaitFrame(void);
Result audrenGetState(u32* out_state);
Result audrenRequestUpdateAudioRenderer(const void* in_param_buf, size_t in_param_buf_size, void* out_param_buf, size_t out_param_buf_size, void* perf_buf, size_t perf_buf_size);
Result audrenStartAudioRenderer(void);
Result audrenStopAudioRenderer(void);
Result audrenSetAudioRendererRenderingTimeLimit(int percent);
