// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <cmath>
#include <span>
#include <vector>

#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/compressor.h"
#include "audio_core/renderer/effect/compressor.h"

namespace AudioCore::AudioRenderer {

static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params,
                                         CompressorInfo::State& state) {
    const auto ratio{1.0f / params.compressor_ratio};
    auto makeup_gain{0.0f};
    if (params.makeup_gain_enabled) {
        makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
    }
    state.makeup_gain = makeup_gain;
    state.unk_18 = params.unk_28;

    const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
    const auto b{(a - std::trunc(a)) * 0.69315f};
    const auto c{std::pow(2.0f, b)};

    state.unk_0C = (1.0f - ratio) / 6.0f;
    state.unk_14 = params.threshold + 1.5f;
    state.unk_10 = params.threshold - 1.5f;
    state.unk_20 = c;
}

static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params,
                                       CompressorInfo::State& state) {
    std::memset(&state, 0, sizeof(CompressorInfo::State));

    state.unk_00 = 0;
    state.unk_04 = 1.0f;
    state.unk_08 = 1.0f;

    SetCompressorEffectParameter(params, state);
}

static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
                                  CompressorInfo::State& state, bool enabled,
                                  std::vector<std::span<const s32>> input_buffers,
                                  std::vector<std::span<s32>> output_buffers, u32 sample_count) {
    if (enabled) {
        auto state_00{state.unk_00};
        auto state_04{state.unk_04};
        auto state_08{state.unk_08};
        auto state_18{state.unk_18};

        for (u32 i = 0; i < sample_count; i++) {
            auto a{0.0f};
            for (s16 channel = 0; channel < params.channel_count; channel++) {
                const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
                a += (input_sample * input_sample).to_float();
            }

            state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);

            auto b{-100.0f};
            auto c{0.0f};
            if (state_00 >= 1.0e-10) {
                b = std::log10(state_00) * 10.0f;
                c = 1.0f;
            }

            if (b >= state.unk_10) {
                const auto d{b >= state.unk_14
                                 ? ((1.0f / params.compressor_ratio) - 1.0f) *
                                       (b - params.threshold)
                                 : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
                const auto e{d / 20.0f * 3.3219f};
                const auto f{(e - std::trunc(e)) * 0.69315f};
                c = std::pow(2.0f, f);
            }

            state_18 = params.unk_28;
            auto tmp{c};
            if ((state_04 - c) <= 0.08f) {
                state_18 = params.unk_2C;
                if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
                    tmp = state_04;
                }
            }

            state_04 = tmp;
            state_08 += (c - state_08) * state_18;

            for (s16 channel = 0; channel < params.channel_count; channel++) {
                output_buffers[channel][i] = static_cast<s32>(
                    static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
            }
        }

        state.unk_00 = state_00;
        state.unk_04 = state_04;
        state.unk_08 = state_08;
        state.unk_18 = state_18;
    } else {
        for (s16 channel = 0; channel < params.channel_count; channel++) {
            if (params.inputs[channel] != params.outputs[channel]) {
                std::memcpy((char*)output_buffers[channel].data(),
                            (char*)input_buffers[channel].data(),
                            output_buffers[channel].size_bytes());
            }
        }
    }
}

void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
                             std::string& string) {
    string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
    for (s16 i = 0; i < parameter.channel_count; i++) {
        string += fmt::format("{:02X}, ", inputs[i]);
    }
    string += "\n\toutputs: ";
    for (s16 i = 0; i < parameter.channel_count; i++) {
        string += fmt::format("{:02X}, ", outputs[i]);
    }
    string += "\n";
}

void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
    std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
    std::vector<std::span<s32>> output_buffers(parameter.channel_count);

    for (s16 i = 0; i < parameter.channel_count; i++) {
        input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
                                                         processor.sample_count);
        output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
                                                          processor.sample_count);
    }

    auto state_{reinterpret_cast<CompressorInfo::State*>(state)};

    if (effect_enabled) {
        if (parameter.state == CompressorInfo::ParameterState::Updating) {
            SetCompressorEffectParameter(parameter, *state_);
        } else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
            InitializeCompressorEffect(parameter, *state_);
        }
    }

    ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
                          processor.sample_count);
}

bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
    return true;
}

} // namespace AudioCore::AudioRenderer
