/* Copyright 2015 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 <sys/types.h>
#include <cassert>
#include <android/log.h>

#include "common/OboeDebug.h"
#include "oboe/AudioClock.h"
#include "oboe/AudioStream.h"
#include "oboe/AudioStreamBuilder.h"
#include "EngineOpenSLES.h"
#include "AudioStreamOpenSLES.h"
#include "OpenSLESUtilities.h"

using namespace oboe;

AudioStreamOpenSLES::AudioStreamOpenSLES(const AudioStreamBuilder &builder)
    : AudioStreamBuffered(builder) {
    // OpenSL ES does not support device IDs. So overwrite value from builder.
    mDeviceIds.clear();
    // OpenSL ES does not support session IDs. So overwrite value from builder.
    mSessionId = SessionId::None;
}

static constexpr int32_t   kHighLatencyBufferSizeMillis = 20; // typical Android period
static constexpr SLuint32  kAudioChannelCountMax = 30; // TODO Why 30?
static constexpr SLuint32  SL_ANDROID_UNKNOWN_CHANNELMASK  = 0; // Matches name used internally.

SLuint32 AudioStreamOpenSLES::channelCountToChannelMaskDefault(int channelCount) const {
    if (channelCount > kAudioChannelCountMax) {
        return SL_ANDROID_UNKNOWN_CHANNELMASK;
    }

    SLuint32 bitfield = (1 << channelCount) - 1;

    // Check for OS at run-time.
    if(getSdkVersion() >= __ANDROID_API_N__) {
        return SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(bitfield);
    }

    // Indexed channels masks were added in N.
    // For before N, the best we can do is use a positional channel mask.
    return bitfield;
}

static bool s_isLittleEndian() {
    static uint32_t value = 1;
    return (*reinterpret_cast<uint8_t *>(&value) == 1);  // Does address point to LSB?
}

SLuint32 AudioStreamOpenSLES::getDefaultByteOrder() {
    return s_isLittleEndian() ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
}

Result AudioStreamOpenSLES::open() {
#ifndef OBOE_SUPPRESS_LOG_SPAM
    LOGI("AudioStreamOpenSLES::open() chans=%d, rate=%d", mChannelCount, mSampleRate);
#endif

    // OpenSL ES only supports I16 and Float
    if (mFormat != AudioFormat::I16 && mFormat != AudioFormat::Float) {
        LOGW("%s() Android's OpenSL ES implementation only supports I16 and Float. Format: %s",
             __func__, oboe::convertToText(mFormat));
        return Result::ErrorInvalidFormat;
    }

    SLresult result = EngineOpenSLES::getInstance().open();
    if (SL_RESULT_SUCCESS != result) {
        return Result::ErrorInternal;
    }

    Result oboeResult = AudioStreamBuffered::open();
    if (oboeResult != Result::OK) {
        EngineOpenSLES::getInstance().close();
        return oboeResult;
    }
    // Convert to defaults if UNSPECIFIED
    if (mSampleRate == kUnspecified) {
        mSampleRate = DefaultStreamValues::SampleRate;
    }
    if (mChannelCount == kUnspecified) {
        mChannelCount = DefaultStreamValues::ChannelCount;
    }
    if (mContentType == kUnspecified) {
        mContentType = ContentType::Music;
    }
    if (static_cast<const int32_t>(mUsage) == kUnspecified) {
        mUsage = Usage::Media;
    }

    mSharingMode = SharingMode::Shared;

    return Result::OK;
}


SLresult AudioStreamOpenSLES::finishCommonOpen(SLAndroidConfigurationItf configItf) {
    // Setting privacy sensitive mode and allowed capture policy are not supported for OpenSL ES.
    mPrivacySensitiveMode = PrivacySensitiveMode::Unspecified;
    mAllowedCapturePolicy = AllowedCapturePolicy::Unspecified;

    // Spatialization Behavior is not supported for OpenSL ES.
    mSpatializationBehavior = SpatializationBehavior::Never;

    SLresult result = registerBufferQueueCallback();
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }

    result = updateStreamParameters(configItf);
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }

    Result oboeResult = configureBufferSizes(mSampleRate);
    if (Result::OK != oboeResult) {
        return (SLresult) oboeResult;
    }

    allocateFifo();

    calculateDefaultDelayBeforeCloseMillis();

    return SL_RESULT_SUCCESS;
}

static int32_t roundUpDivideByN(int32_t x, int32_t n) {
    return (x + n - 1) / n;
}

int32_t AudioStreamOpenSLES::calculateOptimalBufferQueueLength() {
    int32_t queueLength = kBufferQueueLengthDefault;
    int32_t likelyFramesPerBurst = estimateNativeFramesPerBurst();
    int32_t minCapacity = mBufferCapacityInFrames; // specified by app or zero
    // The buffer capacity needs to be at least twice the size of the requested callbackSize
    // so that we can have double buffering.
    minCapacity = std::max(minCapacity, kDoubleBufferCount * mFramesPerCallback);
    if (minCapacity > 0) {
        int32_t queueLengthFromCapacity = roundUpDivideByN(minCapacity, likelyFramesPerBurst);
        queueLength = std::max(queueLength, queueLengthFromCapacity);
    }
    queueLength = std::min(queueLength, kBufferQueueLengthMax); // clip to max
    // TODO Investigate the effect of queueLength on latency for normal streams. (not low latency)
    return queueLength;
}

/**
 * The best information we have is if DefaultStreamValues::FramesPerBurst
 * was set by the app based on AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER.
 * Without that we just have to guess.
 * @return
 */
int32_t AudioStreamOpenSLES::estimateNativeFramesPerBurst() {
    int32_t framesPerBurst = DefaultStreamValues::FramesPerBurst;
    LOGD("AudioStreamOpenSLES:%s() DefaultStreamValues::FramesPerBurst = %d",
            __func__, DefaultStreamValues::FramesPerBurst);
    framesPerBurst = std::max(framesPerBurst, 16);
    // Calculate the size of a fixed duration high latency buffer based on sample rate.
    // Estimate sample based on default options in order of priority.
    int32_t sampleRate = 48000;
    sampleRate = (DefaultStreamValues::SampleRate > 0)
            ? DefaultStreamValues::SampleRate : sampleRate;
    sampleRate = (mSampleRate > 0) ? mSampleRate : sampleRate;
    int32_t framesPerHighLatencyBuffer =
            (kHighLatencyBufferSizeMillis * sampleRate) / kMillisPerSecond;
    // For high latency streams, use a larger buffer size.
    // Performance Mode support was added in N_MR1 (7.1)
    if (getSdkVersion() >= __ANDROID_API_N_MR1__
            && mPerformanceMode != PerformanceMode::LowLatency
            && framesPerBurst < framesPerHighLatencyBuffer) {
        // Find a multiple of framesPerBurst >= framesPerHighLatencyBuffer.
        int32_t numBursts = roundUpDivideByN(framesPerHighLatencyBuffer, framesPerBurst);
        framesPerBurst *= numBursts;
        LOGD("AudioStreamOpenSLES:%s() NOT low latency, numBursts = %d, mSampleRate = %d, set framesPerBurst = %d",
             __func__, numBursts, mSampleRate, framesPerBurst);
    }
    return framesPerBurst;
}

Result AudioStreamOpenSLES::configureBufferSizes(int32_t sampleRate) {
    LOGD("AudioStreamOpenSLES:%s(%d) initial mFramesPerBurst = %d, mFramesPerCallback = %d",
            __func__, mSampleRate, mFramesPerBurst, mFramesPerCallback);
    mFramesPerBurst = estimateNativeFramesPerBurst();
    mFramesPerCallback = (mFramesPerCallback > 0) ? mFramesPerCallback : mFramesPerBurst;
    LOGD("AudioStreamOpenSLES:%s(%d) final mFramesPerBurst = %d, mFramesPerCallback = %d",
         __func__, mSampleRate, mFramesPerBurst, mFramesPerCallback);

    mBytesPerCallback = mFramesPerCallback * getBytesPerFrame();
    if (mBytesPerCallback <= 0) {
        LOGE("AudioStreamOpenSLES::open() bytesPerCallback < 0 = %d, bad format?",
             mBytesPerCallback);
        return Result::ErrorInvalidFormat; // causing bytesPerFrame == 0
    }

    for (int i = 0; i < mBufferQueueLength; ++i) {
        mCallbackBuffer[i] = std::make_unique<uint8_t[]>(mBytesPerCallback);
    }

    if (!usingFIFO()) {
        mBufferCapacityInFrames = mFramesPerBurst * mBufferQueueLength;
        // Check for overflow.
        if (mBufferCapacityInFrames <= 0) {
            mBufferCapacityInFrames = 0;
            LOGE("AudioStreamOpenSLES::open() numeric overflow because mFramesPerBurst = %d",
                 mFramesPerBurst);
            return Result::ErrorOutOfRange;
        }
        mBufferSizeInFrames = mBufferCapacityInFrames;
    }

    return Result::OK;
}

SLuint32 AudioStreamOpenSLES::convertPerformanceMode(PerformanceMode oboeMode) const {
    SLuint32 openslMode = SL_ANDROID_PERFORMANCE_NONE;
    switch(oboeMode) {
        case PerformanceMode::None:
            openslMode =  SL_ANDROID_PERFORMANCE_NONE;
            break;
        case PerformanceMode::LowLatency:
            openslMode =  (getSessionId() == SessionId::None) ?  SL_ANDROID_PERFORMANCE_LATENCY : SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
            break;
        case PerformanceMode::PowerSaving:
            openslMode =  SL_ANDROID_PERFORMANCE_POWER_SAVING;
            break;
        default:
            break;
    }
    return openslMode;
}

PerformanceMode AudioStreamOpenSLES::convertPerformanceMode(SLuint32 openslMode) const {
    PerformanceMode oboeMode = PerformanceMode::None;
    switch(openslMode) {
        case SL_ANDROID_PERFORMANCE_NONE:
            oboeMode =  PerformanceMode::None;
            break;
        case SL_ANDROID_PERFORMANCE_LATENCY:
        case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:
            oboeMode =  PerformanceMode::LowLatency;
            break;
        case SL_ANDROID_PERFORMANCE_POWER_SAVING:
            oboeMode =  PerformanceMode::PowerSaving;
            break;
        default:
            break;
    }
    return oboeMode;
}

void AudioStreamOpenSLES::logUnsupportedAttributes() {
    // Log unsupported attributes
    // only report if changed from the default

    // Device ID
    if (!mDeviceIds.empty()) {
        LOGW("Device ID [AudioStreamBuilder::setDeviceId()] "
             "is not supported on OpenSLES streams.");
    }
    // Sharing Mode
    if (mSharingMode != SharingMode::Shared) {
        LOGW("SharingMode [AudioStreamBuilder::setSharingMode()] "
             "is not supported on OpenSLES streams.");
    }
    // Performance Mode
    int sdkVersion = getSdkVersion();
    if (mPerformanceMode != PerformanceMode::None && sdkVersion < __ANDROID_API_N_MR1__) {
        LOGW("PerformanceMode [AudioStreamBuilder::setPerformanceMode()] "
             "is not supported on OpenSLES streams running on pre-Android N-MR1 versions.");
    }
    // Content Type
    if (static_cast<const int32_t>(mContentType) != kUnspecified) {
        LOGW("ContentType [AudioStreamBuilder::setContentType()] "
             "is not supported on OpenSLES streams.");
    }

    // Session Id
    if (mSessionId != SessionId::None) {
        LOGW("SessionId [AudioStreamBuilder::setSessionId()] "
             "is not supported on OpenSLES streams.");
    }

    // Privacy Sensitive Mode
    if (mPrivacySensitiveMode != PrivacySensitiveMode::Unspecified) {
        LOGW("PrivacySensitiveMode [AudioStreamBuilder::setPrivacySensitiveMode()] "
             "is not supported on OpenSLES streams.");
    }

    // Spatialization Behavior
    if (mSpatializationBehavior != SpatializationBehavior::Unspecified) {
        LOGW("SpatializationBehavior [AudioStreamBuilder::setSpatializationBehavior()] "
             "is not supported on OpenSLES streams.");
    }

    if (mIsContentSpatialized) {
        LOGW("Boolean [AudioStreamBuilder::setIsContentSpatialized()] "
             "is not supported on OpenSLES streams.");
    }

    // Allowed Capture Policy
    if (mAllowedCapturePolicy != AllowedCapturePolicy::Unspecified) {
        LOGW("AllowedCapturePolicy [AudioStreamBuilder::setAllowedCapturePolicy()] "
             "is not supported on OpenSLES streams.");
    }

    // Package Name
    if (!mPackageName.empty()) {
        LOGW("PackageName [AudioStreamBuilder::setPackageName()] "
             "is not supported on OpenSLES streams.");
    }

    // Attribution Tag
    if (!mAttributionTag.empty()) {
        LOGW("AttributionTag [AudioStreamBuilder::setAttributionTag()] "
             "is not supported on OpenSLES streams.");
    }
}

SLresult AudioStreamOpenSLES::configurePerformanceMode(SLAndroidConfigurationItf configItf) {

    if (configItf == nullptr) {
        LOGW("%s() called with NULL configuration", __func__);
        mPerformanceMode = PerformanceMode::None;
        return SL_RESULT_INTERNAL_ERROR;
    }
    if (getSdkVersion() < __ANDROID_API_N_MR1__) {
        LOGW("%s() not supported until N_MR1", __func__);
        mPerformanceMode = PerformanceMode::None;
        return SL_RESULT_SUCCESS;
    }

    SLresult result = SL_RESULT_SUCCESS;
    SLuint32 performanceMode = convertPerformanceMode(getPerformanceMode());
    result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
                                                     &performanceMode, sizeof(performanceMode));
    if (SL_RESULT_SUCCESS != result) {
        LOGW("SetConfiguration(PERFORMANCE_MODE, SL %u) returned %s",
             performanceMode, getSLErrStr(result));
        mPerformanceMode = PerformanceMode::None;
    }

    return result;
}

SLresult AudioStreamOpenSLES::updateStreamParameters(SLAndroidConfigurationItf configItf) {
    SLresult result = SL_RESULT_SUCCESS;
    if(getSdkVersion() >= __ANDROID_API_N_MR1__ && configItf != nullptr) {
        SLuint32 performanceMode = 0;
        SLuint32 performanceModeSize = sizeof(performanceMode);
        result = (*configItf)->GetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
                                                &performanceModeSize, &performanceMode);
        // A bug in GetConfiguration() before P caused a wrong result code to be returned.
        if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
            result = SL_RESULT_SUCCESS; // Ignore actual result before P.
        }

        if (SL_RESULT_SUCCESS != result) {
            LOGW("GetConfiguration(SL_ANDROID_KEY_PERFORMANCE_MODE) returned %d", result);
            mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None.
        } else {
            mPerformanceMode = convertPerformanceMode(performanceMode); // convert SL to Oboe mode
        }
    } else {
        mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None.
    }
    return result;
}

// This is called under mLock.
Result AudioStreamOpenSLES::close_l() {
    LOGD("AudioOutputStreamOpenSLES::%s() called", __func__);
    if (mState == StreamState::Closed) {
        return Result::ErrorClosed;
    }

    AudioStreamBuffered::close();

    onBeforeDestroy();

    // Mark as CLOSED before we unlock for the join.
    // This will prevent other threads from trying to close().
    setState(StreamState::Closed);

    SLObjectItf  tempObjectInterface = mObjectInterface;
    mObjectInterface = nullptr;
    if (tempObjectInterface != nullptr) {
        // Temporarily unlock so we can join() the callback thread.
        mLock.unlock();
        (*tempObjectInterface)->Destroy(tempObjectInterface); // Will join the callback!
        mLock.lock();
    }

    onAfterDestroy();

    mSimpleBufferQueueInterface = nullptr;
    EngineOpenSLES::getInstance().close();

    return Result::OK;
}

SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq) {
    SLresult result = (*bq)->Enqueue(
            bq, mCallbackBuffer[mCallbackBufferIndex].get(), mBytesPerCallback);
    mCallbackBufferIndex = (mCallbackBufferIndex + 1) % mBufferQueueLength;
    return result;
}

int32_t AudioStreamOpenSLES::getBufferDepth(SLAndroidSimpleBufferQueueItf bq) {
    SLAndroidSimpleBufferQueueState queueState;
    SLresult result = (*bq)->GetState(bq, &queueState);
    return (result == SL_RESULT_SUCCESS) ? queueState.count : -1;
}

bool AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) {
    bool shouldStopStream = false;
    // Ask the app callback to process the buffer.
    DataCallbackResult result =
            fireDataCallback(mCallbackBuffer[mCallbackBufferIndex].get(), mFramesPerCallback);
    if (result == DataCallbackResult::Continue) {
        // Pass the buffer to OpenSLES.
        SLresult enqueueResult = enqueueCallbackBuffer(bq);
        if (enqueueResult != SL_RESULT_SUCCESS) {
            LOGE("%s() returned %d", __func__, enqueueResult);
            shouldStopStream = true;
        }
        // Update Oboe client position with frames handled by the callback.
        if (getDirection() == Direction::Input) {
            mFramesRead += mFramesPerCallback;
        } else {
            mFramesWritten += mFramesPerCallback;
        }
    } else if (result == DataCallbackResult::Stop) {
        LOGD("Oboe callback returned Stop");
        shouldStopStream = true;
    } else {
        LOGW("Oboe callback returned unexpected value = %d", static_cast<int>(result));
        shouldStopStream = true;
    }
    if (shouldStopStream) {
        mCallbackBufferIndex = 0;
    }
    return shouldStopStream;
}

// This callback handler is called every time a buffer has been processed by OpenSL ES.
static void bqCallbackGlue(SLAndroidSimpleBufferQueueItf bq, void *context) {
    bool shouldStopStream = (reinterpret_cast<AudioStreamOpenSLES *>(context))
            ->processBufferCallback(bq);
    if (shouldStopStream) {
        (reinterpret_cast<AudioStreamOpenSLES *>(context))->requestStop();
    }
}

SLresult AudioStreamOpenSLES::registerBufferQueueCallback() {
    // The BufferQueue
    SLresult result = (*mObjectInterface)->GetInterface(mObjectInterface,
            EngineOpenSLES::getInstance().getIidAndroidSimpleBufferQueue(),
            &mSimpleBufferQueueInterface);
    if (SL_RESULT_SUCCESS != result) {
        LOGE("get buffer queue interface:%p result:%s",
             mSimpleBufferQueueInterface,
             getSLErrStr(result));
    } else {
        // Register the BufferQueue callback
        result = (*mSimpleBufferQueueInterface)->RegisterCallback(mSimpleBufferQueueInterface,
                                                                  bqCallbackGlue, this);
        if (SL_RESULT_SUCCESS != result) {
            LOGE("RegisterCallback result:%s", getSLErrStr(result));
        }
    }
    return result;
}

int64_t AudioStreamOpenSLES::getFramesProcessedByServer() {
    updateServiceFrameCounter();
    int64_t millis64 = mPositionMillis.get();
    int64_t framesProcessed = millis64 * getSampleRate() / kMillisPerSecond;
    return framesProcessed;
}

Result AudioStreamOpenSLES::waitForStateChange(StreamState currentState,
                                                     StreamState *nextState,
                                                     int64_t timeoutNanoseconds) {
    Result oboeResult = Result::ErrorTimeout;
    int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary
    int64_t timeLeftNanos = timeoutNanoseconds;

    while (true) {
        const StreamState state = getState(); // this does not require a lock
        if (nextState != nullptr) {
            *nextState = state;
        }
        if (currentState != state) { // state changed?
            oboeResult = Result::OK;
            break;
        }

        // Did we timeout or did user ask for non-blocking?
        if (timeLeftNanos <= 0) {
            break;
        }

        if (sleepTimeNanos > timeLeftNanos){
            sleepTimeNanos = timeLeftNanos;
        }
        AudioClock::sleepForNanos(sleepTimeNanos);
        timeLeftNanos -= sleepTimeNanos;
    }

    return oboeResult;
}
