/*
 * Copyright 2019 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 <memory>

#include "OboeDebug.h"
#include "FilterAudioStream.h"

using namespace oboe;
using namespace flowgraph;

// Output callback uses FixedBlockReader::read()
//                <= SourceFloatCaller::onProcess()
//                <=== DataConversionFlowGraph::read()
//                <== FilterAudioStream::onAudioReady()
//
// Output blocking uses no block adapter because AAudio can accept
// writes of any size. It uses DataConversionFlowGraph::read() <== FilterAudioStream::write() <= app
//
// Input callback uses FixedBlockWriter::write()
//                <= DataConversionFlowGraph::write()
//                <= FilterAudioStream::onAudioReady()
//
// Input blocking uses FixedBlockReader::read() // TODO may not need block adapter
//                <= SourceFloatCaller::onProcess()
//                <=== SinkFloat::read()
//                <= DataConversionFlowGraph::read()
//                <== FilterAudioStream::read()
//                <= app

Result FilterAudioStream::configureFlowGraph() {
    mFlowGraph = std::make_unique<DataConversionFlowGraph>();
    bool isOutput = getDirection() == Direction::Output;

    AudioStream *sourceStream =  isOutput ? this : mChildStream.get();
    AudioStream *sinkStream =  isOutput ? mChildStream.get() : this;

    mRateScaler = ((double) sourceStream->getSampleRate()) / sinkStream->getSampleRate();

    return mFlowGraph->configure(sourceStream, sinkStream);
}

// Put the data to be written at the source end of the flowgraph.
// Then read (pull) the data from the flowgraph and write it to the
// child stream.
ResultWithValue<int32_t> FilterAudioStream::write(const void *buffer,
                               int32_t numFrames,
                               int64_t timeoutNanoseconds) {
    int32_t framesWritten = 0;
    mFlowGraph->setSource(buffer, numFrames);
    while (true) {
        int32_t numRead = mFlowGraph->read(mBlockingBuffer.get(),
                getFramesPerBurst(),
                timeoutNanoseconds);
        if (numRead < 0) {
            return ResultWithValue<int32_t>::createBasedOnSign(numRead);
        }
        if (numRead == 0) {
            break; // finished processing the source buffer
        }
        auto writeResult = mChildStream->write(mBlockingBuffer.get(),
                                               numRead,
                                               timeoutNanoseconds);
        if (!writeResult) {
            return writeResult;
        }
        framesWritten += writeResult.value();
    }
    return ResultWithValue<int32_t>::createBasedOnSign(framesWritten);
}

// Read (pull) the data we want from the sink end of the flowgraph.
// The necessary data will be read from the child stream using a flowgraph callback.
ResultWithValue<int32_t> FilterAudioStream::read(void *buffer,
                                                  int32_t numFrames,
                                                  int64_t timeoutNanoseconds) {
    int32_t framesRead = mFlowGraph->read(buffer, numFrames, timeoutNanoseconds);
    return ResultWithValue<int32_t>::createBasedOnSign(framesRead);
}

DataCallbackResult FilterAudioStream::onAudioReady(AudioStream *oboeStream,
                                void *audioData,
                                int32_t numFrames) {
    int32_t framesProcessed;
    if (oboeStream->getDirection() == Direction::Output) {
        framesProcessed = mFlowGraph->read(audioData, numFrames, 0 /* timeout */);
    } else {
        framesProcessed = mFlowGraph->write(audioData, numFrames);
    }
    return (framesProcessed < numFrames)
           ? DataCallbackResult::Stop
           : mFlowGraph->getDataCallbackResult();
}
