local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")

local sfx = {}

sfx.FILTER_BANDPASS = C.PXL8_SFX_FILTER_BANDPASS
sfx.FILTER_HIGHPASS = C.PXL8_SFX_FILTER_HIGHPASS
sfx.FILTER_LOWPASS = C.PXL8_SFX_FILTER_LOWPASS
sfx.FILTER_NONE = C.PXL8_SFX_FILTER_NONE

sfx.LFO_AMPLITUDE = C.PXL8_SFX_LFO_AMPLITUDE
sfx.LFO_FILTER = C.PXL8_SFX_LFO_FILTER
sfx.LFO_PITCH = C.PXL8_SFX_LFO_PITCH

sfx.NODE_COMPRESSOR = C.PXL8_SFX_NODE_COMPRESSOR
sfx.NODE_DELAY = C.PXL8_SFX_NODE_DELAY
sfx.NODE_REVERB = C.PXL8_SFX_NODE_REVERB

sfx.WAVE_NOISE = C.PXL8_SFX_WAVE_NOISE
sfx.WAVE_PULSE = C.PXL8_SFX_WAVE_PULSE
sfx.WAVE_SAW = C.PXL8_SFX_WAVE_SAW
sfx.WAVE_SINE = C.PXL8_SFX_WAVE_SINE
sfx.WAVE_SQUARE = C.PXL8_SFX_WAVE_SQUARE
sfx.WAVE_TRIANGLE = C.PXL8_SFX_WAVE_TRIANGLE

local SfxNode = {}
SfxNode.__index = SfxNode

function SfxNode:destroy()
    if self._ptr then
        C.pxl8_sfx_node_destroy(self._ptr)
        self._ptr = nil
    end
end

sfx.SfxNode = SfxNode

local SfxContext = {}
SfxContext.__index = SfxContext

function SfxContext.new()
    local ctx = C.pxl8_sfx_context_create()
    if ctx == nil then
        return nil
    end
    return setmetatable({ _ptr = ctx }, SfxContext)
end

function SfxContext:append_node(node)
    C.pxl8_sfx_context_append_node(self._ptr, node._ptr)
end

function SfxContext:attach()
    C.pxl8_sfx_mixer_attach(core.sfx, self._ptr)
end

function SfxContext:destroy()
    if self._ptr then
        C.pxl8_sfx_context_destroy(self._ptr)
        self._ptr = nil
    end
end

function SfxContext:detach()
    C.pxl8_sfx_mixer_detach(core.sfx, self._ptr)
end

function SfxContext:get_head()
    local ptr = C.pxl8_sfx_context_get_head(self._ptr)
    if ptr == nil then return nil end
    return setmetatable({ _ptr = ptr }, SfxNode)
end

function SfxContext:get_volume()
    return C.pxl8_sfx_context_get_volume(self._ptr)
end

function SfxContext:insert_node(after, node)
    C.pxl8_sfx_context_insert_node(self._ptr, after._ptr, node._ptr)
end

function SfxContext:play_note(note, params, volume, duration)
    return C.pxl8_sfx_play_note(self._ptr, note, params, volume or 0.8, duration or 0)
end

function SfxContext:release_voice(voice_id)
    C.pxl8_sfx_release_voice(self._ptr, voice_id)
end

function SfxContext:remove_node(node)
    C.pxl8_sfx_context_remove_node(self._ptr, node._ptr)
end

function SfxContext:set_volume(volume)
    C.pxl8_sfx_context_set_volume(self._ptr, volume)
end

function SfxContext:stop_all()
    C.pxl8_sfx_stop_all(self._ptr)
end

function SfxContext:stop_voice(voice_id)
    C.pxl8_sfx_stop_voice(self._ptr, voice_id)
end

sfx.SfxContext = SfxContext

local Compressor = setmetatable({}, { __index = SfxNode })
Compressor.__index = Compressor

function Compressor.new(opts)
    opts = opts or {}
    local cfg = ffi.new("pxl8_sfx_compressor_config")
    cfg.attack = opts.attack or 10
    cfg.ratio = opts.ratio or 4
    cfg.release = opts.release or 100
    cfg.threshold = opts.threshold or -12
    local ptr = C.pxl8_sfx_compressor_create(cfg)
    if ptr == nil then return nil end
    return setmetatable({ _ptr = ptr }, Compressor)
end

function Compressor:set_attack(attack)
    C.pxl8_sfx_compressor_set_attack(self._ptr, attack)
end

function Compressor:set_ratio(ratio)
    C.pxl8_sfx_compressor_set_ratio(self._ptr, ratio)
end

function Compressor:set_release(release)
    C.pxl8_sfx_compressor_set_release(self._ptr, release)
end

function Compressor:set_threshold(threshold)
    C.pxl8_sfx_compressor_set_threshold(self._ptr, threshold)
end

sfx.Compressor = Compressor

local Delay = setmetatable({}, { __index = SfxNode })
Delay.__index = Delay

function Delay.new(opts)
    opts = opts or {}
    local cfg = ffi.new("pxl8_sfx_delay_config")
    cfg.feedback = opts.feedback or 0.4
    cfg.mix = opts.mix or 0.25
    cfg.time_l = opts.time_l or 350
    cfg.time_r = opts.time_r or 500
    local ptr = C.pxl8_sfx_delay_create(cfg)
    if ptr == nil then return nil end
    return setmetatable({ _ptr = ptr }, Delay)
end

function Delay:set_feedback(feedback)
    C.pxl8_sfx_delay_set_feedback(self._ptr, feedback)
end

function Delay:set_mix(mix)
    C.pxl8_sfx_delay_set_mix(self._ptr, mix)
end

function Delay:set_time(time_l, time_r)
    C.pxl8_sfx_delay_set_time(self._ptr, time_l, time_r)
end

sfx.Delay = Delay

local Reverb = setmetatable({}, { __index = SfxNode })
Reverb.__index = Reverb

function Reverb.new(opts)
    opts = opts or {}
    local cfg = ffi.new("pxl8_sfx_reverb_config")
    cfg.damping = opts.damping or 0.5
    cfg.mix = opts.mix or 0.3
    cfg.room = opts.room or 0.5
    local ptr = C.pxl8_sfx_reverb_create(cfg)
    if ptr == nil then return nil end
    return setmetatable({ _ptr = ptr }, Reverb)
end

function Reverb:set_damping(damping)
    C.pxl8_sfx_reverb_set_damping(self._ptr, damping)
end

function Reverb:set_mix(mix)
    C.pxl8_sfx_reverb_set_mix(self._ptr, mix)
end

function Reverb:set_room(room)
    C.pxl8_sfx_reverb_set_room(self._ptr, room)
end

sfx.Reverb = Reverb

function sfx.get_master_volume()
    return C.pxl8_sfx_mixer_get_master_volume(core.sfx)
end

sfx.note_to_freq = C.pxl8_sfx_note_to_freq

function sfx.set_master_volume(volume)
    C.pxl8_sfx_mixer_set_master_volume(core.sfx, volume)
end

function sfx.voice_params(opts)
    opts = opts or {}
    local params = ffi.new("pxl8_sfx_voice_params")

    params.amp_env.attack = opts.attack or 0.01
    params.amp_env.decay = opts.decay or 0.1
    params.amp_env.sustain = opts.sustain or 0.5
    params.amp_env.release = opts.release or 0.2

    params.filter_env.attack = opts.filter_attack or 0.01
    params.filter_env.decay = opts.filter_decay or 0.1
    params.filter_env.sustain = opts.filter_sustain or 0.3
    params.filter_env.release = opts.filter_release or 0.1

    params.filter_type = opts.filter_type or C.PXL8_SFX_FILTER_NONE
    params.lfo_target = opts.lfo_target or C.PXL8_SFX_LFO_PITCH
    params.lfo_waveform = opts.lfo_waveform or C.PXL8_SFX_WAVE_SINE
    params.waveform = opts.waveform or C.PXL8_SFX_WAVE_SINE

    params.filter_cutoff = opts.filter_cutoff or 4000
    params.filter_env_depth = opts.filter_env_depth or 0
    params.filter_resonance = opts.filter_resonance or 0
    params.fx_send = opts.fx_send or 0
    params.lfo_depth = opts.lfo_depth or 0
    params.lfo_rate = opts.lfo_rate or 0
    params.pulse_width = opts.pulse_width or 0.5

    return params
end

return sfx
