// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/android/media_player_bridge.h"

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/strings/string_util.h"
#include "jni/MediaPlayerBridge_jni.h"
#include "media/base/android/media_player_manager.h"
#include "media/base/android/media_resource_getter.h"

using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaLocalRef;

// Time update happens every 250ms.
const int kTimeUpdateInterval = 250;

// blob url scheme.
const char kBlobScheme[] = "blob";

namespace media {

MediaPlayerBridge::MediaPlayerBridge(
    int player_id,
    const GURL& url,
    const GURL& first_party_for_cookies,
    const std::string& user_agent,
    bool hide_url_log,
    MediaPlayerManager* manager,
    const RequestMediaResourcesCB& request_media_resources_cb,
    const ReleaseMediaResourcesCB& release_media_resources_cb)
    : MediaPlayerAndroid(player_id,
                         manager,
                         request_media_resources_cb,
                         release_media_resources_cb),
      prepared_(false),
      pending_play_(false),
      url_(url),
      first_party_for_cookies_(first_party_for_cookies),
      user_agent_(user_agent),
      hide_url_log_(hide_url_log),
      width_(0),
      height_(0),
      can_pause_(true),
      can_seek_forward_(true),
      can_seek_backward_(true),
      is_surface_in_use_(false),
      weak_factory_(this) {
  listener_.reset(new MediaPlayerListener(base::MessageLoopProxy::current(),
                                          weak_factory_.GetWeakPtr()));
}

MediaPlayerBridge::~MediaPlayerBridge() {
  if (!j_media_player_bridge_.is_null()) {
    JNIEnv* env = base::android::AttachCurrentThread();
    CHECK(env);
    Java_MediaPlayerBridge_destroy(env, j_media_player_bridge_.obj());
  }
  Release();
}

void MediaPlayerBridge::Initialize() {
  cookies_.clear();
  if (url_.SchemeIsFile()) {
    ExtractMediaMetadata(url_.spec());
    return;
  }

  media::MediaResourceGetter* resource_getter =
      manager()->GetMediaResourceGetter();
  if (url_.SchemeIsFileSystem() || url_.SchemeIs(kBlobScheme)) {
    resource_getter->GetPlatformPathFromURL(
        url_,
        base::Bind(&MediaPlayerBridge::ExtractMediaMetadata,
                   weak_factory_.GetWeakPtr()));
    return;
  }

  resource_getter->GetCookies(url_,
                              first_party_for_cookies_,
                              base::Bind(&MediaPlayerBridge::OnCookiesRetrieved,
                                         weak_factory_.GetWeakPtr()));
}

void MediaPlayerBridge::CreateJavaMediaPlayerBridge() {
  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);

  j_media_player_bridge_.Reset(Java_MediaPlayerBridge_create(
      env, reinterpret_cast<intptr_t>(this)));

  SetMediaPlayerListener();
}

void MediaPlayerBridge::SetJavaMediaPlayerBridge(
    jobject j_media_player_bridge) {
  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);

  j_media_player_bridge_.Reset(env, j_media_player_bridge);
}

base::android::ScopedJavaLocalRef<jobject> MediaPlayerBridge::
    GetJavaMediaPlayerBridge() {
  base::android::ScopedJavaLocalRef<jobject> j_bridge(
      j_media_player_bridge_);
  return j_bridge;
}

void MediaPlayerBridge::SetMediaPlayerListener() {
  jobject j_context = base::android::GetApplicationContext();
  DCHECK(j_context);

  listener_->CreateMediaPlayerListener(j_context, j_media_player_bridge_.obj());
}

void MediaPlayerBridge::SetDuration(base::TimeDelta duration) {
  duration_ = duration;
}

void MediaPlayerBridge::SetVideoSurface(gfx::ScopedJavaSurface surface) {
  if (j_media_player_bridge_.is_null()) {
    if (surface.IsEmpty())
      return;
    Prepare();
  }

  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);
  is_surface_in_use_ = true;
  Java_MediaPlayerBridge_setSurface(
      env, j_media_player_bridge_.obj(), surface.j_surface().obj());
}

void MediaPlayerBridge::Prepare() {
  DCHECK(j_media_player_bridge_.is_null());
  CreateJavaMediaPlayerBridge();
  if (url_.SchemeIsFileSystem() || url_.SchemeIs(kBlobScheme)) {
    manager()->GetMediaResourceGetter()->GetPlatformPathFromURL(
        url_,
        base::Bind(&MediaPlayerBridge::SetDataSource,
                   weak_factory_.GetWeakPtr()));
    return;
  }

  SetDataSource(url_.spec());
}

void MediaPlayerBridge::SetDataSource(const std::string& url) {
  if (j_media_player_bridge_.is_null())
    return;

  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);

  // Create a Java String for the URL.
  ScopedJavaLocalRef<jstring> j_url_string = ConvertUTF8ToJavaString(env, url);
  ScopedJavaLocalRef<jstring> j_cookies = ConvertUTF8ToJavaString(
      env, cookies_);
  ScopedJavaLocalRef<jstring> j_user_agent = ConvertUTF8ToJavaString(
      env, user_agent_);

  jobject j_context = base::android::GetApplicationContext();
  DCHECK(j_context);

  const std::string data_uri_prefix("data:");
  if (StartsWithASCII(url, data_uri_prefix, true)) {
    if (!Java_MediaPlayerBridge_setDataUriDataSource(
        env, j_media_player_bridge_.obj(), j_context, j_url_string.obj())) {
      OnMediaError(MEDIA_ERROR_FORMAT);
    }
    return;
  }

  if (!Java_MediaPlayerBridge_setDataSource(
      env, j_media_player_bridge_.obj(), j_context, j_url_string.obj(),
      j_cookies.obj(), j_user_agent.obj(), hide_url_log_)) {
    OnMediaError(MEDIA_ERROR_FORMAT);
    return;
  }

  request_media_resources_cb_.Run(player_id());
  if (!Java_MediaPlayerBridge_prepareAsync(env, j_media_player_bridge_.obj()))
    OnMediaError(MEDIA_ERROR_FORMAT);
}

void MediaPlayerBridge::OnDidSetDataUriDataSource(JNIEnv* env, jobject obj,
    jboolean success) {
  if (!success) {
    OnMediaError(MEDIA_ERROR_FORMAT);
    return;
  }

  request_media_resources_cb_.Run(player_id());
  if (!Java_MediaPlayerBridge_prepareAsync(env, j_media_player_bridge_.obj()))
    OnMediaError(MEDIA_ERROR_FORMAT);
}

void MediaPlayerBridge::OnCookiesRetrieved(const std::string& cookies) {
  cookies_ = cookies;
  ExtractMediaMetadata(url_.spec());
}

void MediaPlayerBridge::ExtractMediaMetadata(const std::string& url) {
  manager()->GetMediaResourceGetter()->ExtractMediaMetadata(
      url,
      cookies_,
      user_agent_,
      base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted,
                 weak_factory_.GetWeakPtr()));
}

void MediaPlayerBridge::OnMediaMetadataExtracted(
    base::TimeDelta duration, int width, int height, bool success) {
  if (success) {
    duration_ = duration;
    width_ = width;
    height_ = height;
  }
  manager()->OnMediaMetadataChanged(
      player_id(), duration_, width_, height_, success);
}

void MediaPlayerBridge::Start() {
  if (j_media_player_bridge_.is_null()) {
    pending_play_ = true;
    Prepare();
  } else {
    if (prepared_)
      StartInternal();
    else
      pending_play_ = true;
  }
}

void MediaPlayerBridge::Pause(bool is_media_related_action) {
  if (j_media_player_bridge_.is_null()) {
    pending_play_ = false;
  } else {
    if (prepared_ && IsPlaying())
      PauseInternal();
    else
      pending_play_ = false;
  }
}

bool MediaPlayerBridge::IsPlaying() {
  if (!prepared_)
    return pending_play_;

  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);
  jboolean result = Java_MediaPlayerBridge_isPlaying(
      env, j_media_player_bridge_.obj());
  return result;
}

int MediaPlayerBridge::GetVideoWidth() {
  if (!prepared_)
    return width_;
  JNIEnv* env = base::android::AttachCurrentThread();
  return Java_MediaPlayerBridge_getVideoWidth(
      env, j_media_player_bridge_.obj());
}

int MediaPlayerBridge::GetVideoHeight() {
  if (!prepared_)
    return height_;
  JNIEnv* env = base::android::AttachCurrentThread();
  return Java_MediaPlayerBridge_getVideoHeight(
      env, j_media_player_bridge_.obj());
}

void MediaPlayerBridge::SeekTo(base::TimeDelta timestamp) {
  // Record the time to seek when OnMediaPrepared() is called.
  pending_seek_ = timestamp;

  if (j_media_player_bridge_.is_null())
    Prepare();
  else if (prepared_)
    SeekInternal(timestamp);
}

base::TimeDelta MediaPlayerBridge::GetCurrentTime() {
  if (!prepared_)
    return pending_seek_;
  JNIEnv* env = base::android::AttachCurrentThread();
  return base::TimeDelta::FromMilliseconds(
      Java_MediaPlayerBridge_getCurrentPosition(
          env, j_media_player_bridge_.obj()));
}

base::TimeDelta MediaPlayerBridge::GetDuration() {
  if (!prepared_)
    return duration_;
  JNIEnv* env = base::android::AttachCurrentThread();
  return base::TimeDelta::FromMilliseconds(
      Java_MediaPlayerBridge_getDuration(
          env, j_media_player_bridge_.obj()));
}

void MediaPlayerBridge::Release() {
  if (j_media_player_bridge_.is_null())
    return;

  time_update_timer_.Stop();
  if (prepared_)
    pending_seek_ = GetCurrentTime();
  prepared_ = false;
  pending_play_ = false;
  is_surface_in_use_ = false;
  SetVideoSurface(gfx::ScopedJavaSurface());
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_MediaPlayerBridge_release(env, j_media_player_bridge_.obj());
  j_media_player_bridge_.Reset();
  release_media_resources_cb_.Run(player_id());
  listener_->ReleaseMediaPlayerListenerResources();
}

void MediaPlayerBridge::SetVolume(double volume) {
  if (j_media_player_bridge_.is_null())
    return;

  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);
  Java_MediaPlayerBridge_setVolume(
      env, j_media_player_bridge_.obj(), volume);
}

void MediaPlayerBridge::OnVideoSizeChanged(int width, int height) {
  width_ = width;
  height_ = height;
  manager()->OnVideoSizeChanged(player_id(), width, height);
}

void MediaPlayerBridge::OnMediaError(int error_type) {
  manager()->OnError(player_id(), error_type);
}

void MediaPlayerBridge::OnBufferingUpdate(int percent) {
  manager()->OnBufferingUpdate(player_id(), percent);
}

void MediaPlayerBridge::OnPlaybackComplete() {
  time_update_timer_.Stop();
  manager()->OnPlaybackComplete(player_id());
}

void MediaPlayerBridge::OnMediaInterrupted() {
  time_update_timer_.Stop();
  manager()->OnMediaInterrupted(player_id());
}

void MediaPlayerBridge::OnSeekComplete() {
  manager()->OnSeekComplete(player_id(), GetCurrentTime());
}

void MediaPlayerBridge::OnMediaPrepared() {
  if (j_media_player_bridge_.is_null())
    return;

  prepared_ = true;
  duration_ = GetDuration();

  // If media player was recovered from a saved state, consume all the pending
  // events.
  PendingSeekInternal(pending_seek_);

  if (pending_play_) {
    StartInternal();
    pending_play_ = false;
  }

  UpdateAllowedOperations();
  manager()->OnMediaMetadataChanged(
      player_id(), duration_, width_, height_, true);
}

ScopedJavaLocalRef<jobject> MediaPlayerBridge::GetAllowedOperations() {
  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);

  return Java_MediaPlayerBridge_getAllowedOperations(
      env, j_media_player_bridge_.obj());
}

void MediaPlayerBridge::UpdateAllowedOperations() {
  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);

  ScopedJavaLocalRef<jobject> allowedOperations = GetAllowedOperations();

  can_pause_ = Java_AllowedOperations_canPause(env, allowedOperations.obj());
  can_seek_forward_ = Java_AllowedOperations_canSeekForward(
      env, allowedOperations.obj());
  can_seek_backward_ = Java_AllowedOperations_canSeekBackward(
      env, allowedOperations.obj());
}

void MediaPlayerBridge::StartInternal() {
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_MediaPlayerBridge_start(env, j_media_player_bridge_.obj());
  if (!time_update_timer_.IsRunning()) {
    time_update_timer_.Start(
        FROM_HERE,
        base::TimeDelta::FromMilliseconds(kTimeUpdateInterval),
        this, &MediaPlayerBridge::OnTimeUpdateTimerFired);
  }
}

void MediaPlayerBridge::PauseInternal() {
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_MediaPlayerBridge_pause(env, j_media_player_bridge_.obj());
  time_update_timer_.Stop();
}

void MediaPlayerBridge::PendingSeekInternal(const base::TimeDelta& time) {
  SeekInternal(time);
}

void MediaPlayerBridge::SeekInternal(base::TimeDelta time) {
  if (time > duration_)
    time = duration_;

  // Seeking to an invalid position may cause media player to stuck in an
  // error state.
  if (time < base::TimeDelta()) {
    DCHECK_EQ(-1.0, time.InMillisecondsF());
    return;
  }

  JNIEnv* env = base::android::AttachCurrentThread();
  CHECK(env);
  int time_msec = static_cast<int>(time.InMilliseconds());
  Java_MediaPlayerBridge_seekTo(
      env, j_media_player_bridge_.obj(), time_msec);
}

void MediaPlayerBridge::OnTimeUpdateTimerFired() {
  manager()->OnTimeUpdate(player_id(), GetCurrentTime());
}

bool MediaPlayerBridge::RegisterMediaPlayerBridge(JNIEnv* env) {
  bool ret = RegisterNativesImpl(env);
  DCHECK(g_MediaPlayerBridge_clazz);
  return ret;
}

bool MediaPlayerBridge::CanPause() {
  return can_pause_;
}

bool MediaPlayerBridge::CanSeekForward() {
  return can_seek_forward_;
}

bool MediaPlayerBridge::CanSeekBackward() {
  return can_seek_backward_;
}

bool MediaPlayerBridge::IsPlayerReady() {
  return prepared_;
}

GURL MediaPlayerBridge::GetUrl() {
  return url_;
}

GURL MediaPlayerBridge::GetFirstPartyForCookies() {
  return first_party_for_cookies_;
}

bool MediaPlayerBridge::IsSurfaceInUse() const {
  return is_surface_in_use_;
}

}  // namespace media