/*
 * Copyright (C) 2009 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 "MediaPlayerImpl"
#include "utils/Log.h"

#include <OMX_Component.h>

#include <unistd.h>

#include <media/stagefright/AudioPlayer.h>
#include <media/stagefright/CachingDataSource.h>
// #include <media/stagefright/CameraSource.h>
#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/HTTPStream.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/MediaPlayerImpl.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/MmapSource.h>
#include <media/stagefright/OMXCodec.h>
#include <media/stagefright/ShoutcastSource.h>
#include <media/stagefright/TimeSource.h>
#include <ui/PixelFormat.h>
#include <ui/Surface.h>

namespace android {

MediaPlayerImpl::MediaPlayerImpl(const char *uri)
    : mInitCheck(NO_INIT),
      mTimeSource(NULL),
      mAudioPlayer(NULL),
      mVideoWidth(0),
      mVideoHeight(0),
      mVideoPosition(0),
      mDuration(0),
      mPlaying(false),
      mPaused(false),
      mSeeking(false) {
    LOGI("MediaPlayerImpl(%s)", uri);
    DataSource::RegisterDefaultSniffers();

    status_t err = mClient.connect();
    if (err != OK) {
        LOGE("Failed to connect to OMXClient.");
        return;
    }

    if (!strncasecmp("shoutcast://", uri, 12)) {
        setAudioSource(makeShoutcastSource(uri));
#if 0
    } else if (!strncasecmp("camera:", uri, 7)) {
        mVideoWidth = 480;
        mVideoHeight = 320;
        mVideoDecoder = CameraSource::Create();
#endif
    } else {
        sp<DataSource> source;
        if (!strncasecmp("file://", uri, 7)) {
            source = new MmapSource(uri + 7);
        } else if (!strncasecmp("http://", uri, 7)) {
            source = new HTTPDataSource(uri);
            source = new CachingDataSource(source, 64 * 1024, 10);
        } else {
            // Assume it's a filename.
            source = new MmapSource(uri);
        }

        mExtractor = MediaExtractor::Create(source);

        if (mExtractor == NULL) {
            return;
        }
    }

    init();

    mInitCheck = OK;
}

MediaPlayerImpl::MediaPlayerImpl(int fd, int64_t offset, int64_t length)
    : mInitCheck(NO_INIT),
      mTimeSource(NULL),
      mAudioPlayer(NULL),
      mVideoWidth(0),
      mVideoHeight(0),
      mVideoPosition(0),
      mDuration(0),
      mPlaying(false),
      mPaused(false),
      mSeeking(false) {
    LOGI("MediaPlayerImpl(%d, %lld, %lld)", fd, offset, length);
    DataSource::RegisterDefaultSniffers();

    status_t err = mClient.connect();
    if (err != OK) {
        LOGE("Failed to connect to OMXClient.");
        return;
    }

    mExtractor = MediaExtractor::Create(
            new MmapSource(fd, offset, length));

    if (mExtractor == NULL) {
        return;
    }

    init();

    mInitCheck = OK;
}

status_t MediaPlayerImpl::initCheck() const {
    return mInitCheck;
}

MediaPlayerImpl::~MediaPlayerImpl() {
    stop();
    setSurface(NULL);

    if (mInitCheck == OK) {
        mClient.disconnect();
    }

    LOGV("~MediaPlayerImpl done.");
}

void MediaPlayerImpl::play() {
    LOGI("play");

    if (mPlaying) {
        if (mPaused) {
            if (mAudioSource != NULL) {
                mAudioPlayer->resume();
            }
            mPaused = false;
        }
        return;
    }

    mPlaying = true;

    if (mAudioSource != NULL) {
        mAudioPlayer = new AudioPlayer(mAudioSink);
        mAudioPlayer->setSource(mAudioDecoder);

        if (mVideoDecoder == NULL) {
            // If there is no video, start playing right away,
            // otherwise we'll start the audio player after we decode
            // the first video frame, this way we won't be behind right
            // away.
            mAudioPlayer->start();
        }

        mTimeSource = mAudioPlayer;
    } else {
        mTimeSource = new SystemTimeSource;
    }

    if (mVideoDecoder != NULL) {
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

        pthread_create(&mVideoThread, &attr, VideoWrapper, this);

        pthread_attr_destroy(&attr);
    }
}

void MediaPlayerImpl::pause() {
    if (!mPlaying || mPaused) {
        return;
    }

    if (mAudioSource != NULL) {
        mAudioPlayer->pause();
    }

    mPaused = true;
}

void MediaPlayerImpl::stop() {
    if (!mPlaying) {
        return;
    }

    mPlaying = false;

    if (mVideoDecoder != NULL) {
        void *dummy;
        pthread_join(mVideoThread, &dummy);
    }

    if (mAudioSource != NULL) {
        mAudioPlayer->stop();

        delete mAudioPlayer;
        mAudioPlayer = NULL;
    } else {
        delete mTimeSource;
    }

    mTimeSource = NULL;
}

// static
void *MediaPlayerImpl::VideoWrapper(void *me) {
    ((MediaPlayerImpl *)me)->videoEntry();

    return NULL;
}

void MediaPlayerImpl::videoEntry() {
    bool firstFrame = true;
    bool eof = false;

    status_t err = mVideoDecoder->start();
    CHECK_EQ(err, OK);

    while (mPlaying) {
        MediaBuffer *buffer;

        MediaSource::ReadOptions options;
        bool seeking = false;

        {
            Mutex::Autolock autoLock(mLock);
            if (mSeeking) {
                LOGI("seek-options to %lld", mSeekTimeUs);
                options.setSeekTo(mSeekTimeUs);

                mSeeking = false;
                seeking = true;
                eof = false;
            }
        }

        if (eof || mPaused) {
            usleep(100000);
            continue;
        }

        status_t err = mVideoDecoder->read(&buffer, &options);
        CHECK((err == OK && buffer != NULL) || (err != OK && buffer == NULL));

        if (err == ERROR_END_OF_STREAM || err != OK) {
            eof = true;
            continue;
        }

        if (buffer->range_length() == 0) {
            // The final buffer is empty.
            buffer->release();
            continue;
        }

        int32_t units, scale;
        bool success =
            buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
        CHECK(success);
        success =
            buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
        CHECK(success);

        int64_t pts_us = (int64_t)units * 1000000 / scale;
        {
            Mutex::Autolock autoLock(mLock);
            mVideoPosition = pts_us;

            LOGV("now_video = %.2f secs (%lld ms)",
                 pts_us / 1E6, (pts_us + 500) / 1000);
        }

        if (seeking && mAudioPlayer != NULL) {
            // Now that we know where exactly video seeked (taking sync-samples
            // into account), we will seek the audio track to the same time.
            mAudioPlayer->seekTo(pts_us);
        }

        if (firstFrame || seeking) {
            if (firstFrame && mAudioPlayer != NULL) {
                // We've deferred starting the audio player until now.
                mAudioPlayer->start();
            }
            mTimeSourceDeltaUs = mTimeSource->getRealTimeUs() - pts_us;
            firstFrame = false;
        }

        displayOrDiscardFrame(buffer, pts_us);
    }

    mVideoDecoder->stop();
}

void MediaPlayerImpl::displayOrDiscardFrame(
        MediaBuffer *buffer, int64_t pts_us) {
    for (;;) {
        if (!mPlaying || mPaused) {
            buffer->release();
            buffer = NULL;

            return;
        }

        int64_t realtime_us, mediatime_us;
        if (mAudioPlayer != NULL
            && mAudioPlayer->getMediaTimeMapping(&realtime_us, &mediatime_us)) {
            mTimeSourceDeltaUs = realtime_us - mediatime_us;
            LOGV("mTimeSourceDeltaUs = %.2f secs", mTimeSourceDeltaUs / 1E6);
        }

        int64_t now_us = mTimeSource->getRealTimeUs();
        now_us -= mTimeSourceDeltaUs;

        int64_t delay_us = pts_us - now_us;

        if (delay_us < -15000) {
            // We're late.

            LOGI("we're late by %lld ms, dropping a frame\n",
                 -delay_us / 1000);

            buffer->release();
            buffer = NULL;
            return;
        } else if (delay_us > 100000) {
            LOGI("we're much too early (by %lld ms)\n",
                 delay_us / 1000);
            usleep(100000);
            continue;
        } else if (delay_us > 0) {
            usleep(delay_us);
        }

        break;
    }

    {
        Mutex::Autolock autoLock(mLock);
        if (mVideoRenderer.get() != NULL) {
            sendFrameToISurface(buffer);
        }
    }

    buffer->release();
    buffer = NULL;
}

void MediaPlayerImpl::init() {
    if (mExtractor != NULL) {
        size_t num_tracks = mExtractor->countTracks();

        mDuration = 0;

        for (size_t i = 0; i < num_tracks; ++i) {
            const sp<MetaData> meta = mExtractor->getTrackMetaData(i);
            CHECK(meta != NULL);

            const char *mime;
            if (!meta->findCString(kKeyMIMEType, &mime)) {
                continue;
            }

            bool is_audio = false;
            bool is_acceptable = false;
            if (!strncasecmp(mime, "audio/", 6)) {
                is_audio = true;
                is_acceptable = (mAudioSource == NULL);
            } else if (!strncasecmp(mime, "video/", 6)) {
                is_acceptable = (mVideoSource == NULL);
            }

            if (!is_acceptable) {
                continue;
            }

            sp<MediaSource> source = mExtractor->getTrack(i);

            int32_t units, scale;
            if (meta->findInt32(kKeyDuration, &units)
                && meta->findInt32(kKeyTimeScale, &scale)) {
                int64_t duration_us = (int64_t)units * 1000000 / scale;
                if (duration_us > mDuration) {
                    mDuration = duration_us;
                }
            }

            if (is_audio) {
                setAudioSource(source);
            } else {
                setVideoSource(source);
            }
        }
    }
}

void MediaPlayerImpl::setAudioSource(const sp<MediaSource> &source) {
    LOGI("setAudioSource");
    mAudioSource = source;

    sp<MetaData> meta = source->getFormat();

    mAudioDecoder = OMXCodec::Create(
            mClient.interface(), meta, false /* createEncoder */, source);
}

void MediaPlayerImpl::setVideoSource(const sp<MediaSource> &source) {
    LOGI("setVideoSource");
    mVideoSource = source;

    sp<MetaData> meta = source->getFormat();

    bool success = meta->findInt32(kKeyWidth, &mVideoWidth);
    CHECK(success);

    success = meta->findInt32(kKeyHeight, &mVideoHeight);
    CHECK(success);

    mVideoDecoder = OMXCodec::Create(
            mClient.interface(), meta, false /* createEncoder */, source);

    if (mISurface.get() != NULL || mSurface.get() != NULL) {
        depopulateISurface();
        populateISurface();
    }
}

void MediaPlayerImpl::setSurface(const sp<Surface> &surface) {
    LOGI("setSurface %p", surface.get());
    Mutex::Autolock autoLock(mLock);

    depopulateISurface();

    mSurface = surface;
    mISurface = NULL;

    if (mSurface.get() != NULL) {
        populateISurface();
    }
}

void MediaPlayerImpl::setISurface(const sp<ISurface> &isurface) {
    LOGI("setISurface %p", isurface.get());
    Mutex::Autolock autoLock(mLock);

    depopulateISurface();

    mSurface = NULL;
    mISurface = isurface;

    if (mISurface.get() != NULL) {
        populateISurface();
    }
}

MediaSource *MediaPlayerImpl::makeShoutcastSource(const char *uri) {
    if (strncasecmp(uri, "shoutcast://", 12)) {
        return NULL;
    }

    string host;
    string path;
    int port;

    char *slash = strchr(uri + 12, '/');
    if (slash == NULL) {
        host = uri + 12;
        path = "/";
    } else {
        host = string(uri + 12, slash - (uri + 12));
        path = slash;
    }

    char *colon = strchr(host.c_str(), ':');
    if (colon == NULL) {
        port = 80;
    } else {
        char *end;
        long tmp = strtol(colon + 1, &end, 10);
        CHECK(end > colon + 1);
        CHECK(tmp > 0 && tmp < 65536);
        port = tmp;

        host = string(host, 0, colon - host.c_str());
    }

    LOGI("Connecting to host '%s', port %d, path '%s'",
         host.c_str(), port, path.c_str());

    HTTPStream *http = new HTTPStream;
    int http_status;

    for (;;) {
        status_t err = http->connect(host.c_str(), port);
        CHECK_EQ(err, OK);

        err = http->send("GET ");
        err = http->send(path.c_str());
        err = http->send(" HTTP/1.1\r\n");
        err = http->send("Host: ");
        err = http->send(host.c_str());
        err = http->send("\r\n");
        err = http->send("Icy-MetaData: 1\r\n\r\n");

        CHECK_EQ(OK, http->receive_header(&http_status));

        if (http_status == 301 || http_status == 302) {
            string location;
            CHECK(http->find_header_value("Location", &location));

            CHECK(string(location, 0, 7) == "http://");
            location.erase(0, 7);
            string::size_type slashPos = location.find('/');
            if (slashPos == string::npos) {
                slashPos = location.size();
                location += '/';
            }

            http->disconnect();

            LOGI("Redirecting to %s\n", location.c_str());

            host = string(location, 0, slashPos);

            string::size_type colonPos = host.find(':');
            if (colonPos != string::npos) {
                const char *start = host.c_str() + colonPos + 1;
                char *end;
                long tmp = strtol(start, &end, 10);
                CHECK(end > start && (*end == '\0'));

                port = (tmp >= 0 && tmp < 65536) ? (int)tmp : 80;
            } else {
                port = 80;
            }

            path = string(location, slashPos);

            continue;
        }

        break;
    }

    if (http_status != 200) {
        LOGE("Connection failed: http_status = %d", http_status);
        return NULL;
    }

    MediaSource *source = new ShoutcastSource(http);

    return source;
}

bool MediaPlayerImpl::isPlaying() const {
    return mPlaying && !mPaused;
}

int64_t MediaPlayerImpl::getDuration() {
    return mDuration;
}

int64_t MediaPlayerImpl::getPosition() {
    int64_t position = 0;
    if (mVideoSource != NULL) {
        Mutex::Autolock autoLock(mLock);
        position = mVideoPosition;
    } else if (mAudioPlayer != NULL) {
        position = mAudioPlayer->getMediaTimeUs();
    }

    return position;
}

status_t MediaPlayerImpl::seekTo(int64_t time) {
    LOGI("seekTo %lld", time);

    if (mPaused) {
        return UNKNOWN_ERROR;
    }

    if (mVideoSource == NULL && mAudioPlayer != NULL) {
        mAudioPlayer->seekTo(time);
    } else {
        Mutex::Autolock autoLock(mLock);
        mSeekTimeUs = time;
        mSeeking = true;
    }

    return OK;
}

void MediaPlayerImpl::populateISurface() {
    if (mVideoSource == NULL) {
        return;
    }

    sp<MetaData> meta = mVideoDecoder->getFormat();

    int32_t format;
    const char *component;
    int32_t decodedWidth, decodedHeight;
    bool success = meta->findInt32(kKeyColorFormat, &format);
    success = success && meta->findCString(kKeyDecoderComponent, &component);
    success = success && meta->findInt32(kKeyWidth, &decodedWidth);
    success = success && meta->findInt32(kKeyHeight, &decodedHeight);
    CHECK(success);

    if (mSurface.get() != NULL) {
        mVideoRenderer =
            mClient.interface()->createRenderer(
                    mSurface, component,
                    (OMX_COLOR_FORMATTYPE)format,
                    decodedWidth, decodedHeight,
                    mVideoWidth, mVideoHeight);
    } else {
        mVideoRenderer =
            mClient.interface()->createRenderer(
                    mISurface, component,
                    (OMX_COLOR_FORMATTYPE)format,
                    decodedWidth, decodedHeight,
                    mVideoWidth, mVideoHeight);
    }
}

void MediaPlayerImpl::depopulateISurface() {
    mVideoRenderer.clear();
}

void MediaPlayerImpl::sendFrameToISurface(MediaBuffer *buffer) {
    void *id;
    if (buffer->meta_data()->findPointer(kKeyBufferID, &id)) {
        mVideoRenderer->render((IOMX::buffer_id)id);
    }
}

void MediaPlayerImpl::setAudioSink(
        const sp<MediaPlayerBase::AudioSink> &audioSink) {
    LOGI("setAudioSink %p", audioSink.get());
    mAudioSink = audioSink;
}

}  // namespace android

