// Copyright (c) 2011 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 "webkit/glue/media/simple_data_source.h"

#include "base/message_loop.h"
#include "base/process_util.h"
#include "media/base/filter_host.h"
#include "net/base/data_url.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_status.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebKitClient.h"
#include "webkit/glue/media/web_data_source_factory.h"
#include "webkit/glue/webkit_glue.h"

namespace webkit_glue {

static const char kDataScheme[] = "data";

static WebDataSource* NewSimpleDataSource(MessageLoop* render_loop,
                                          WebKit::WebFrame* frame) {
  return new SimpleDataSource(render_loop, frame);
}

// static
media::DataSourceFactory* SimpleDataSource::CreateFactory(
    MessageLoop* render_loop,
    WebKit::WebFrame* frame,
    WebDataSourceBuildObserverHack* build_observer) {
  return new WebDataSourceFactory(render_loop, frame, &NewSimpleDataSource,
                                  build_observer);
}

SimpleDataSource::SimpleDataSource(
    MessageLoop* render_loop,
    WebKit::WebFrame* frame)
    : render_loop_(render_loop),
      frame_(frame),
      size_(-1),
      single_origin_(true),
      state_(UNINITIALIZED),
      keep_test_loader_(false) {
  DCHECK(render_loop);
}

SimpleDataSource::~SimpleDataSource() {
  base::AutoLock auto_lock(lock_);
  DCHECK(state_ == UNINITIALIZED || state_ == STOPPED);
}

void SimpleDataSource::set_host(media::FilterHost* host) {
  DataSource::set_host(host);

  base::AutoLock auto_lock(lock_);
  if (state_ == INITIALIZED) {
    UpdateHostState();
  }
}

void SimpleDataSource::Stop(media::FilterCallback* callback) {
  base::AutoLock auto_lock(lock_);
  state_ = STOPPED;
  if (callback) {
    callback->Run();
    delete callback;
  }

  // Post a task to the render thread to cancel loading the resource.
  render_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &SimpleDataSource::CancelTask));
}

void SimpleDataSource::Initialize(
    const std::string& url,
    media::PipelineStatusCallback* callback) {
  // Reference to prevent destruction while inside the |initialize_callback_|
  // call. This is a temporary fix to prevent crashes caused by holding the
  // lock and running the destructor.
  scoped_refptr<SimpleDataSource> destruction_guard(this);
  {
    base::AutoLock auto_lock(lock_);
    DCHECK_EQ(state_, UNINITIALIZED);
    DCHECK(callback);
    state_ = INITIALIZING;
    initialize_callback_.reset(callback);

    // Validate the URL.
    SetURL(GURL(url));
    if (!url_.is_valid() || !IsProtocolSupportedForMedia(url_)) {
      DoneInitialization_Locked(false);
      return;
    }

    // Post a task to the render thread to start loading the resource.
    render_loop_->PostTask(FROM_HERE,
        NewRunnableMethod(this, &SimpleDataSource::StartTask));
  }
}

void SimpleDataSource::CancelInitialize() {
  base::AutoLock auto_lock(lock_);
  DCHECK(initialize_callback_.get());
  state_ = STOPPED;
  initialize_callback_.reset();

  // Post a task to the render thread to cancel loading the resource.
  render_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &SimpleDataSource::CancelTask));
}

const media::MediaFormat& SimpleDataSource::media_format() {
  return media_format_;
}

void SimpleDataSource::Read(int64 position,
                            size_t size,
                            uint8* data,
                            ReadCallback* read_callback) {
  DCHECK_GE(size_, 0);
  if (position >= size_) {
    read_callback->RunWithParams(Tuple1<size_t>(0));
    delete read_callback;
  } else {
    size_t copied = std::min(size, static_cast<size_t>(size_ - position));
    memcpy(data, data_.c_str() + position, copied);
    read_callback->RunWithParams(Tuple1<size_t>(copied));
    delete read_callback;
  }
}

bool SimpleDataSource::GetSize(int64* size_out) {
  *size_out = size_;
  return true;
}

bool SimpleDataSource::IsStreaming() {
  return false;
}

void SimpleDataSource::SetPreload(media::Preload preload) {}

void SimpleDataSource::SetURLLoaderForTest(WebKit::WebURLLoader* mock_loader) {
  url_loader_.reset(mock_loader);
  keep_test_loader_ = true;
}

void SimpleDataSource::willSendRequest(
    WebKit::WebURLLoader* loader,
    WebKit::WebURLRequest& newRequest,
    const WebKit::WebURLResponse& redirectResponse) {
  DCHECK(MessageLoop::current() == render_loop_);
  base::AutoLock auto_lock(lock_);

  // Only allow |single_origin_| if we haven't seen a different origin yet.
  if (single_origin_)
    single_origin_ = url_.GetOrigin() == GURL(newRequest.url()).GetOrigin();

  url_ = newRequest.url();
}

void SimpleDataSource::didSendData(
    WebKit::WebURLLoader* loader,
    unsigned long long bytesSent,
    unsigned long long totalBytesToBeSent) {
  NOTIMPLEMENTED();
}

void SimpleDataSource::didReceiveResponse(
    WebKit::WebURLLoader* loader,
    const WebKit::WebURLResponse& response) {
  DCHECK(MessageLoop::current() == render_loop_);
  size_ = response.expectedContentLength();
}

void SimpleDataSource::didDownloadData(
    WebKit::WebURLLoader* loader,
    int dataLength) {
  NOTIMPLEMENTED();
}

void SimpleDataSource::didReceiveData(
    WebKit::WebURLLoader* loader,
    const char* data,
    int data_length,
    int encoded_data_length) {
  DCHECK(MessageLoop::current() == render_loop_);
  data_.append(data, data_length);
}

void SimpleDataSource::didReceiveCachedMetadata(
    WebKit::WebURLLoader* loader,
    const char* data,
    int dataLength) {
  NOTIMPLEMENTED();
}

void SimpleDataSource::didFinishLoading(
    WebKit::WebURLLoader* loader,
    double finishTime) {
  DCHECK(MessageLoop::current() == render_loop_);
  // Reference to prevent destruction while inside the |initialize_callback_|
  // call. This is a temporary fix to prevent crashes caused by holding the
  // lock and running the destructor.
  scoped_refptr<SimpleDataSource> destruction_guard(this);
  {
    base::AutoLock auto_lock(lock_);
    // It's possible this gets called after Stop(), in which case |host_| is no
    // longer valid.
    if (state_ == STOPPED)
      return;

    // Otherwise we should be initializing and have created a WebURLLoader.
    DCHECK_EQ(state_, INITIALIZING);

    // If we don't get a content length or the request has failed, report it
    // as a network error.
    if (size_ == -1)
      size_ = data_.length();
    DCHECK(static_cast<size_t>(size_) == data_.length());

    DoneInitialization_Locked(true);
  }
}

void SimpleDataSource::didFail(
    WebKit::WebURLLoader* loader,
    const WebKit::WebURLError& error) {
  DCHECK(MessageLoop::current() == render_loop_);
  // Reference to prevent destruction while inside the |initialize_callback_|
  // call. This is a temporary fix to prevent crashes caused by holding the
  // lock and running the destructor.
  scoped_refptr<SimpleDataSource> destruction_guard(this);
  {
    base::AutoLock auto_lock(lock_);
    // It's possible this gets called after Stop(), in which case |host_| is no
    // longer valid.
    if (state_ == STOPPED)
      return;

    // Otherwise we should be initializing and have created a WebURLLoader.
    DCHECK_EQ(state_, INITIALIZING);

    // If we don't get a content length or the request has failed, report it
    // as a network error.
    if (size_ == -1)
      size_ = data_.length();
    DCHECK(static_cast<size_t>(size_) == data_.length());

    DoneInitialization_Locked(false);
  }
}

bool SimpleDataSource::HasSingleOrigin() {
  DCHECK(MessageLoop::current() == render_loop_);
  return single_origin_;
}

void SimpleDataSource::Abort() {
  DCHECK(MessageLoop::current() == render_loop_);
  frame_ = NULL;
}

void SimpleDataSource::SetURL(const GURL& url) {
  url_ = url;
  media_format_.Clear();
  media_format_.SetAsString(media::MediaFormat::kURL, url.spec());
}

void SimpleDataSource::StartTask() {
  DCHECK(MessageLoop::current() == render_loop_);
  // Reference to prevent destruction while inside the |initialize_callback_|
  // call. This is a temporary fix to prevent crashes caused by holding the
  // lock and running the destructor.
  scoped_refptr<SimpleDataSource> destruction_guard(this);
  {
    base::AutoLock auto_lock(lock_);

    // We may have stopped.
    if (state_ == STOPPED)
      return;

    CHECK(frame_);

    DCHECK_EQ(state_, INITIALIZING);

    if (url_.SchemeIs(kDataScheme)) {
      // If this using data protocol, we just need to decode it.
      std::string mime_type, charset;
      bool success = net::DataURL::Parse(url_, &mime_type, &charset, &data_);

      // Don't care about the mime-type just proceed if decoding was successful.
      size_ = data_.length();
      DoneInitialization_Locked(success);
    } else {
      // Prepare the request.
      WebKit::WebURLRequest request(url_);
      request.setTargetType(WebKit::WebURLRequest::TargetIsMedia);

      frame_->setReferrerForRequest(request, WebKit::WebURL());

      // This flag is for unittests as we don't want to reset |url_loader|
      if (!keep_test_loader_)
        url_loader_.reset(frame_->createAssociatedURLLoader());

      // Start the resource loading.
      url_loader_->loadAsynchronously(request, this);
    }
  }
}

void SimpleDataSource::CancelTask() {
  DCHECK(MessageLoop::current() == render_loop_);
  base::AutoLock auto_lock(lock_);
  DCHECK_EQ(state_, STOPPED);

  // Cancel any pending requests.
  if (url_loader_.get()) {
    url_loader_->cancel();
    url_loader_.reset();
  }
}

void SimpleDataSource::DoneInitialization_Locked(bool success) {
  lock_.AssertAcquired();
  media::PipelineStatus status = media::PIPELINE_ERROR_NETWORK;
  if (success) {
    state_ = INITIALIZED;

    UpdateHostState();
    status = media::PIPELINE_OK;
  } else {
    state_ = UNINITIALIZED;
    url_loader_.reset();
  }

  scoped_ptr<media::PipelineStatusCallback> initialize_callback(
      initialize_callback_.release());
  initialize_callback->Run(status);
}

void SimpleDataSource::UpdateHostState() {
  if (host()) {
    host()->SetTotalBytes(size_);
    host()->SetBufferedBytes(size_);
    // If scheme is file or data, say we are loaded.
    host()->SetLoaded(url_.SchemeIsFile() || url_.SchemeIs(kDataScheme));
  }
}

}  // namespace webkit_glue