#include "config.h"

#include <cmath>
#include <cstdlib>

#include <algorithm>

#include "AL/efx.h"

#include "alc/effects/base.h"
#include "effects.h"

#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX


namespace {

void Autowah_setParamf(EffectProps *props, ALenum param, float val)
{
    switch(param)
    {
    case AL_AUTOWAH_ATTACK_TIME:
        if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
            throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"};
        props->Autowah.AttackTime = val;
        break;

    case AL_AUTOWAH_RELEASE_TIME:
        if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
            throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"};
        props->Autowah.ReleaseTime = val;
        break;

    case AL_AUTOWAH_RESONANCE:
        if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
            throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"};
        props->Autowah.Resonance = val;
        break;

    case AL_AUTOWAH_PEAK_GAIN:
        if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
            throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"};
        props->Autowah.PeakGain = val;
        break;

    default:
        throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
    }
}
void Autowah_setParamfv(EffectProps *props,  ALenum param, const float *vals)
{ Autowah_setParamf(props, param, vals[0]); }

void Autowah_setParami(EffectProps*, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
void Autowah_setParamiv(EffectProps*, ALenum param, const int*)
{
    throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
        param};
}

void Autowah_getParamf(const EffectProps *props, ALenum param, float *val)
{
    switch(param)
    {
    case AL_AUTOWAH_ATTACK_TIME:
        *val = props->Autowah.AttackTime;
        break;

    case AL_AUTOWAH_RELEASE_TIME:
        *val = props->Autowah.ReleaseTime;
        break;

    case AL_AUTOWAH_RESONANCE:
        *val = props->Autowah.Resonance;
        break;

    case AL_AUTOWAH_PEAK_GAIN:
        *val = props->Autowah.PeakGain;
        break;

    default:
        throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
    }

}
void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Autowah_getParamf(props, param, vals); }

void Autowah_getParami(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
void Autowah_getParamiv(const EffectProps*, ALenum param, int*)
{
    throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
        param};
}

EffectProps genDefaultProps() noexcept
{
    EffectProps props{};
    props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
    props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
    props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
    props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
    return props;
}

} // namespace

DEFINE_ALEFFECT_VTABLE(Autowah);

const EffectProps AutowahEffectProps{genDefaultProps()};

#ifdef ALSOFT_EAX
namespace {

class EaxAutoWahEffectException : public EaxException {
public:
    explicit EaxAutoWahEffectException(const char* message)
        : EaxException{"EAX_AUTO_WAH_EFFECT", message}
    {}
}; // EaxAutoWahEffectException

class EaxAutoWahEffect final : public EaxEffect4<EaxAutoWahEffectException, EAXAUTOWAHPROPERTIES> {
public:
    EaxAutoWahEffect(int eax_version);

private:
    struct AttackTimeValidator {
        void operator()(float flAttackTime) const
        {
            eax_validate_range<Exception>(
                "Attack Time",
                flAttackTime,
                EAXAUTOWAH_MINATTACKTIME,
                EAXAUTOWAH_MAXATTACKTIME);
        }
    }; // AttackTimeValidator

    struct ReleaseTimeValidator {
        void operator()(float flReleaseTime) const
        {
            eax_validate_range<Exception>(
                "Release Time",
                flReleaseTime,
                EAXAUTOWAH_MINRELEASETIME,
                EAXAUTOWAH_MAXRELEASETIME);
        }
    }; // ReleaseTimeValidator

    struct ResonanceValidator {
        void operator()(long lResonance) const
        {
            eax_validate_range<Exception>(
                "Resonance",
                lResonance,
                EAXAUTOWAH_MINRESONANCE,
                EAXAUTOWAH_MAXRESONANCE);
        }
    }; // ResonanceValidator

    struct PeakLevelValidator {
        void operator()(long lPeakLevel) const
        {
            eax_validate_range<Exception>(
                "Peak Level",
                lPeakLevel,
                EAXAUTOWAH_MINPEAKLEVEL,
                EAXAUTOWAH_MAXPEAKLEVEL);
        }
    }; // PeakLevelValidator

    struct AllValidator {
        void operator()(const Props& all) const
        {
            AttackTimeValidator{}(all.flAttackTime);
            ReleaseTimeValidator{}(all.flReleaseTime);
            ResonanceValidator{}(all.lResonance);
            PeakLevelValidator{}(all.lPeakLevel);
        }
    }; // AllValidator

    void set_defaults(Props& props) override;

    void set_efx_attack_time() noexcept;
    void set_efx_release_time() noexcept;
    void set_efx_resonance() noexcept;
    void set_efx_peak_gain() noexcept;
    void set_efx_defaults() override;

    void get(const EaxCall& call, const Props& props) override;
    void set(const EaxCall& call, Props& props) override;
    bool commit_props(const Props& props) override;
}; // EaxAutoWahEffect

EaxAutoWahEffect::EaxAutoWahEffect(int eax_version)
    : EaxEffect4{AL_EFFECT_AUTOWAH, eax_version}
{}

void EaxAutoWahEffect::set_defaults(Props& props)
{
    props.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
    props.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
    props.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
    props.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
}

void EaxAutoWahEffect::set_efx_attack_time() noexcept
{
    al_effect_props_.Autowah.AttackTime = clamp(
        props_.flAttackTime,
        AL_AUTOWAH_MIN_ATTACK_TIME,
        AL_AUTOWAH_MAX_ATTACK_TIME);
}

void EaxAutoWahEffect::set_efx_release_time() noexcept
{
    al_effect_props_.Autowah.ReleaseTime = clamp(
        props_.flReleaseTime,
        AL_AUTOWAH_MIN_RELEASE_TIME,
        AL_AUTOWAH_MAX_RELEASE_TIME);
}

void EaxAutoWahEffect::set_efx_resonance() noexcept
{
    al_effect_props_.Autowah.Resonance = clamp(
        level_mb_to_gain(static_cast<float>(props_.lResonance)),
        AL_AUTOWAH_MIN_RESONANCE,
        AL_AUTOWAH_MAX_RESONANCE);
}

void EaxAutoWahEffect::set_efx_peak_gain() noexcept
{
    al_effect_props_.Autowah.PeakGain = clamp(
        level_mb_to_gain(static_cast<float>(props_.lPeakLevel)),
        AL_AUTOWAH_MIN_PEAK_GAIN,
        AL_AUTOWAH_MAX_PEAK_GAIN);
}

void EaxAutoWahEffect::set_efx_defaults()
{
    set_efx_attack_time();
    set_efx_release_time();
    set_efx_resonance();
    set_efx_peak_gain();
}

void EaxAutoWahEffect::get(const EaxCall& call, const Props& props)
{
    switch (call.get_property_id())
    {
        case EAXAUTOWAH_NONE: break;
        case EAXAUTOWAH_ALLPARAMETERS: call.set_value<Exception>(props); break;
        case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.flAttackTime); break;
        case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.flReleaseTime); break;
        case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.lResonance); break;
        case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(props.lPeakLevel); break;
        default: fail_unknown_property_id();
    }
}

void EaxAutoWahEffect::set(const EaxCall& call, Props& props)
{
    switch (call.get_property_id())
    {
        case EAXAUTOWAH_NONE: break;
        case EAXAUTOWAH_ALLPARAMETERS: defer<AllValidator>(call, props); break;
        case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.flAttackTime); break;
        case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.flReleaseTime); break;
        case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.lResonance); break;
        case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.lPeakLevel); break;
        default: fail_unknown_property_id();
    }
}

bool EaxAutoWahEffect::commit_props(const Props& props)
{
    auto is_dirty = false;

    if (props_.flAttackTime != props.flAttackTime)
    {
        is_dirty = true;
        set_efx_attack_time();
    }

    if (props_.flReleaseTime != props.flReleaseTime)
    {
        is_dirty = true;
        set_efx_release_time();
    }

    if (props_.lResonance != props.lResonance)
    {
        is_dirty = true;
        set_efx_resonance();
    }

    if (props_.lPeakLevel != props.lPeakLevel)
    {
        is_dirty = true;
        set_efx_peak_gain();
    }

    return is_dirty;
}

} // namespace

EaxEffectUPtr eax_create_eax_auto_wah_effect(int eax_version)
{
    return eax_create_eax4_effect<EaxAutoWahEffect>(eax_version);
}

#endif // ALSOFT_EAX