/*
**
** Copyright 2022, 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.
*/

// #define LOG_NDEBUG 0
#define LOG_TAG "MelReporter"

#include "MelReporter.h"

#include <android/media/ISoundDoseCallback.h>
#include <audio_utils/power.h>
#include <utils/Log.h>

using aidl::android::hardware::audio::core::sounddose::ISoundDose;

namespace android {

bool MelReporter::activateHalSoundDoseComputation(const std::string& module,
        const sp<DeviceHalInterface>& device) {
    if (mSoundDoseManager->isFrameworkMelForced()) {
        ALOGD("%s: Forcing use of internal MEL computation.", __func__);
        activateInternalSoundDoseComputation();
        return false;
    }

    ndk::SpAIBinder soundDoseBinder;
    if (device->getSoundDoseInterface(module, &soundDoseBinder) != OK) {
        ALOGW("%s: HAL cannot provide sound dose interface for module %s",
              __func__, module.c_str());
        return false;
    }

    if (soundDoseBinder == nullptr) {
         ALOGW("%s: HAL doesn't implement a sound dose interface for module %s",
              __func__, module.c_str());
        return false;
    }

    std::shared_ptr<ISoundDose> soundDoseInterface = ISoundDose::fromBinder(soundDoseBinder);

    if (!mSoundDoseManager->setHalSoundDoseInterface(module, soundDoseInterface)) {
        ALOGW("%s: cannot activate HAL MEL reporting for module %s", __func__, module.c_str());
        return false;
    }

    stopInternalMelComputation();
    return true;
}

void MelReporter::activateInternalSoundDoseComputation() {
    {
        audio_utils::lock_guard _l(mutex());
        if (!mUseHalSoundDoseInterface) {
            // no need to start internal MEL on active patches
            return;
        }
        mUseHalSoundDoseInterface = false;
    }

    // reset the HAL interfaces and use internal MELs
    mSoundDoseManager->resetHalSoundDoseInterfaces();
}

void MelReporter::onFirstRef() {
    mAfMelReporterCallback->getPatchCommandThread()->addListener(this);

    mSoundDoseManager = sp<SoundDoseManager>::make(sp<IMelReporterCallback>::fromExisting(this));
}

void MelReporter::updateMetadataForCsd(audio_io_handle_t streamHandle,
        const std::vector<playback_track_metadata_v7_t>& metadataVec) {
    if (!mSoundDoseManager->isCsdEnabled()) {
        ALOGV("%s csd is disabled", __func__);
        return;
    }

    audio_utils::lock_guard _laf(mAfMelReporterCallback->mutex());  // AudioFlinger_Mutex
    audio_utils::lock_guard _l(mutex());
    auto activeMelPatchId = activePatchStreamHandle_l(streamHandle);
    if (!activeMelPatchId) {
        ALOGV("%s stream handle %d does not have an active patch", __func__, streamHandle);
        return;
    }

    bool shouldActivateCsd = false;
    for (const auto& metadata : metadataVec) {
        if (metadata.base.usage == AUDIO_USAGE_GAME || metadata.base.usage == AUDIO_USAGE_MEDIA) {
            shouldActivateCsd = true;
        }
    }

    auto activeMelPatchIt = mActiveMelPatches.find(activeMelPatchId.value());
    if (activeMelPatchIt != mActiveMelPatches.end()) {
        if (shouldActivateCsd != activeMelPatchIt->second.csdActive) {
            if (activeMelPatchIt->second.csdActive) {
                ALOGV("%s should not compute CSD for stream handle %d", __func__, streamHandle);
                stopMelComputationForPatch_l(activeMelPatchIt->second);
            } else {
                ALOGV("%s should compute CSD for stream handle %d", __func__, streamHandle);
                startMelComputationForActivePatch_l(activeMelPatchIt->second);
            }
            activeMelPatchIt->second.csdActive = shouldActivateCsd;
        }
    }
}

void MelReporter::resetReferencesForTest() {
    mAfMelReporterCallback.clear();
    mSoundDoseManager->resetReferencesForTest();
}

void MelReporter::onCreateAudioPatch(audio_patch_handle_t handle,
        const IAfPatchPanel::Patch& patch) {
    if (!mSoundDoseManager->isCsdEnabled()) {
        ALOGV("%s csd is disabled", __func__);
        return;
    }

    ALOGV("%s: handle %d mHalHandle %d device sink %08x",
            __func__, handle, patch.mHalHandle,
            patch.mAudioPatch.num_sinks > 0 ? patch.mAudioPatch.sinks[0].ext.device.type : 0);
    if (patch.mAudioPatch.num_sources == 0
        || patch.mAudioPatch.sources[0].type != AUDIO_PORT_TYPE_MIX) {
        ALOGV("%s: patch does not contain any mix sources", __func__);
        return;
    }

    audio_io_handle_t streamHandle = patch.mAudioPatch.sources[0].ext.mix.handle;
    ActiveMelPatch newPatch;
    newPatch.streamHandle = streamHandle;
    newPatch.csdActive = false;
    for (size_t i = 0; i < patch.mAudioPatch.num_sinks; ++i) {
        if (patch.mAudioPatch.sinks[i].type == AUDIO_PORT_TYPE_DEVICE &&
                mSoundDoseManager->shouldComputeCsdForDeviceType(
                        patch.mAudioPatch.sinks[i].ext.device.type)) {
            audio_port_handle_t deviceId = patch.mAudioPatch.sinks[i].id;
            bool shouldComputeCsd = mSoundDoseManager->shouldComputeCsdForDeviceWithAddress(
                    patch.mAudioPatch.sinks[i].ext.device.type,
                    patch.mAudioPatch.sinks[i].ext.device.address);
            newPatch.deviceStates.push_back({deviceId, shouldComputeCsd});
            newPatch.csdActive |= shouldComputeCsd;
            AudioDeviceTypeAddr adt{patch.mAudioPatch.sinks[i].ext.device.type,
                                    patch.mAudioPatch.sinks[i].ext.device.address};
            mSoundDoseManager->mapAddressToDeviceId(adt, deviceId);
        }
    }

    if (!newPatch.deviceStates.empty() && newPatch.csdActive) {
        audio_utils::lock_guard _afl(mAfMelReporterCallback->mutex());  // AudioFlinger_Mutex
        audio_utils::lock_guard _l(mutex());
        ALOGV("%s add patch handle %d to active devices", __func__, handle);
        startMelComputationForActivePatch_l(newPatch);
        mActiveMelPatches[handle] = newPatch;
    }
}

void MelReporter::startMelComputationForActivePatch_l(const ActiveMelPatch& patch)
NO_THREAD_SAFETY_ANALYSIS  // access of AudioFlinger::checkOutputThread_l
{
    auto outputThread = mAfMelReporterCallback->checkOutputThread_l(patch.streamHandle);
    if (outputThread == nullptr) {
        ALOGE("%s cannot find thread for stream handle %d", __func__, patch.streamHandle);
        return;
    }

    for (const auto& device : patch.deviceStates) {
        if (device.second) {
            ++mActiveDevices[device.first];
            ALOGI("%s add stream %d that uses device %d for CSD, nr of streams: %d", __func__,
                  patch.streamHandle, device.first, mActiveDevices[device.first]);

            if (outputThread != nullptr && !useHalSoundDoseInterface_l()) {
                outputThread->startMelComputation_l(
                        mSoundDoseManager->getOrCreateProcessorForDevice(
                                device.first,
                                patch.streamHandle,
                                outputThread->sampleRate(),
                                outputThread->channelCount(),
                                outputThread->format()));
            }
        }
    }
}

void MelReporter::startMelComputationForDeviceId(audio_port_handle_t deviceId) {
    ALOGV("%s(%d)", __func__, deviceId);
    audio_utils::lock_guard _laf(mAfMelReporterCallback->mutex());
    audio_utils::lock_guard _l(mutex());

    for (auto& activeMelPatch : mActiveMelPatches) {
        bool csdActive = false;
        for (auto& device: activeMelPatch.second.deviceStates) {
            if (device.first == deviceId && !device.second) {
                device.second = true;
            }
            csdActive |= device.second;
        }
        if (csdActive && !activeMelPatch.second.csdActive) {
            activeMelPatch.second.csdActive = csdActive;
            startMelComputationForActivePatch_l(activeMelPatch.second);
        }
    }
}

void MelReporter::onReleaseAudioPatch(audio_patch_handle_t handle) {
    if (!mSoundDoseManager->isCsdEnabled()) {
        ALOGV("%s csd is disabled", __func__);
        return;
    }

    ActiveMelPatch melPatch;
    {
        audio_utils::lock_guard _l(mutex());

        auto patchIt = mActiveMelPatches.find(handle);
        if (patchIt == mActiveMelPatches.end()) {
            ALOGV("%s patch handle %d does not contain any mix sources with active MEL calculation",
                    __func__, handle);
            return;
        }

        melPatch = patchIt->second;
        mActiveMelPatches.erase(patchIt);
    }

    audio_utils::lock_guard _afl(mAfMelReporterCallback->mutex());  // AudioFlinger_Mutex
    audio_utils::lock_guard _l(mutex());
    if (melPatch.csdActive) {
        // only need to stop if patch was active
        melPatch.csdActive = false;
        stopMelComputationForPatch_l(melPatch);
    }
}

void MelReporter::onUpdateAudioPatch(audio_patch_handle_t oldHandle,
        audio_patch_handle_t newHandle, const IAfPatchPanel::Patch& patch) {
    onReleaseAudioPatch(oldHandle);
    onCreateAudioPatch(newHandle, patch);
}

sp<media::ISoundDose> MelReporter::getSoundDoseInterface(
        const sp<media::ISoundDoseCallback>& callback) {
    // no need to lock since getSoundDoseInterface is synchronized
    return mSoundDoseManager->getSoundDoseInterface(callback);
}

void MelReporter::stopInternalMelComputation() {
    ALOGV("%s", __func__);
    audio_utils::lock_guard _l(mutex());
    if (mUseHalSoundDoseInterface) {
        return;
    }
    mActiveMelPatches.clear();
    mUseHalSoundDoseInterface = true;
}

void MelReporter::stopMelComputationForPatch_l(const ActiveMelPatch& patch)
NO_THREAD_SAFETY_ANALYSIS  // access of AudioFlinger::checkOutputThread_l
{
    auto outputThread = mAfMelReporterCallback->checkOutputThread_l(patch.streamHandle);

    ALOGV("%s: stop MEL for stream id: %d", __func__, patch.streamHandle);
    for (const auto& device : patch.deviceStates) {
        if (mActiveDevices[device.first] > 0) {
            --mActiveDevices[device.first];
            if (mActiveDevices[device.first] == 0) {
                // no stream is using deviceId anymore
                ALOGI("%s removing device %d from active CSD devices", __func__, device.first);
                mSoundDoseManager->clearMapDeviceIdEntries(device.first);
            }
        }
    }

    mSoundDoseManager->removeStreamProcessor(patch.streamHandle);
    if (outputThread != nullptr && !useHalSoundDoseInterface_l()) {
        outputThread->stopMelComputation_l();
    }
}

void MelReporter::stopMelComputationForDeviceId(audio_port_handle_t deviceId) {
    ALOGV("%s(%d)", __func__, deviceId);
    audio_utils::lock_guard _laf(mAfMelReporterCallback->mutex());
    audio_utils::lock_guard _l(mutex());

    for (auto& activeMelPatch : mActiveMelPatches) {
        bool csdActive = false;
        for (auto& device: activeMelPatch.second.deviceStates) {
            if (device.first == deviceId && device.second) {
                device.second = false;
            }
            csdActive |= device.second;
        }

        if (!csdActive && activeMelPatch.second.csdActive) {
            activeMelPatch.second.csdActive = csdActive;
            stopMelComputationForPatch_l(activeMelPatch.second);
        }
    }

}

void MelReporter::applyAllAudioPatches() {
    ALOGV("%s", __func__);

    std::vector<IAfPatchPanel::Patch> patchesCopy;
    {
        audio_utils::lock_guard _laf(mAfMelReporterCallback->mutex());
        for (const auto& patch : mAfPatchPanel->patches_l()) {
            patchesCopy.emplace_back(patch.second);
        }
    }

    for (const auto& patch : patchesCopy) {
        onCreateAudioPatch(patch.mHalHandle, patch);
    }
}

std::optional<audio_patch_handle_t> MelReporter::activePatchStreamHandle_l(
        audio_io_handle_t streamHandle) {
    for(const auto& patchIt : mActiveMelPatches) {
        if (patchIt.second.streamHandle == streamHandle) {
            return patchIt.first;
        }
    }
    return std::nullopt;
}

bool MelReporter::useHalSoundDoseInterface_l() {
    return !mSoundDoseManager->isFrameworkMelForced() & mUseHalSoundDoseInterface;
}

std::string MelReporter::dump() {
    audio_utils::lock_guard _l(mutex());
    std::string output("\nSound Dose:\n");
    output.append(mSoundDoseManager->dump());
    return output;
}

}  // namespace android
