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

//#define LOG_NDEBUG 0
#define LOG_TAG "VorbisPlayer"
#include "utils/Log.h"

#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>


#include "VorbisPlayer.h"

#ifdef HAVE_GETTID
static pid_t myTid() { return gettid(); }
#else
static pid_t myTid() { return getpid(); }
#endif

// ----------------------------------------------------------------------------

namespace android {

// ----------------------------------------------------------------------------

// TODO: Determine appropriate return codes
static status_t ERROR_NOT_OPEN = -1;
static status_t ERROR_OPEN_FAILED = -2;
static status_t ERROR_ALLOCATE_FAILED = -4;
static status_t ERROR_NOT_SUPPORTED = -8;
static status_t ERROR_NOT_READY = -16;
static status_t STATE_INIT = 0;
static status_t STATE_ERROR = 1;
static status_t STATE_OPEN = 2;


VorbisPlayer::VorbisPlayer() :
    mAudioBuffer(NULL), mPlayTime(-1), mDuration(-1), mState(STATE_ERROR),
    mStreamType(AudioSystem::MUSIC), mLoop(false), mAndroidLoop(false),
    mExit(false), mPaused(false), mRender(false), mRenderTid(-1)
{
    LOGV("constructor\n");
    memset(&mVorbisFile, 0, sizeof mVorbisFile);
}

void VorbisPlayer::onFirstRef()
{
    LOGV("onFirstRef");
    // create playback thread
    Mutex::Autolock l(mMutex);
    createThreadEtc(renderThread, this, "vorbis decoder", ANDROID_PRIORITY_AUDIO);
    mCondition.wait(mMutex);
    if (mRenderTid > 0) {
        LOGV("render thread(%d) started", mRenderTid);
        mState = STATE_INIT;
    }
}

status_t VorbisPlayer::initCheck()
{
    if (mState != STATE_ERROR) return NO_ERROR;
    return ERROR_NOT_READY;
}

VorbisPlayer::~VorbisPlayer() {
    LOGV("VorbisPlayer destructor\n");
    release();
}

status_t VorbisPlayer::setDataSource(const char* path)
{
    return setdatasource(path, -1, 0, 0x7ffffffffffffffLL); // intentionally less than LONG_MAX
}

status_t VorbisPlayer::setDataSource(int fd, int64_t offset, int64_t length)
{
    return setdatasource(NULL, fd, offset, length);
}

size_t VorbisPlayer::vp_fread(void *buf, size_t size, size_t nmemb, void *me) {
    VorbisPlayer *self = (VorbisPlayer*) me;

    long curpos = vp_ftell(me);
    while (nmemb != 0 && (curpos + size * nmemb) > self->mLength) {
        nmemb--;
    }
    return fread(buf, size, nmemb, self->mFile);
}

int VorbisPlayer::vp_fseek(void *me, ogg_int64_t off, int whence) {
    VorbisPlayer *self = (VorbisPlayer*) me;
    if (whence == SEEK_SET)
        return fseek(self->mFile, off + self->mOffset, whence);
    else if (whence == SEEK_CUR)
        return fseek(self->mFile, off, whence);
    else if (whence == SEEK_END)
        return fseek(self->mFile, self->mOffset + self->mLength + off, SEEK_SET);
    return -1;
}

int VorbisPlayer::vp_fclose(void *me) {
    LOGV("vp_fclose");
    VorbisPlayer *self = (VorbisPlayer*) me;
    int ret = fclose (self->mFile);
    self->mFile = NULL;
    return ret;
}

long VorbisPlayer::vp_ftell(void *me) {
    VorbisPlayer *self = (VorbisPlayer*) me;
    return ftell(self->mFile) - self->mOffset;
}

status_t VorbisPlayer::setdatasource(const char *path, int fd, int64_t offset, int64_t length)
{
    LOGV("setDataSource url=%s, fd=%d\n", path, fd);

    // file still open?
    Mutex::Autolock l(mMutex);
    if (mState == STATE_OPEN) {
        reset_nosync();
    }

    // open file and set paused state
    if (path) {
        mFile = fopen(path, "r");
    } else {
        mFile = fdopen(dup(fd), "r");
    }
    if (mFile == NULL) {
        return ERROR_OPEN_FAILED;
    }

    struct stat sb;
    int ret;
    if (path) {
        ret = stat(path, &sb);
    } else {
        ret = fstat(fd, &sb);
    }
    if (ret != 0) {
        mState = STATE_ERROR;
        fclose(mFile);
        return ERROR_OPEN_FAILED;
    }
    if (sb.st_size > (length + offset)) {
        mLength = length;
    } else {
        mLength = sb.st_size - offset;
    }

    ov_callbacks callbacks = {
        (size_t (*)(void *, size_t, size_t, void *))  vp_fread,
        (int (*)(void *, ogg_int64_t, int))           vp_fseek,
        (int (*)(void *))                             vp_fclose,
        (long (*)(void *))                            vp_ftell
    };

    mOffset = offset;
    fseek(mFile, offset, SEEK_SET);

    int result = ov_open_callbacks(this, &mVorbisFile, NULL, 0, callbacks);
    if (result < 0) {
        LOGE("ov_open() failed: [%d]\n", (int)result);
        mState = STATE_ERROR;
        fclose(mFile);
        return ERROR_OPEN_FAILED;
    }

    // look for the android loop tag  (for ringtones)
    char **ptr = ov_comment(&mVorbisFile,-1)->user_comments;
    while(*ptr) {
        // does the comment start with ANDROID_LOOP_TAG
        if(strncmp(*ptr, ANDROID_LOOP_TAG, strlen(ANDROID_LOOP_TAG)) == 0) {
            // read the value of the tag
            char *val = *ptr + strlen(ANDROID_LOOP_TAG) + 1;
            mAndroidLoop = (strncmp(val, "true", 4) == 0);
        }
        // we keep parsing even after finding one occurence of ANDROID_LOOP_TAG,
        // as we could find another one  (the tag might have been appended more than once).
        ++ptr;
    }
    LOGV_IF(mAndroidLoop, "looped sound");

    mState = STATE_OPEN;
    return NO_ERROR;
}

status_t VorbisPlayer::prepare()
{
    LOGV("prepare\n");
    if (mState != STATE_OPEN ) {
        return ERROR_NOT_OPEN;
    }
    return NO_ERROR;
}

status_t VorbisPlayer::prepareAsync() {
    LOGV("prepareAsync\n");
    // can't hold the lock here because of the callback
    // it's safe because we don't change state
    if (mState != STATE_OPEN ) {
        sendEvent(MEDIA_ERROR);
        return NO_ERROR;
    }
    sendEvent(MEDIA_PREPARED);
    return NO_ERROR;
}

status_t VorbisPlayer::start()
{
    LOGV("start\n");
    Mutex::Autolock l(mMutex);
    if (mState != STATE_OPEN) {
        return ERROR_NOT_OPEN;
    }

    mPaused = false;
    mRender = true;

    // wake up render thread
    LOGV("  wakeup render thread\n");
    mCondition.signal();
    return NO_ERROR;
}

status_t VorbisPlayer::stop()
{
    LOGV("stop\n");
    Mutex::Autolock l(mMutex);
    if (mState != STATE_OPEN) {
        return ERROR_NOT_OPEN;
    }
    mPaused = true;
    mRender = false;
    return NO_ERROR;
}

status_t VorbisPlayer::seekTo(int position)
{
    LOGV("seekTo %d\n", position);
    Mutex::Autolock l(mMutex);
    if (mState != STATE_OPEN) {
        return ERROR_NOT_OPEN;
    }

    int result = ov_time_seek(&mVorbisFile, position);
    if (result != 0) {
        LOGE("ov_time_seek() returned %d\n", result);
        return result;
    }
    sendEvent(MEDIA_SEEK_COMPLETE);
    return NO_ERROR;
}

status_t VorbisPlayer::pause()
{
    LOGV("pause\n");
    Mutex::Autolock l(mMutex);
    if (mState != STATE_OPEN) {
        return ERROR_NOT_OPEN;
    }
    mPaused = true;
    return NO_ERROR;
}

bool VorbisPlayer::isPlaying()
{
    LOGV("isPlaying\n");
    if (mState == STATE_OPEN) {
        return mRender;
    }
    return false;
}

status_t VorbisPlayer::getCurrentPosition(int* position)
{
    LOGV("getCurrentPosition\n");
    Mutex::Autolock l(mMutex);
    if (mState != STATE_OPEN) {
        LOGE("getCurrentPosition(): file not open");
        return ERROR_NOT_OPEN;
    }
    *position = ov_time_tell(&mVorbisFile);
    if (*position < 0) {
        LOGE("getCurrentPosition(): ov_time_tell returned %d", *position);
        return *position;
    }
    return NO_ERROR;
}

status_t VorbisPlayer::getDuration(int* duration)
{
    LOGV("getDuration\n");
    Mutex::Autolock l(mMutex);
    if (mState != STATE_OPEN) {
        return ERROR_NOT_OPEN;
    }

    int ret = ov_time_total(&mVorbisFile, -1);
    if (ret == OV_EINVAL) {
        return -1;
    }

    *duration = ret;
    return NO_ERROR;
}

status_t VorbisPlayer::release()
{
    LOGV("release\n");
    Mutex::Autolock l(mMutex);
    reset_nosync();

    // TODO: timeout when thread won't exit
    // wait for render thread to exit
    if (mRenderTid > 0) {
        mExit = true;
        mCondition.signal();
        mCondition.wait(mMutex);
    }
    return NO_ERROR;
}

status_t VorbisPlayer::reset()
{
    LOGV("reset\n");
    Mutex::Autolock l(mMutex);
    if (mState != STATE_OPEN) {
        return NO_ERROR;
    }
    return reset_nosync();
}

// always call with lock held
status_t VorbisPlayer::reset_nosync()
{
    // close file
    ov_clear(&mVorbisFile); // this also closes the FILE
    if (mFile != NULL) {
        LOGV("OOPS! Vorbis didn't close the file");
        fclose(mFile);
    }
    mState = STATE_ERROR;

    mPlayTime = -1;
    mDuration = -1;
    mLoop = false;
    mAndroidLoop = false;
    mPaused = false;
    mRender = false;
    return NO_ERROR;
}

status_t VorbisPlayer::setLooping(int loop)
{
    LOGV("setLooping\n");
    Mutex::Autolock l(mMutex);
    mLoop = (loop != 0);
    return NO_ERROR;
}

status_t VorbisPlayer::createOutputTrack() {
    // open audio track
    vorbis_info *vi = ov_info(&mVorbisFile, -1);

    LOGV("Create AudioTrack object: rate=%ld, channels=%d\n",
            vi->rate, vi->channels);
    if (mAudioSink->open(vi->rate, vi->channels, AudioSystem::PCM_16_BIT, DEFAULT_AUDIOSINK_BUFFERCOUNT) != NO_ERROR) {
        LOGE("mAudioSink open failed");
        return ERROR_OPEN_FAILED;
    }
    return NO_ERROR;
}

int VorbisPlayer::renderThread(void* p) {
    return ((VorbisPlayer*)p)->render();
}

#define AUDIOBUFFER_SIZE 4096

int VorbisPlayer::render() {
    int result = -1;
    int temp;
    int current_section = 0;
    bool audioStarted = false;

    LOGV("render\n");

    // allocate render buffer
    mAudioBuffer = new char[AUDIOBUFFER_SIZE];
    if (!mAudioBuffer) {
        LOGE("mAudioBuffer allocate failed\n");
        goto threadExit;
    }

    // let main thread know we're ready
    {
        Mutex::Autolock l(mMutex);
        mRenderTid = myTid();
        mCondition.signal();
    }

    while (1) {
        long numread = 0;
        {
            Mutex::Autolock l(mMutex);

            // pausing?
            if (mPaused) {
                if (mAudioSink->ready()) mAudioSink->pause();
                mRender = false;
                audioStarted = false;
            }

            // nothing to render, wait for client thread to wake us up
            if (!mExit && !mRender) {
                LOGV("render - signal wait\n");
                mCondition.wait(mMutex);
                LOGV("render - signal rx'd\n");
            }
            if (mExit) break;

            // We could end up here if start() is called, and before we get a
            // chance to run, the app calls stop() or reset(). Re-check render
            // flag so we don't try to render in stop or reset state.
            if (!mRender) continue;

            // render vorbis data into the input buffer
            numread = ov_read(&mVorbisFile, mAudioBuffer, AUDIOBUFFER_SIZE, &current_section);
            if (numread == 0) {
                // end of file, do we need to loop?
                // ...
                if (mLoop || mAndroidLoop) {
                    ov_time_seek(&mVorbisFile, 0);
                    current_section = 0;
                    numread = ov_read(&mVorbisFile, mAudioBuffer, AUDIOBUFFER_SIZE, &current_section);
                } else {
                    mAudioSink->stop();
                    audioStarted = false;
                    mRender = false;
                    mPaused = true;
                    int endpos = ov_time_tell(&mVorbisFile);

                    LOGV("send MEDIA_PLAYBACK_COMPLETE");
                    sendEvent(MEDIA_PLAYBACK_COMPLETE);

                    // wait until we're started again
                    LOGV("playback complete - wait for signal");
                    mCondition.wait(mMutex);
                    LOGV("playback complete - signal rx'd");
                    if (mExit) break;

                    // if we're still at the end, restart from the beginning
                    if (mState == STATE_OPEN) {
                        int curpos = ov_time_tell(&mVorbisFile);
                        if (curpos == endpos) {
                            ov_time_seek(&mVorbisFile, 0);
                        }
                        current_section = 0;
                        numread = ov_read(&mVorbisFile, mAudioBuffer, AUDIOBUFFER_SIZE, &current_section);
                    }
                }
            }
        }

        // codec returns negative number on error
        if (numread < 0) {
            LOGE("Error in Vorbis decoder");
            sendEvent(MEDIA_ERROR);
            break;
        }

        // create audio output track if necessary
        if (!mAudioSink->ready()) {
            LOGV("render - create output track\n");
            if (createOutputTrack() != NO_ERROR)
                break;
        }

        // Write data to the audio hardware
        if ((temp = mAudioSink->write(mAudioBuffer, numread)) < 0) {
            LOGE("Error in writing:%d",temp);
            result = temp;
            break;
        }

        // start audio output if necessary
        if (!audioStarted && !mPaused && !mExit) {
            LOGV("render - starting audio\n");
            mAudioSink->start();
            audioStarted = true;
        }
    }

threadExit:
    mAudioSink.clear();
    if (mAudioBuffer) {
        delete [] mAudioBuffer;
        mAudioBuffer = NULL;
    }

    // tell main thread goodbye
    Mutex::Autolock l(mMutex);
    mRenderTid = -1;
    mCondition.signal();
    return result;
}

} // end namespace android
