/*
 * 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 <cstddef>
#include <iterator>
#include <memory>
#define LOG_TAG "EffectProxy"
// #define LOG_NDEBUG 0

#include <fmq/AidlMessageQueue.h>
#include <system/audio_aidl_utils.h>
#include <utils/Log.h>

#include "EffectProxy.h"

using ::aidl::android::hardware::audio::effect::Capability;
using ::aidl::android::hardware::audio::effect::CommandId;
using ::aidl::android::hardware::audio::effect::Descriptor;
using ::aidl::android::hardware::audio::effect::Flags;
using ::aidl::android::hardware::audio::effect::IEffect;
using ::aidl::android::hardware::audio::effect::IFactory;
using ::aidl::android::hardware::audio::effect::Parameter;
using ::aidl::android::hardware::audio::effect::State;
using ::aidl::android::media::audio::common::AudioUuid;

namespace android::effect {

EffectProxy::EffectProxy(const AudioUuid& uuid, const std::vector<Descriptor>& descriptors,
                         const std::shared_ptr<IFactory>& factory)
    : mDescriptorCommon(buildDescriptorCommon(uuid, descriptors)),
      mSubEffects(
              [](const std::vector<Descriptor>& descs, const std::shared_ptr<IFactory>& factory) {
                  std::vector<SubEffect> subEffects;
                  ALOG_ASSERT(factory, "invalid EffectFactory handle");
                  ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();
                  for (const auto& desc : descs) {
                      SubEffect sub({.descriptor = desc});
                      status = factory->createEffect(desc.common.id.uuid, &sub.handle);
                      if (!status.isOk() || !sub.handle) {
                          sub.handle = nullptr;
                          ALOGW("%s create sub-effect %s failed", __func__,
                                ::android::audio::utils::toString(desc.common.id.uuid).c_str());
                      }
                      subEffects.emplace_back(sub);
                  }
                  return subEffects;
              }(descriptors, factory)),
      mFactory(factory) {}

EffectProxy::~EffectProxy() {
    close();
    destroy();
    mSubEffects.clear();
}

ndk::ScopedAStatus EffectProxy::destroy() {
    ALOGV("%s: %s", __func__,
          ::android::audio::utils::toString(mDescriptorCommon.id.type).c_str());
    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
        ndk::ScopedAStatus status = mFactory->destroyEffect(effect);
        if (status.isOk()) {
            effect.reset();
        }
        return status;
    });
}

ndk::ScopedAStatus EffectProxy::setOffloadParam(const effect_offload_param_t* offload) {
    const auto& itor = std::find_if(mSubEffects.begin(), mSubEffects.end(), [&](const auto& sub) {
        const auto& desc = sub.descriptor;
        return offload->isOffload == desc.common.flags.offloadIndication;
    });
    if (itor == mSubEffects.end()) {
        ALOGE("%s no %soffload sub-effect found", __func__, offload->isOffload ? "" : "non-");
        mActiveSubIdx = 0;
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER,
                                                                "noActiveEffctFound");
    }

    mActiveSubIdx = std::distance(mSubEffects.begin(), itor);
    ALOGI("%s: active %soffload sub-effect %zu: %s", __func__,
          offload->isOffload ? "" : "non-", mActiveSubIdx,
          ::android::audio::utils::toString(mSubEffects[mActiveSubIdx].descriptor.common.id.uuid)
                  .c_str());
    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
        return effect->setParameter(Parameter::make<Parameter::offload>(offload->isOffload));
    });
}

// EffectProxy go over sub-effects and call IEffect interfaces
ndk::ScopedAStatus EffectProxy::open(const Parameter::Common& common,
                                     const std::optional<Parameter::Specific>& specific,
                                     IEffect::OpenEffectReturn* ret __unused) {
    ndk::ScopedAStatus status =
            ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, "nullEffectHandle");
    for (auto& sub : mSubEffects) {
        IEffect::OpenEffectReturn openReturn;
        if (!sub.handle || !(status = sub.handle->open(common, specific, &openReturn)).isOk()) {
            ALOGE("%s: failed to open %p UUID %s", __func__, sub.handle.get(),
                  ::android::audio::utils::toString(sub.descriptor.common.id.uuid).c_str());
            break;
        }
        sub.effectMq.statusQ = std::make_shared<StatusMQ>(openReturn.statusMQ);
        sub.effectMq.inputQ = std::make_shared<DataMQ>(openReturn.inputDataMQ);
        sub.effectMq.outputQ = std::make_shared<DataMQ>(openReturn.outputDataMQ);
    }

    // close all opened effects if failure
    if (!status.isOk()) {
        ALOGE("%s: closing all sub-effects with error %s", __func__,
              status.getDescription().c_str());
        close();
    }

    return status;
}

ndk::ScopedAStatus EffectProxy::reopen(OpenEffectReturn* ret __unused) {
    ndk::ScopedAStatus status =
            ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE, "nullEffectHandle");
    for (auto& sub : mSubEffects) {
        IEffect::OpenEffectReturn openReturn;
        if (!sub.handle || !(status = sub.handle->reopen(&openReturn)).isOk()) {
            ALOGE("%s: failed to open %p UUID %s", __func__, sub.handle.get(),
                  ::android::audio::utils::toString(sub.descriptor.common.id.uuid).c_str());
            break;
        }
        sub.effectMq.statusQ = std::make_shared<StatusMQ>(openReturn.statusMQ);
        sub.effectMq.inputQ = std::make_shared<DataMQ>(openReturn.inputDataMQ);
        sub.effectMq.outputQ = std::make_shared<DataMQ>(openReturn.outputDataMQ);
    }

    // close all opened effects if failure
    if (!status.isOk()) {
        ALOGW("%s: closing all sub-effects with error %s", __func__,
              status.getDescription().c_str());
        close();
    }

    return status;
}

ndk::ScopedAStatus EffectProxy::close() {
    command(CommandId::STOP);
    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
        return effect->close();
    });
}

ndk::ScopedAStatus EffectProxy::getDescriptor(Descriptor* desc) {
    *desc = mSubEffects[mActiveSubIdx].descriptor;
    desc->common = mDescriptorCommon;
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus EffectProxy::buildDescriptor(const AudioUuid& uuid,
                                                const std::vector<Descriptor>& subEffectDescs,
                                                Descriptor* desc) {
    if (!desc) {
        ALOGE("%s: null descriptor pointer", __func__);
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER, "nullptr");
    }

    if (subEffectDescs.size() < 2) {
        ALOGE("%s: proxy need at least 2 sub-effects, got %zu", __func__, subEffectDescs.size());
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
                                                                "needMoreSubEffects");
    }

    desc->common = buildDescriptorCommon(uuid, subEffectDescs);
    return ndk::ScopedAStatus::ok();
}

// Sub-effects are required to have identical features, so here we return the SW sub-effect
// descriptor, with the implementation UUID replaced with proxy UUID, and flags setting respect all
// sub-effects.
Descriptor::Common EffectProxy::buildDescriptorCommon(
        const AudioUuid& uuid, const std::vector<Descriptor>& subEffectDescs) {
    Descriptor::Common swCommon;
    const Flags& firstFlag = subEffectDescs[0].common.flags;
    bool offloadExist = false;
    for (const auto& desc : subEffectDescs) {
        if (desc.common.flags.offloadIndication) {
            offloadExist = true;
        } else {
            swCommon = desc.common;
        }
        if (desc.common.flags.audioModeIndication != firstFlag.audioModeIndication ||
            desc.common.flags.audioSourceIndication != firstFlag.audioSourceIndication ||
            desc.common.flags.sinkMetadataIndication != firstFlag.sinkMetadataIndication ||
            desc.common.flags.sourceMetadataIndication != firstFlag.sourceMetadataIndication ||
            desc.common.flags.deviceIndication != firstFlag.deviceIndication) {
            ALOGW("Inconsistent flags %s vs %s", desc.common.flags.toString().c_str(),
                  firstFlag.toString().c_str());
        }
    }

    swCommon.flags.offloadIndication = offloadExist;
    // replace implementation UUID with proxy UUID.
    swCommon.id.uuid = uuid;
    swCommon.id.proxy = std::nullopt;
    return swCommon;
}

// Handle with active sub-effect first, only send to other sub-effects when success
ndk::ScopedAStatus EffectProxy::command(CommandId id) {
    return runWithActiveSubEffectThenOthers(
            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
                return effect->command(id);
            });
}

// Return the active sub-effect state
ndk::ScopedAStatus EffectProxy::getState(State* state) {
    return runWithActiveSubEffect(
            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
                return effect->getState(state);
            });
}

// Handle with active sub-effect first, only send to other sub-effects when success
ndk::ScopedAStatus EffectProxy::setParameter(const Parameter& param) {
    return runWithActiveSubEffectThenOthers(
            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
                return effect->setParameter(param);
            });
}

// Return the active sub-effect parameter
ndk::ScopedAStatus EffectProxy::getParameter(const Parameter::Id& id, Parameter* param) {
    return runWithActiveSubEffect(
            [&](const std::shared_ptr<IEffect>& effect) -> ndk::ScopedAStatus {
                return effect->getParameter(id, param);
            });
}

ndk::ScopedAStatus EffectProxy::runWithActiveSubEffectThenOthers(
        std::function<ndk::ScopedAStatus(const std::shared_ptr<IEffect>&)> const& func) {
    ndk::ScopedAStatus status = runWithActiveSubEffect(func);
    if (!status.isOk()) {
        ALOGW("%s active sub-effect return error %s", __func__, status.getDescription().c_str());
    }

    // proceed with others
    for (size_t i = 0; i < mSubEffects.size(); i++) {
        if (i == mActiveSubIdx) {
            continue;
        }
        if (!mSubEffects[i].handle) {
            ALOGW("%s null sub-effect interface for %s", __func__,
                  mSubEffects[i].descriptor.common.id.uuid.toString().c_str());
            continue;
        }
        func(mSubEffects[i].handle);
    }
    return status;
}

ndk::ScopedAStatus EffectProxy::runWithActiveSubEffect(
        std::function<ndk::ScopedAStatus(const std::shared_ptr<IEffect>&)> const& func) {
    if (!mSubEffects[mActiveSubIdx].handle) {
        ALOGE("%s null active sub-effect interface, active %s", __func__,
              mSubEffects[mActiveSubIdx].descriptor.toString().c_str());
        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_NULL_POINTER,
                                                                "activeSubEffectNull");
    }
    return func(mSubEffects[mActiveSubIdx].handle);
}

ndk::ScopedAStatus EffectProxy::runWithAllSubEffects(
        std::function<ndk::ScopedAStatus(std::shared_ptr<IEffect>&)> const& func) {
    ndk::ScopedAStatus status = ndk::ScopedAStatus::ok();
    // proceed with others if active sub-effect success
    for (auto& sub : mSubEffects) {
        if (!sub.handle) {
            ALOGW("%s null sub-effect interface %s", __func__, sub.descriptor.toString().c_str());
            continue;
        }
        ndk::ScopedAStatus temp = func(sub.handle);
        if (!temp.isOk()) {
            status = ndk::ScopedAStatus::fromStatus(temp.getStatus());
        }
    }
    return status;
}

bool EffectProxy::isBypassing() const {
    return mSubEffects[mActiveSubIdx].descriptor.common.flags.bypass;
}

bool EffectProxy::isTunnel() const {
    return mSubEffects[mActiveSubIdx].descriptor.common.flags.hwAcceleratorMode ==
           Flags::HardwareAccelerator::TUNNEL;
}

binder_status_t EffectProxy::dump(int fd, const char** args, uint32_t numArgs) {
    const std::string dumpString = toString();
    write(fd, dumpString.c_str(), dumpString.size());

    return runWithAllSubEffects([&](std::shared_ptr<IEffect>& effect) {
               return ndk::ScopedAStatus::fromStatus(effect->dump(fd, args, numArgs));
           })
            .getStatus();
}

std::string EffectProxy::toString(size_t level) const {
    std::string prefixSpace(level, ' ');
    std::string ss = prefixSpace + "EffectProxy:\n";
    prefixSpace += " ";
    base::StringAppendF(&ss, "%sDescriptorCommon: %s\n", prefixSpace.c_str(),
                        mDescriptorCommon.toString().c_str());
    base::StringAppendF(&ss, "%sActiveSubIdx: %zu\n", prefixSpace.c_str(), mActiveSubIdx);
    base::StringAppendF(&ss, "%sAllSubEffects:\n", prefixSpace.c_str());
    for (size_t i = 0; i < mSubEffects.size(); i++) {
        base::StringAppendF(&ss, "%s[%zu] - Handle: %p, %s\n", prefixSpace.c_str(), i,
                            mSubEffects[i].handle.get(),
                            mSubEffects[i].descriptor.toString().c_str());
    }
    return ss;
}

} // namespace android::effect
