/*
 * Copyright 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 <stdlib.h>
#include <aaudio/AAudioExtensions.h>

#include "common/OboeDebug.h"
#include "oboe/AudioClock.h"
#include "TestColdStartLatency.h"
#include "OboeTools.h"

using namespace oboe;

int32_t TestColdStartLatency::open(bool useInput, bool useLowLatency, bool useMmap, bool
        useExclusive) {

    mDataCallback = std::make_shared<MyDataCallback>();

    // Enable MMAP if needed
    bool wasMMapEnabled = AAudioExtensions::getInstance().isMMapEnabled();
    AAudioExtensions::getInstance().setMMapEnabled(useMmap);

    int64_t beginOpenNanos = AudioClock::getNanoseconds();

    AudioStreamBuilder builder;
    Result result = builder.setFormat(AudioFormat::Float)
            ->setPerformanceMode(useLowLatency ? PerformanceMode::LowLatency :
                    PerformanceMode::None)
            ->setDirection(useInput ? Direction::Input : Direction::Output)
            ->setChannelCount(kChannelCount)
            ->setDataCallback(mDataCallback)
            ->setSharingMode(useExclusive ? SharingMode::Exclusive : SharingMode::Shared)
            ->openStream(mStream);

    int64_t endOpenNanos = AudioClock::getNanoseconds();
    int64_t actualDurationNanos = endOpenNanos - beginOpenNanos;
    mOpenTimeMicros = actualDurationNanos / NANOS_PER_MICROSECOND;

    // Revert MMAP back to its previous state
    AAudioExtensions::getInstance().setMMapEnabled(wasMMapEnabled);

    mDeviceId = mStream->getDeviceId();

    return (int32_t) result;
}

int32_t TestColdStartLatency::start() {
    mBeginStartNanos = AudioClock::getNanoseconds();
    Result result = mStream->requestStart();
    int64_t endStartNanos = AudioClock::getNanoseconds();
    int64_t actualDurationNanos = endStartNanos - mBeginStartNanos;
    mStartTimeMicros = actualDurationNanos / NANOS_PER_MICROSECOND;
    return (int32_t) result;
}

int32_t TestColdStartLatency::close() {
    Result result1 = mStream->requestStop();
    Result result2 = mStream->close();
    return (int32_t)((result1 != Result::OK) ? result1 : result2);
}

int32_t TestColdStartLatency::getColdStartTimeMicros() {
    int64_t position;
    int64_t timestampNanos;
    if (mStream->getDirection() == Direction::Output) {
        auto result = mStream->getTimestamp(CLOCK_MONOTONIC);
        if (!result) {
            return -1; // ERROR
        }
        auto frameTimestamp = result.value();
        // Calculate the time that frame[0] would have been played by the speaker.
        position = frameTimestamp.position;
        timestampNanos = frameTimestamp.timestamp;
    } else {
        position = mStream->getFramesRead();
        timestampNanos = AudioClock::getNanoseconds();
    }
    double sampleRate = (double) mStream->getSampleRate();

    int64_t elapsedNanos = NANOS_PER_SECOND * (position / sampleRate);
    int64_t timeOfFrameZero = timestampNanos - elapsedNanos;
    int64_t coldStartLatencyNanos = timeOfFrameZero - mBeginStartNanos;
    return coldStartLatencyNanos / NANOS_PER_MICROSECOND;
}

// Callback that sleeps then touches the audio buffer.
DataCallbackResult TestColdStartLatency::MyDataCallback::onAudioReady(
        AudioStream *audioStream,
        void *audioData,
        int32_t numFrames) {
    float *floatData = (float *) audioData;
    const int numSamples = numFrames * kChannelCount;
    if (audioStream->getDirection() == Direction::Output) {
        // Fill mono buffer with a sine wave.
        for (int i = 0; i < numSamples; i++) {
            *floatData++ = sinf(mPhase) * 0.2f;
            if ((i % kChannelCount) == (kChannelCount - 1)) {
                mPhase += kPhaseIncrement;
                // Wrap the phase around in a circle.
                if (mPhase >= M_PI) mPhase -= 2 * M_PI;
            }
        }
    }
    return DataCallbackResult::Continue;
}
