/*
 * 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 <cstdint>
#include <cstring>
#define LOG_TAG "AidlConversionSpatializer"
//#define LOG_NDEBUG 0

#include <aidl/android/hardware/audio/effect/DefaultExtension.h>
#include <aidl/android/hardware/audio/effect/VendorExtension.h>
#include <error/expected_utils.h>
#include <media/AidlConversionCppNdk.h>
#include <media/AidlConversionEffect.h>
#include <media/AidlConversionNdk.h>
#include <system/audio_effects/aidl_effects_utils.h>
#include <system/audio_effects/effect_spatializer.h>
#include <utils/Log.h>

#include "AidlConversionSpatializer.h"

namespace android {
namespace effect {

using aidl::android::getParameterSpecificField;
using aidl::android::aidl_utils::statusTFromBinderStatus;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::hardware::audio::effect::DefaultExtension;
using aidl::android::hardware::audio::effect::Parameter;
using aidl::android::hardware::audio::effect::Range;
using aidl::android::hardware::audio::effect::Spatializer;
using aidl::android::hardware::audio::effect::VendorExtension;
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::HeadTracking;
using aidl::android::media::audio::common::Spatialization;
using aidl::android::media::audio::common::toString;
using android::status_t;
using utils::EffectParamReader;
using utils::EffectParamWriter;

bool AidlConversionSpatializer::isSpatializerParameterSupported() {
    return mIsSpatializerAidlParamSupported.value_or(
            (mIsSpatializerAidlParamSupported =
                     [&]() {
                         ::aidl::android::hardware::audio::effect::Parameter aidlParam;
                         auto id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag,
                                                              Spatializer::vendor);
                         // No range defined in descriptor capability means no Spatializer AIDL
                         // implementation BAD_VALUE return from getParameter indicates the
                         // parameter is not supported by HAL
                         return mDesc.capability.range.getTag() == Range::spatializer &&
                                mEffect->getParameter(id, &aidlParam).getStatus() !=
                                        android::BAD_VALUE;
                     }())
                    .value());
}

status_t AidlConversionSpatializer::setParameter(EffectParamReader& param) {
    Parameter aidlParam;
    if (isSpatializerParameterSupported()) {
        uint32_t command = 0;
        if (!param.validateParamValueSize(sizeof(uint32_t), sizeof(int8_t)) ||
            OK != param.readFromParameter(&command)) {
            ALOGE("%s %d invalid param %s", __func__, __LINE__, param.toString().c_str());
            return BAD_VALUE;
        }

        switch (command) {
            case SPATIALIZER_PARAM_LEVEL: {
                Spatialization::Level level = Spatialization::Level::NONE;
                if (OK != param.readFromValue(&level)) {
                    ALOGE("%s invalid level value %s", __func__, param.toString().c_str());
                    return BAD_VALUE;
                }
                aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer,
                                                    spatializationLevel, level);
                break;
            }
            case SPATIALIZER_PARAM_HEADTRACKING_MODE: {
                HeadTracking::Mode mode = HeadTracking::Mode::DISABLED;
                if (OK != param.readFromValue(&mode)) {
                    ALOGE("%s invalid mode value %s", __func__, param.toString().c_str());
                    return BAD_VALUE;
                }
                aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, headTrackingMode,
                                                    mode);
                break;
            }
            case SPATIALIZER_PARAM_HEAD_TO_STAGE: {
                const size_t valueSize = param.getValueSize();
                if (valueSize / sizeof(float) > 6 || valueSize % sizeof(float) != 0) {
                    ALOGE("%s invalid parameter value size %zu", __func__, valueSize);
                    return BAD_VALUE;
                }
                std::array<float, 6> headToStage = {};
                for (size_t i = 0; i < valueSize / sizeof(float); i++) {
                    if (OK != param.readFromValue(&headToStage[i])) {
                        ALOGE("%s failed to read headToStage from %s", __func__,
                              param.toString().c_str());
                        return BAD_VALUE;
                    }
                }
                HeadTracking::SensorData sensorData =
                        HeadTracking::SensorData::make<HeadTracking::SensorData::headToStage>(
                                headToStage);
                aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer,
                                                    headTrackingSensorData, sensorData);
                break;
            }
            case SPATIALIZER_PARAM_HEADTRACKING_CONNECTION: {
                int32_t modeInt32 = 0;
                int32_t sensorId = -1;
                if (OK != param.readFromValue(&modeInt32) || OK != param.readFromValue(&sensorId)) {
                    ALOGE("%s %d invalid parameter value %s", __func__, __LINE__,
                          param.toString().c_str());
                    return BAD_VALUE;
                }

                const auto mode = static_cast<HeadTracking::ConnectionMode>(modeInt32);
                if (mode < *ndk::enum_range<HeadTracking::ConnectionMode>().begin() ||
                    mode > *ndk::enum_range<HeadTracking::ConnectionMode>().end()) {
                    ALOGE("%s %d invalid mode %d", __func__, __LINE__, modeInt32);
                    return BAD_VALUE;
                }
                aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer,
                                                    headTrackingConnectionMode, mode);
                if (status_t status = statusTFromBinderStatus(mEffect->setParameter(aidlParam));
                    status != OK) {
                    ALOGE("%s failed to set headTrackingConnectionMode %s", __func__,
                          toString(mode).c_str());
                    return status;
                }
                aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, headTrackingSensorId,
                                                    sensorId);
                return statusTFromBinderStatus(mEffect->setParameter(aidlParam));
            }
            default: {
                // for vendor extension, copy data area to the DefaultExtension, parameter ignored
                VendorExtension ext = VALUE_OR_RETURN_STATUS(
                        aidl::android::legacy2aidl_EffectParameterReader_VendorExtension(param));
                aidlParam =
                        MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, vendor, ext);
                break;
            }
        }
    } else {
        aidlParam = VALUE_OR_RETURN_STATUS(
                ::aidl::android::legacy2aidl_EffectParameterReader_Parameter(param));
    }

    return statusTFromBinderStatus(mEffect->setParameter(aidlParam));
}

status_t AidlConversionSpatializer::getParameter(EffectParamWriter& param) {
    if (isSpatializerParameterSupported()) {
        uint32_t command = 0;
        if (!param.validateParamValueSize(sizeof(uint32_t), sizeof(int8_t)) ||
            OK != param.readFromParameter(&command)) {
            ALOGE("%s %d invalid param %s", __func__, __LINE__, param.toString().c_str());
            return BAD_VALUE;
        }

        switch (command) {
            case SPATIALIZER_PARAM_SUPPORTED_LEVELS: {
                const auto& range = getRange<Range::spatializer, Range::SpatializerRange>(
                        mDesc.capability, Spatializer::spatializationLevel);
                if (!range) {
                    return BAD_VALUE;
                }
                std::vector<Spatialization::Level> levels;
                for (const auto level : ::ndk::enum_range<Spatialization::Level>()) {
                    const auto spatializer =
                            Spatializer::make<Spatializer::spatializationLevel>(level);
                    if (spatializer >= range->min && spatializer <= range->max) {
                        levels.emplace_back(level);
                    }
                }
                const uint8_t num = levels.size();
                RETURN_STATUS_IF_ERROR(param.writeToValue(&num));
                for (const auto level : levels) {
                    RETURN_STATUS_IF_ERROR(param.writeToValue(&level));
                }
                return OK;
            }
            case SPATIALIZER_PARAM_LEVEL: {
                Parameter aidlParam;
                Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag,
                                                              Spatializer::spatializationLevel);
                RETURN_STATUS_IF_ERROR(
                        statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam)));
                const auto level = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD(
                        aidlParam, Spatializer, spatializer, Spatializer::spatializationLevel,
                        Spatialization::Level));
                return param.writeToValue(&level);
            }
            case SPATIALIZER_PARAM_HEADTRACKING_SUPPORTED: {
                const auto& range = getRange<Range::spatializer, Range::SpatializerRange>(
                        mDesc.capability, Spatializer::spatializationLevel);
                if (!range) {
                    ALOGE("%s %d: range not defined for spatializationLevel", __func__, __LINE__);
                    return BAD_VALUE;
                }
                const auto& nonSupport = Spatializer::make<Spatializer::spatializationLevel>(
                        Spatialization::Level::NONE);
                const bool support = (range->min > range->max ||
                                         (range->min == nonSupport && range->max == nonSupport))
                                                ? false
                                                : true;
                return param.writeToValue(&support);
            }
            case SPATIALIZER_PARAM_HEADTRACKING_MODE: {
                Parameter aidlParam;
                Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag,
                                                Spatializer::headTrackingMode);
                RETURN_STATUS_IF_ERROR(
                        statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam)));
                const auto mode = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD(
                        aidlParam, Spatializer, spatializer, Spatializer::headTrackingMode,
                        HeadTracking::Mode));
                return param.writeToValue(&mode);
            }
            case SPATIALIZER_PARAM_SUPPORTED_CHANNEL_MASKS: {
                Parameter aidlParam;
                Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag,
                                                              Spatializer::supportedChannelLayout);
                RETURN_STATUS_IF_ERROR(
                        statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam)));
                const auto& supportedLayouts = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD(
                        aidlParam, Spatializer, spatializer, Spatializer::supportedChannelLayout,
                        std::vector<AudioChannelLayout>));
                // audio_channel_mask_t is uint32_t enum, write number in 32bit
                const uint32_t num = supportedLayouts.size();
                RETURN_STATUS_IF_ERROR(param.writeToValue(&num));
                for (const auto& layout : supportedLayouts) {
                    audio_channel_mask_t mask = VALUE_OR_RETURN_STATUS(
                            ::aidl::android::aidl2legacy_AudioChannelLayout_audio_channel_mask_t(
                                    layout, false /* isInput */));
                    RETURN_STATUS_IF_ERROR(param.writeToValue(&mask));
                }
                return OK;
            }
            case SPATIALIZER_PARAM_SUPPORTED_SPATIALIZATION_MODES: {
                const auto& range = getRange<Range::spatializer, Range::SpatializerRange>(
                        mDesc.capability, Spatializer::spatializationMode);
                if (!range) {
                    return BAD_VALUE;
                }
                std::vector<Spatialization::Mode> modes;
                for (const auto mode : ::ndk::enum_range<Spatialization::Mode>()) {
                    if (const auto spatializer =
                                Spatializer::make<Spatializer::spatializationMode>(mode);
                        spatializer >= range->min && spatializer <= range->max) {
                        modes.emplace_back(mode);
                    }
                }
                const uint8_t num = modes.size();
                RETURN_STATUS_IF_ERROR(param.writeToValue(&num));
                for (const auto mode : modes) {
                    RETURN_STATUS_IF_ERROR(param.writeToValue(&mode));
                }
                return OK;
            }
            case SPATIALIZER_PARAM_SUPPORTED_HEADTRACKING_CONNECTION: {
                const auto& range = getRange<Range::spatializer, Range::SpatializerRange>(
                        mDesc.capability, Spatializer::headTrackingConnectionMode);
                if (!range) {
                    return BAD_VALUE;
                }
                std::vector<HeadTracking::ConnectionMode> modes;
                for (const auto mode : ::ndk::enum_range<HeadTracking::ConnectionMode>()) {
                    if (const auto spatializer =
                                Spatializer::make<Spatializer::headTrackingConnectionMode>(mode);
                        spatializer < range->min || spatializer > range->max) {
                        modes.emplace_back(mode);
                    }
                }
                const uint8_t num = modes.size();
                RETURN_STATUS_IF_ERROR(param.writeToValue(&num));
                for (const auto mode : modes) {
                    RETURN_STATUS_IF_ERROR(param.writeToValue(&mode));
                }
                return OK;
            }
            case SPATIALIZER_PARAM_HEADTRACKING_CONNECTION: {
                status_t status = OK;
                Parameter aidlParam;
                Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID(
                        Spatializer, spatializerTag, Spatializer::headTrackingConnectionMode);
                RETURN_STATUS_IF_ERROR(
                        statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam)));
                const auto mode = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD(
                        aidlParam, Spatializer, spatializer,
                        Spatializer::headTrackingConnectionMode, HeadTracking::ConnectionMode));

                id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag,
                                                Spatializer::headTrackingSensorId);
                RETURN_STATUS_IF_ERROR(
                        statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam)));
                const auto sensorId = VALUE_OR_RETURN_STATUS(
                        GET_PARAMETER_SPECIFIC_FIELD(aidlParam, Spatializer, spatializer,
                                                     Spatializer::headTrackingSensorId, int32_t));
                uint32_t modeInt32 = static_cast<int32_t>(mode);
                if (status = param.writeToValue(&modeInt32); status != OK) {
                    ALOGW("%s %d: write mode %s to value failed %d", __func__, __LINE__,
                          toString(mode).c_str(), status);
                    return status;
                }
                if (status = param.writeToValue(&sensorId); status != OK) {
                    ALOGW("%s %d: write sensorId %d to value failed %d", __func__, __LINE__,
                          sensorId, status);
                    return status;
                }
                return OK;
            }
            default: {
                VENDOR_EXTENSION_GET_AND_RETURN(Spatializer, spatializer, param);
            }
        }
    } else {
        Parameter aidlParam;
        DefaultExtension defaultExt;
        // read parameters into DefaultExtension vector<uint8_t>
        defaultExt.bytes.resize(param.getParameterSize());
        if (OK != param.readFromParameter(defaultExt.bytes.data(), param.getParameterSize())) {
            ALOGE("%s %d invalid param %s", __func__, __LINE__, param.toString().c_str());
            param.setStatus(BAD_VALUE);
            return BAD_VALUE;
        }

        VendorExtension idTag;
        idTag.extension.setParcelable(defaultExt);
        Parameter::Id id = UNION_MAKE(Parameter::Id, vendorEffectTag, idTag);
        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam)));
        // copy the AIDL extension data back to effect_param_t
        return VALUE_OR_RETURN_STATUS(
                ::aidl::android::aidl2legacy_Parameter_EffectParameterWriter(aidlParam, param));
    }
}

} // namespace effect
} // namespace android
