/*
 * Copyright (C) 2018 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_TAG "AAudioFlowGraph"
//#define LOG_NDEBUG 0
#include <utils/Log.h>

#include "AAudioFlowGraph.h"

#include <flowgraph/Limiter.h>
#include <flowgraph/ManyToMultiConverter.h>
#include <flowgraph/MonoBlend.h>
#include <flowgraph/MonoToMultiConverter.h>
#include <flowgraph/MultiToManyConverter.h>
#include <flowgraph/RampLinear.h>
#include <flowgraph/SinkFloat.h>
#include <flowgraph/SinkI16.h>
#include <flowgraph/SinkI24.h>
#include <flowgraph/SinkI32.h>
#include <flowgraph/SinkI8_24.h>
#include <flowgraph/SourceFloat.h>
#include <flowgraph/SourceI16.h>
#include <flowgraph/SourceI24.h>
#include <flowgraph/SourceI32.h>
#include <flowgraph/SourceI8_24.h>

using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;

aaudio_result_t AAudioFlowGraph::configure(audio_format_t sourceFormat,
                          int32_t sourceChannelCount,
                          int32_t sourceSampleRate,
                          audio_format_t sinkFormat,
                          int32_t sinkChannelCount,
                          int32_t sinkSampleRate,
                          bool useMonoBlend,
                          bool useVolumeRamps,
                          float audioBalance,
                          aaudio::resampler::MultiChannelResampler::Quality resamplerQuality) {
    FlowGraphPortFloatOutput *lastOutput = nullptr;

    ALOGD("%s() source format = 0x%08x, channels = %d, sample rate = %d, "
          "sink format = 0x%08x, channels = %d, sample rate = %d, "
          "useMonoBlend = %d, audioBalance = %f, useVolumeRamps %d",
          __func__, sourceFormat, sourceChannelCount, sourceSampleRate, sinkFormat,
          sinkChannelCount, sinkSampleRate, useMonoBlend, audioBalance, useVolumeRamps);

    switch (sourceFormat) {
        case AUDIO_FORMAT_PCM_FLOAT:
            mSource = std::make_unique<SourceFloat>(sourceChannelCount);
            break;
        case AUDIO_FORMAT_PCM_16_BIT:
            mSource = std::make_unique<SourceI16>(sourceChannelCount);
            break;
        case AUDIO_FORMAT_PCM_24_BIT_PACKED:
            mSource = std::make_unique<SourceI24>(sourceChannelCount);
            break;
        case AUDIO_FORMAT_PCM_32_BIT:
            mSource = std::make_unique<SourceI32>(sourceChannelCount);
            break;
        case AUDIO_FORMAT_PCM_8_24_BIT:
            mSource = std::make_unique<SourceI8_24>(sourceChannelCount);
            break;
        default:
            ALOGE("%s() Unsupported source format = %d", __func__, sourceFormat);
            return AAUDIO_ERROR_UNIMPLEMENTED;
    }
    lastOutput = &mSource->output;

    if (useMonoBlend) {
        mMonoBlend = std::make_unique<MonoBlend>(sourceChannelCount);
        lastOutput->connect(&mMonoBlend->input);
        lastOutput = &mMonoBlend->output;
    }

    // For a pure float graph, there is chance that the data range may be very large.
    // So we should limit to a reasonable value that allows a little headroom.
    if (sourceFormat == AUDIO_FORMAT_PCM_FLOAT && sinkFormat == AUDIO_FORMAT_PCM_FLOAT) {
        mLimiter = std::make_unique<Limiter>(sourceChannelCount);
        lastOutput->connect(&mLimiter->input);
        lastOutput = &mLimiter->output;
    }

    if (sourceSampleRate != sinkSampleRate) {
        mResampler.reset(aaudio::resampler::MultiChannelResampler::make(sourceChannelCount,
                sourceSampleRate, sinkSampleRate, resamplerQuality));
        mRateConverter = std::make_unique<SampleRateConverter>(sourceChannelCount,
                                                               *mResampler);
        lastOutput->connect(&mRateConverter->input);
        lastOutput = &mRateConverter->output;
    }

    // Expand the number of channels if required.
    if (sourceChannelCount == 1 && sinkChannelCount > 1) {
        mChannelConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
        lastOutput->connect(&mChannelConverter->input);
        lastOutput = &mChannelConverter->output;
    } else if (sourceChannelCount != sinkChannelCount) {
        ALOGE("%s() Channel reduction not supported.", __func__);
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

    if (useVolumeRamps) {
        // Apply volume ramps to set the left/right audio balance and target volumes.
        // The signals will be decoupled, volume ramps will be applied, before the signals are
        // combined again.
        mMultiToManyConverter = std::make_unique<MultiToManyConverter>(sinkChannelCount);
        mManyToMultiConverter = std::make_unique<ManyToMultiConverter>(sinkChannelCount);
        lastOutput->connect(&mMultiToManyConverter->input);
        for (int i = 0; i < sinkChannelCount; i++) {
            mVolumeRamps.emplace_back(std::make_unique<RampLinear>(1));
            mPanningVolumes.emplace_back(1.0f);
            lastOutput = mMultiToManyConverter->outputs[i].get();
            lastOutput->connect(&(mVolumeRamps[i].get()->input));
            lastOutput = &(mVolumeRamps[i].get()->output);
            lastOutput->connect(mManyToMultiConverter->inputs[i].get());
        }
        lastOutput = &mManyToMultiConverter->output;
        setAudioBalance(audioBalance);
    }

    switch (sinkFormat) {
        case AUDIO_FORMAT_PCM_FLOAT:
            mSink = std::make_unique<SinkFloat>(sinkChannelCount);
            break;
        case AUDIO_FORMAT_PCM_16_BIT:
            mSink = std::make_unique<SinkI16>(sinkChannelCount);
            break;
        case AUDIO_FORMAT_PCM_24_BIT_PACKED:
            mSink = std::make_unique<SinkI24>(sinkChannelCount);
            break;
        case AUDIO_FORMAT_PCM_32_BIT:
            mSink = std::make_unique<SinkI32>(sinkChannelCount);
            break;
        case AUDIO_FORMAT_PCM_8_24_BIT:
            mSink = std::make_unique<SinkI8_24>(sinkChannelCount);
            break;
        default:
            ALOGE("%s() Unsupported sink format = %d", __func__, sinkFormat);
            return AAUDIO_ERROR_UNIMPLEMENTED;
    }
    lastOutput->connect(&mSink->input);

    return AAUDIO_OK;
}

int32_t AAudioFlowGraph::pull(void *destination, int32_t targetFramesToRead) {
    return mSink->read(destination, targetFramesToRead);
}

int32_t AAudioFlowGraph::process(const void *source, int32_t numFramesToWrite, void *destination,
                    int32_t targetFramesToRead) {
    mSource->setData(source, numFramesToWrite);
    return mSink->read(destination, targetFramesToRead);
}

/**
 * @param volume between 0.0 and 1.0
 */
void AAudioFlowGraph::setTargetVolume(float volume) {
    for (int i = 0; i < mVolumeRamps.size(); i++) {
        mVolumeRamps[i]->setTarget(volume * mPanningVolumes[i]);
    }
    mTargetVolume = volume;
}

/**
 * @param audioBalance between -1.0 and 1.0
 */
void AAudioFlowGraph::setAudioBalance(float audioBalance) {
    if (mPanningVolumes.size() >= 2) {
        float leftMultiplier = 0;
        float rightMultiplier = 0;
        mBalance.computeStereoBalance(audioBalance, &leftMultiplier, &rightMultiplier);
        mPanningVolumes[0] = leftMultiplier;
        mPanningVolumes[1] = rightMultiplier;
        mVolumeRamps[0]->setTarget(mTargetVolume * leftMultiplier);
        mVolumeRamps[1]->setTarget(mTargetVolume * rightMultiplier);
    }
}

/**
 * @param numFrames to slowly adjust for volume changes
 */
void AAudioFlowGraph::setRampLengthInFrames(int32_t numFrames) {
    for (auto& ramp : mVolumeRamps) {
        ramp->setLengthInFrames(numFrames);
    }
}
