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

#ifndef ANDROID_MEDIAPLAYER2AUDIOOUTPUT_H
#define ANDROID_MEDIAPLAYER2AUDIOOUTPUT_H

#include <mediaplayer2/MediaPlayer2Interface.h>
#include <mediaplayer2/JAudioTrack.h>
#include <mediaplayer2/JObjectHolder.h>

#include <utility>
#include <utils/String16.h>
#include <utils/Vector.h>

#include "jni.h"

namespace android {

class AudioTrack;

class MediaPlayer2AudioOutput : public MediaPlayer2Interface::AudioSink
{
    class CallbackData;

public:
    MediaPlayer2AudioOutput(int32_t sessionId,
                            uid_t uid,
                            int pid,
                            const jobject attributes);
    virtual ~MediaPlayer2AudioOutput();

    virtual bool ready() const {
        return mJAudioTrack != nullptr;
    }
    virtual ssize_t bufferSize() const;
    virtual ssize_t frameCount() const;
    virtual ssize_t channelCount() const;
    virtual ssize_t frameSize() const;
    virtual uint32_t latency() const;
    virtual float msecsPerFrame() const;
    virtual status_t getPosition(uint32_t *position) const;
    virtual status_t getTimestamp(AudioTimestamp &ts) const;
    virtual int64_t getPlayedOutDurationUs(int64_t nowUs) const;
    virtual status_t getFramesWritten(uint32_t *frameswritten) const;
    virtual int32_t getSessionId() const;
    virtual void setSessionId(const int32_t id);
    virtual uint32_t getSampleRate() const;
    virtual int64_t getBufferDurationInUs() const;

    virtual status_t open(
            uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask,
            audio_format_t format,
            AudioCallback cb, void *cookie,
            audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_NONE,
            const audio_offload_info_t *offloadInfo = NULL,
            uint32_t suggestedFrameCount = 0);

    virtual status_t start();
    virtual ssize_t write(const void* buffer, size_t size, bool blocking = true);
    virtual void stop();
    virtual void flush();
    virtual void pause();
    virtual void close();
    void setAudioAttributes(const jobject attributes);
    virtual audio_stream_type_t getAudioStreamType() const;

    void setVolume(float volume);
    virtual status_t setPlaybackRate(const AudioPlaybackRate& rate);
    virtual status_t getPlaybackRate(AudioPlaybackRate* rate /* nonnull */);

    status_t setAuxEffectSendLevel(float level);
    status_t attachAuxEffect(int effectId);
    virtual status_t dump(int fd, const Vector<String16>& args) const;

    static bool isOnEmulator();
    static int getMinBufferCount();
    virtual bool needsTrailingPadding() {
        return true;
        // TODO: return correct value.
        //return mNextOutput == NULL;
    }
    // AudioRouting
    virtual status_t setPreferredDevice(jobject device);
    virtual jobject getRoutedDevice();
    virtual status_t addAudioDeviceCallback(jobject routingDelegate);
    virtual status_t removeAudioDeviceCallback(jobject listener);

private:
    static void setMinBufferCount();
    static void CallbackWrapper(int event, void *me, void *info);
    void deleteRecycledTrack_l();
    void close_l();
    status_t updateTrack_l();

    sp<JAudioTrack>         mJAudioTrack;
    AudioCallback           mCallback;
    void *                  mCallbackCookie;
    CallbackData *          mCallbackData;
    sp<JObjectHolder>       mAttributes;
    float                   mVolume;
    AudioPlaybackRate       mPlaybackRate;
    uint32_t                mSampleRateHz; // sample rate of the content, as set in open()
    float                   mMsecsPerFrame;
    size_t                  mFrameSize;
    int32_t                 mSessionId;
    uid_t                   mUid;
    int                     mPid;
    float                   mSendLevel;
    int                     mAuxEffectId;
    audio_output_flags_t    mFlags;
    sp<JObjectHolder>       mPreferredDevice;
    mutable Mutex           mLock;

    // <listener, routingDelegate>
    Vector<std::pair<sp<JObjectHolder>, sp<JObjectHolder>>> mRoutingDelegates;

    // static variables below not protected by mutex
    static bool             mIsOnEmulator;
    static int              mMinBufferCount;  // 12 for emulator; otherwise 4

    // CallbackData is what is passed to the AudioTrack as the "user" data.
    // We need to be able to target this to a different Output on the fly,
    // so we can't use the Output itself for this.
    class CallbackData {
        friend MediaPlayer2AudioOutput;
    public:
        explicit CallbackData(MediaPlayer2AudioOutput *cookie) {
            mData = cookie;
            mSwitching = false;
        }
        MediaPlayer2AudioOutput *getOutput() const {
            return mData;
        }
        void setOutput(MediaPlayer2AudioOutput* newcookie) {
            mData = newcookie;
        }
        // lock/unlock are used by the callback before accessing the payload of this object
        void lock() const {
            mLock.lock();
        }
        void unlock() const {
            mLock.unlock();
        }

        // tryBeginTrackSwitch/endTrackSwitch are used when the CallbackData is handed over
        // to the next sink.

        // tryBeginTrackSwitch() returns true only if it obtains the lock.
        bool tryBeginTrackSwitch() {
            LOG_ALWAYS_FATAL_IF(mSwitching, "tryBeginTrackSwitch() already called");
            if (mLock.tryLock() != OK) {
                return false;
            }
            mSwitching = true;
            return true;
        }
        void endTrackSwitch() {
            if (mSwitching) {
                mLock.unlock();
            }
            mSwitching = false;
        }

    private:
        MediaPlayer2AudioOutput *mData;
        mutable Mutex mLock; // a recursive mutex might make this unnecessary.
        bool mSwitching;
        DISALLOW_EVIL_CONSTRUCTORS(CallbackData);
    };
};

}; // namespace android

#endif // ANDROID_MEDIAPLAYER2AUDIOOUTPUT_H