/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <algorithm>
#include <limits.h>
#include <unordered_set>

#define LOG_TAG "EffectReverb"

#include <audio_effects/effect_bassboost.h>
#include <audio_effects/effect_equalizer.h>
#include <audio_effects/effect_virtualizer.h>
#include <android-base/logging.h>
#include <fmq/AidlMessageQueue.h>
#include <Utils.h>

#include "EffectReverb.h"
#include "ReverbTypes.h"

using aidl::android::hardware::audio::effect::Descriptor;
using aidl::android::hardware::audio::effect::EffectReverb;
using aidl::android::hardware::audio::effect::getEffectImplUuidAuxEnvReverb;
using aidl::android::hardware::audio::effect::getEffectImplUuidAuxPresetReverb;
using aidl::android::hardware::audio::effect::getEffectImplUuidInsertEnvReverb;
using aidl::android::hardware::audio::effect::getEffectImplUuidInsertPresetReverb;
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::State;
using aidl::android::media::audio::common::AudioUuid;

bool isReverbUuidSupported(const AudioUuid* uuid) {
    return (*uuid == getEffectImplUuidAuxEnvReverb() ||
            *uuid == getEffectImplUuidAuxPresetReverb() ||
            *uuid == getEffectImplUuidInsertEnvReverb() ||
            *uuid == getEffectImplUuidInsertPresetReverb());
}

extern "C" binder_exception_t createEffect(const AudioUuid* uuid,
                                           std::shared_ptr<IEffect>* instanceSpp) {
    if (uuid == nullptr || !isReverbUuidSupported(uuid)) {
        LOG(ERROR) << __func__ << "uuid not supported";
        return EX_ILLEGAL_ARGUMENT;
    }
    if (instanceSpp) {
        *instanceSpp = ndk::SharedRefBase::make<EffectReverb>(*uuid);
        return EX_NONE;
    } else {
        LOG(ERROR) << __func__ << " invalid input parameter!";
        return EX_ILLEGAL_ARGUMENT;
    }
}

extern "C" binder_exception_t queryEffect(const AudioUuid* in_impl_uuid, Descriptor* _aidl_return) {
    if (*in_impl_uuid == getEffectImplUuidAuxEnvReverb()) {
        *_aidl_return = aidl::android::hardware::audio::effect::lvm::kAuxEnvReverbDesc;
    } else if (*in_impl_uuid == getEffectImplUuidInsertEnvReverb()) {
        *_aidl_return = aidl::android::hardware::audio::effect::lvm::kInsertEnvReverbDesc;
    } else if (*in_impl_uuid == getEffectImplUuidAuxPresetReverb()) {
        *_aidl_return = aidl::android::hardware::audio::effect::lvm::kAuxPresetReverbDesc;
    } else if (*in_impl_uuid == getEffectImplUuidInsertPresetReverb()) {
        *_aidl_return = aidl::android::hardware::audio::effect::lvm::kInsertPresetReverbDesc;
    } else {
        LOG(ERROR) << __func__ << "uuid not supported";
        return EX_ILLEGAL_ARGUMENT;
    }
    return EX_NONE;
}

namespace aidl::android::hardware::audio::effect {

EffectReverb::EffectReverb(const AudioUuid& uuid) {
    if (uuid == getEffectImplUuidAuxEnvReverb()) {
        mType = lvm::ReverbEffectType::AUX_ENV;
        mDescriptor = &lvm::kAuxEnvReverbDesc;
        mEffectName = &lvm::kAuxEnvReverbEffectName;
    } else if (uuid == getEffectImplUuidInsertEnvReverb()) {
        mType = lvm::ReverbEffectType::INSERT_ENV;
        mDescriptor = &lvm::kInsertEnvReverbDesc;
        mEffectName = &lvm::kInsertEnvReverbEffectName;
    } else if (uuid == getEffectImplUuidAuxPresetReverb()) {
        mType = lvm::ReverbEffectType::AUX_PRESET;
        mDescriptor = &lvm::kAuxPresetReverbDesc;
        mEffectName = &lvm::kAuxPresetReverbEffectName;
    } else if (uuid == getEffectImplUuidInsertPresetReverb()) {
        mType = lvm::ReverbEffectType::INSERT_PRESET;
        mDescriptor = &lvm::kInsertPresetReverbDesc;
        mEffectName = &lvm::kInsertPresetReverbEffectName;
    } else {
        LOG(ERROR) << __func__ << uuid.toString() << " not supported!";
    }
}

EffectReverb::~EffectReverb() {
    cleanUp();
}

ndk::ScopedAStatus EffectReverb::getDescriptor(Descriptor* _aidl_return) {
    RETURN_IF(!_aidl_return, EX_ILLEGAL_ARGUMENT, "Parameter:nullptr");
    *_aidl_return = *mDescriptor;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus EffectReverb::setParameterSpecific(const Parameter::Specific& specific) {
    LOG(VERBOSE) << __func__ << " specific " << specific.toString();
    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");

    auto tag = specific.getTag();
    switch (tag) {
        case Parameter::Specific::presetReverb:
            return setParameterPresetReverb(specific);
        case Parameter::Specific::environmentalReverb:
            return setParameterEnvironmentalReverb(specific);
        default:
            LOG(ERROR) << __func__ << " unsupported tag " << toString(tag);
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                    "specificParamNotSupported");
    }
}

ndk::ScopedAStatus EffectReverb::setParameterPresetReverb(const Parameter::Specific& specific) {
    auto& prParam = specific.get<Parameter::Specific::presetReverb>();
    RETURN_IF(!inRange(prParam, lvm::kPresetReverbRanges), EX_ILLEGAL_ARGUMENT, "outOfRange");
    auto tag = prParam.getTag();

    switch (tag) {
        case PresetReverb::preset: {
            RETURN_IF(mContext->setPresetReverbPreset(prParam.get<PresetReverb::preset>()) !=
                              RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setPresetFailed");
            return ndk::ScopedAStatus::ok();
        }
        default: {
            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                    "PresetReverbTagNotSupported");
        }
    }
}

ndk::ScopedAStatus EffectReverb::setParameterEnvironmentalReverb(
        const Parameter::Specific& specific) {
    auto& erParam = specific.get<Parameter::Specific::environmentalReverb>();
    RETURN_IF(!inRange(erParam, lvm::kEnvReverbRanges), EX_ILLEGAL_ARGUMENT, "outOfRange");
    auto tag = erParam.getTag();

    switch (tag) {
        case EnvironmentalReverb::roomLevelMb: {
            RETURN_IF(mContext->setEnvironmentalReverbRoomLevel(
                              erParam.get<EnvironmentalReverb::roomLevelMb>()) != RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setRoomLevelFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::roomHfLevelMb: {
            RETURN_IF(
                    mContext->setEnvironmentalReverbRoomHfLevel(
                            erParam.get<EnvironmentalReverb::roomHfLevelMb>()) != RetCode::SUCCESS,
                    EX_ILLEGAL_ARGUMENT, "setRoomHfLevelFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::decayTimeMs: {
            RETURN_IF(mContext->setEnvironmentalReverbDecayTime(
                              erParam.get<EnvironmentalReverb::decayTimeMs>()) != RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setDecayTimeFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::decayHfRatioPm: {
            RETURN_IF(
                    mContext->setEnvironmentalReverbDecayHfRatio(
                            erParam.get<EnvironmentalReverb::decayHfRatioPm>()) != RetCode::SUCCESS,
                    EX_ILLEGAL_ARGUMENT, "setDecayHfRatioFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::reflectionsLevelMb: {
            RETURN_IF(mContext->setReflectionsLevel(
                              erParam.get<EnvironmentalReverb::reflectionsLevelMb>()) !=
                              RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setReflectionsLevelFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::reflectionsDelayMs: {
            RETURN_IF(mContext->setReflectionsDelay(
                              erParam.get<EnvironmentalReverb::reflectionsDelayMs>()) !=
                              RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setReflectionsDelayFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::levelMb: {
            RETURN_IF(mContext->setEnvironmentalReverbLevel(
                              erParam.get<EnvironmentalReverb::levelMb>()) != RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setLevelFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::delayMs: {
            RETURN_IF(mContext->setEnvironmentalReverbDelay(
                              erParam.get<EnvironmentalReverb::delayMs>()) != RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setDelayFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::diffusionPm: {
            RETURN_IF(mContext->setEnvironmentalReverbDiffusion(
                              erParam.get<EnvironmentalReverb::diffusionPm>()) != RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setDiffusionFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::densityPm: {
            RETURN_IF(mContext->setEnvironmentalReverbDensity(
                              erParam.get<EnvironmentalReverb::densityPm>()) != RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setDensityFailed");
            return ndk::ScopedAStatus::ok();
        }
        case EnvironmentalReverb::bypass: {
            RETURN_IF(mContext->setEnvironmentalReverbBypass(
                              erParam.get<EnvironmentalReverb::bypass>()) != RetCode::SUCCESS,
                      EX_ILLEGAL_ARGUMENT, "setBypassFailed");
            return ndk::ScopedAStatus::ok();
        }
        default: {
            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
                    EX_ILLEGAL_ARGUMENT, "EnvironmentalReverbTagNotSupported");
        }
    }
}

ndk::ScopedAStatus EffectReverb::getParameterSpecific(const Parameter::Id& id,
                                                      Parameter::Specific* specific) {
    RETURN_IF(!specific, EX_NULL_POINTER, "nullPtr");
    auto tag = id.getTag();

    switch (tag) {
        case Parameter::Id::presetReverbTag:
            return getParameterPresetReverb(id.get<Parameter::Id::presetReverbTag>(), specific);
        case Parameter::Id::environmentalReverbTag:
            return getParameterEnvironmentalReverb(id.get<Parameter::Id::environmentalReverbTag>(),
                                                   specific);
        default:
            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                    "wrongIdTag");
    }
}

ndk::ScopedAStatus EffectReverb::getParameterPresetReverb(const PresetReverb::Id& id,
                                                          Parameter::Specific* specific) {
    RETURN_IF(id.getTag() != PresetReverb::Id::commonTag, EX_ILLEGAL_ARGUMENT,
              "PresetReverbTagNotSupported");
    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
    PresetReverb prParam;
    auto tag = id.get<PresetReverb::Id::commonTag>();
    switch (tag) {
        case PresetReverb::preset: {
            prParam.set<PresetReverb::preset>(mContext->getPresetReverbPreset());
            break;
        }
        default: {
            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                    "PresetReverbTagNotSupported");
        }
    }

    specific->set<Parameter::Specific::presetReverb>(prParam);
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus EffectReverb::getParameterEnvironmentalReverb(const EnvironmentalReverb::Id& id,
                                                                 Parameter::Specific* specific) {
    RETURN_IF(id.getTag() != EnvironmentalReverb::Id::commonTag, EX_ILLEGAL_ARGUMENT,
              "EnvironmentalReverbTagNotSupported");
    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
    EnvironmentalReverb erParam;

    auto tag = id.get<EnvironmentalReverb::Id::commonTag>();
    switch (tag) {
        case EnvironmentalReverb::roomLevelMb: {
            erParam.set<EnvironmentalReverb::roomLevelMb>(
                    mContext->getEnvironmentalReverbRoomLevel());
            break;
        }
        case EnvironmentalReverb::roomHfLevelMb: {
            erParam.set<EnvironmentalReverb::roomHfLevelMb>(
                    mContext->getEnvironmentalReverbRoomHfLevel());
            break;
        }
        case EnvironmentalReverb::decayTimeMs: {
            erParam.set<EnvironmentalReverb::decayTimeMs>(
                    mContext->getEnvironmentalReverbDecayTime());
            break;
        }
        case EnvironmentalReverb::decayHfRatioPm: {
            erParam.set<EnvironmentalReverb::decayHfRatioPm>(
                    mContext->getEnvironmentalReverbDecayHfRatio());
            break;
        }
        case EnvironmentalReverb::reflectionsLevelMb: {
            erParam.set<EnvironmentalReverb::reflectionsLevelMb>(mContext->getReflectionsLevel());
            break;
        }
        case EnvironmentalReverb::reflectionsDelayMs: {
            erParam.set<EnvironmentalReverb::reflectionsDelayMs>(mContext->getReflectionsDelay());
            break;
        }
        case EnvironmentalReverb::levelMb: {
            erParam.set<EnvironmentalReverb::levelMb>(mContext->getEnvironmentalReverbLevel());
            break;
        }
        case EnvironmentalReverb::delayMs: {
            erParam.set<EnvironmentalReverb::delayMs>(mContext->getEnvironmentalReverbDelay());
            break;
        }
        case EnvironmentalReverb::diffusionPm: {
            erParam.set<EnvironmentalReverb::diffusionPm>(
                    mContext->getEnvironmentalReverbDiffusion());
            break;
        }
        case EnvironmentalReverb::densityPm: {
            erParam.set<EnvironmentalReverb::densityPm>(mContext->getEnvironmentalReverbDensity());
            break;
        }
        case EnvironmentalReverb::bypass: {
            erParam.set<EnvironmentalReverb::bypass>(mContext->getEnvironmentalReverbBypass());
            break;
        }
        default: {
            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
                    EX_ILLEGAL_ARGUMENT, "EnvironmentalReverbTagNotSupported");
        }
    }

    specific->set<Parameter::Specific::environmentalReverb>(erParam);
    return ndk::ScopedAStatus::ok();
}

std::shared_ptr<EffectContext> EffectReverb::createContext(const Parameter::Common& common) {
    if (mContext) {
        LOG(DEBUG) << __func__ << " context already exist";
    } else {
        mContext = std::make_shared<ReverbContext>(1 /* statusFmqDepth */, common, mType);
    }

    return mContext;
}

RetCode EffectReverb::releaseContext() {
    if (mContext) {
        mContext.reset();
    }
    return RetCode::SUCCESS;
}

ndk::ScopedAStatus EffectReverb::commandImpl(CommandId command) {
    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
    switch (command) {
        case CommandId::START:
            mContext->enable();
            break;
        case CommandId::STOP:
            mContext->disable();
            break;
        case CommandId::RESET:
            mContext->disable();
            mContext->resetBuffer();
            break;
        default:
            LOG(ERROR) << __func__ << " commandId " << toString(command) << " not supported";
            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                    "commandIdNotSupported");
    }
    return ndk::ScopedAStatus::ok();
}

// Processing method running in EffectWorker thread.
IEffect::Status EffectReverb::effectProcessImpl(float* in, float* out, int sampleToProcess) {
    IEffect::Status status = {EX_NULL_POINTER, 0, 0};
    RETURN_VALUE_IF(!mContext, status, "nullContext");
    return mContext->process(in, out, sampleToProcess);
}

}  // namespace aidl::android::hardware::audio::effect