/*
 * Copyright 2016 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.
 */

#ifndef AAUDIO_AUDIOSTREAM_H
#define AAUDIO_AUDIOSTREAM_H

#include <atomic>
#include <mutex>
#include <stdint.h>
#include <aaudio/AAudio.h>

#include "utility/AAudioUtilities.h"
#include "utility/MonotonicCounter.h"

namespace aaudio {

typedef void *(*aaudio_audio_thread_proc_t)(void *);

class AudioStreamBuilder;

/**
 * AAudio audio stream.
 */
class AudioStream {
public:

    AudioStream();

    virtual ~AudioStream();


    // =========== Begin ABSTRACT methods ===========================

    /* Asynchronous requests.
     * Use waitForStateChange() to wait for completion.
     */
    virtual aaudio_result_t requestStart() = 0;
    virtual aaudio_result_t requestPause() = 0;
    virtual aaudio_result_t requestFlush() = 0;
    virtual aaudio_result_t requestStop() = 0;

    virtual aaudio_result_t getTimestamp(clockid_t clockId,
                                       int64_t *framePosition,
                                       int64_t *timeNanoseconds) = 0;


    /**
     * Update state while in the middle of waitForStateChange()
     * @return
     */
    virtual aaudio_result_t updateStateWhileWaiting() = 0;


    // =========== End ABSTRACT methods ===========================

    virtual aaudio_result_t waitForStateChange(aaudio_stream_state_t currentState,
                                               aaudio_stream_state_t *nextState,
                                               int64_t timeoutNanoseconds);

    /**
     * Open the stream using the parameters in the builder.
     * Allocate the necessary resources.
     */
    virtual aaudio_result_t open(const AudioStreamBuilder& builder);

    /**
     * Close the stream and deallocate any resources from the open() call.
     * It is safe to call close() multiple times.
     */
    virtual aaudio_result_t close() {
        return AAUDIO_OK;
    }

    virtual aaudio_result_t setBufferSize(int32_t requestedFrames) {
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

    virtual aaudio_result_t createThread(int64_t periodNanoseconds,
                                       aaudio_audio_thread_proc_t threadProc,
                                       void *threadArg);

    virtual aaudio_result_t joinThread(void **returnArg, int64_t timeoutNanoseconds);

    virtual aaudio_result_t registerThread() {
        return AAUDIO_OK;
    }

    virtual aaudio_result_t unregisterThread() {
        return AAUDIO_OK;
    }

    /**
     * Internal function used to call the audio thread passed by the user.
     * It is unfortunately public because it needs to be called by a static 'C' function.
     */
    void* wrapUserThread();

    // ============== Queries ===========================

    aaudio_stream_state_t getState() const {
        return mState;
    }

    virtual int32_t getBufferSize() const {
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

    virtual int32_t getBufferCapacity() const {
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

    virtual int32_t getFramesPerBurst() const {
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

    virtual int32_t getXRunCount() const {
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

    bool isPlaying() const {
        return mState == AAUDIO_STREAM_STATE_STARTING || mState == AAUDIO_STREAM_STATE_STARTED;
    }

    virtual bool isMMap() {
        return false;
    }

    aaudio_result_t getSampleRate() const {
        return mSampleRate;
    }

    aaudio_audio_format_t getFormat()  const {
        return mFormat;
    }

    aaudio_result_t getSamplesPerFrame() const {
        return mSamplesPerFrame;
    }

    virtual int32_t getPerformanceMode() const {
        return mPerformanceMode;
    }

    void setPerformanceMode(aaudio_performance_mode_t performanceMode) {
        mPerformanceMode = performanceMode;
    }

    int32_t getDeviceId() const {
        return mDeviceId;
    }

    aaudio_sharing_mode_t getSharingMode() const {
        return mSharingMode;
    }

    bool isSharingModeMatchRequired() const {
        return mSharingModeMatchRequired;
    }

    aaudio_direction_t getDirection() const {
        return mDirection;
    }

    /**
     * This is only valid after setSamplesPerFrame() and setFormat() have been called.
     */
    int32_t getBytesPerFrame() const {
        return mSamplesPerFrame * getBytesPerSample();
    }

    /**
     * This is only valid after setFormat() has been called.
     */
    int32_t getBytesPerSample() const {
        return AAudioConvert_formatToSizeInBytes(mFormat);
    }

    virtual int64_t getFramesWritten() {
        return mFramesWritten.get();
    }

    virtual int64_t getFramesRead() {
        return mFramesRead.get();
    }

    AAudioStream_dataCallback getDataCallbackProc() const {
        return mDataCallbackProc;
    }
    AAudioStream_errorCallback getErrorCallbackProc() const {
        return mErrorCallbackProc;
    }

    void *getDataCallbackUserData() const {
        return mDataCallbackUserData;
    }
    void *getErrorCallbackUserData() const {
        return mErrorCallbackUserData;
    }

    int32_t getFramesPerDataCallback() const {
        return mFramesPerDataCallback;
    }

    bool isDataCallbackActive() {
        return (mDataCallbackProc != nullptr) && isPlaying();
    }

    // ============== I/O ===========================
    // A Stream will only implement read() or write() depending on its direction.
    virtual aaudio_result_t write(const void *buffer,
                             int32_t numFrames,
                             int64_t timeoutNanoseconds) {
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

    virtual aaudio_result_t read(void *buffer,
                            int32_t numFrames,
                            int64_t timeoutNanoseconds) {
        return AAUDIO_ERROR_UNIMPLEMENTED;
    }

protected:

    virtual int64_t incrementFramesWritten(int32_t frames) {
        return mFramesWritten.increment(frames);
    }

    virtual int64_t incrementFramesRead(int32_t frames) {
        return mFramesRead.increment(frames);
    }

    /**
     * This should not be called after the open() call.
     */
    void setSampleRate(int32_t sampleRate) {
        mSampleRate = sampleRate;
    }

    /**
     * This should not be called after the open() call.
     */
    void setSamplesPerFrame(int32_t samplesPerFrame) {
        mSamplesPerFrame = samplesPerFrame;
    }

    /**
     * This should not be called after the open() call.
     */
    void setSharingMode(aaudio_sharing_mode_t sharingMode) {
        mSharingMode = sharingMode;
    }

    /**
     * This should not be called after the open() call.
     */
    void setFormat(aaudio_audio_format_t format) {
        mFormat = format;
    }

    void setState(aaudio_stream_state_t state) {
        mState = state;
    }

    void setDeviceId(int32_t deviceId) {
        mDeviceId = deviceId;
    }

    std::mutex           mStreamMutex;

    std::atomic<bool>    mCallbackEnabled;

protected:
    MonotonicCounter     mFramesWritten;
    MonotonicCounter     mFramesRead;

    void setPeriodNanoseconds(int64_t periodNanoseconds) {
        mPeriodNanoseconds.store(periodNanoseconds, std::memory_order_release);
    }

    int64_t getPeriodNanoseconds() {
        return mPeriodNanoseconds.load(std::memory_order_acquire);
    }

private:
    // These do not change after open().
    int32_t                mSamplesPerFrame = AAUDIO_UNSPECIFIED;
    int32_t                mSampleRate = AAUDIO_UNSPECIFIED;
    int32_t                mDeviceId = AAUDIO_UNSPECIFIED;
    aaudio_sharing_mode_t  mSharingMode = AAUDIO_SHARING_MODE_SHARED;
    bool                   mSharingModeMatchRequired = false; // must match sharing mode requested
    aaudio_audio_format_t  mFormat = AAUDIO_FORMAT_UNSPECIFIED;
    aaudio_direction_t     mDirection = AAUDIO_DIRECTION_OUTPUT;
    aaudio_stream_state_t  mState = AAUDIO_STREAM_STATE_UNINITIALIZED;

    aaudio_performance_mode_t mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE;

    // callback ----------------------------------

    AAudioStream_dataCallback   mDataCallbackProc = nullptr;  // external callback functions
    void                       *mDataCallbackUserData = nullptr;
    int32_t                     mFramesPerDataCallback = AAUDIO_UNSPECIFIED; // frames

    AAudioStream_errorCallback  mErrorCallbackProc = nullptr;
    void                       *mErrorCallbackUserData = nullptr;

    // background thread ----------------------------------
    bool                   mHasThread = false;
    pthread_t              mThread; // initialized in constructor

    // These are set by the application thread and then read by the audio pthread.
    std::atomic<int64_t>   mPeriodNanoseconds; // for tuning SCHED_FIFO threads
    // TODO make atomic?
    aaudio_audio_thread_proc_t mThreadProc = nullptr;
    void*                  mThreadArg = nullptr;
    aaudio_result_t        mThreadRegistrationResult = AAUDIO_OK;


};

} /* namespace aaudio */

#endif /* AAUDIO_AUDIOSTREAM_H */
