// Copyright (c) 2009 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 "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/process_util.h"
#include "chrome/renderer/media/data_source_impl.h"
#include "chrome/renderer/render_view.h"
#include "chrome/renderer/webmediaplayer_delegate_impl.h"
#include "chrome/renderer/render_thread.h"
#include "media/base/filter_host.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "webkit/glue/webappcachecontext.h"

DataSourceImpl::DataSourceImpl(WebMediaPlayerDelegateImpl* delegate)
    : delegate_(delegate),
      render_loop_(RenderThread::current()->message_loop()),
      stopped_(false),
      download_event_(false, false),
      downloaded_bytes_(0),
      total_bytes_(0),
      total_bytes_known_(false),
      download_completed_(false),
      resource_loader_bridge_(NULL),
      read_event_(false, false),
      ALLOW_THIS_IN_INITIALIZER_LIST(
          read_callback_(this, &DataSourceImpl::OnDidFileStreamRead)),
      stream_(NULL),
      last_read_size_(0),
      position_(0),
      io_loop_(delegate->view()->GetMessageLoopForIO()),
      seek_event_(false, false) {
}

DataSourceImpl::~DataSourceImpl() {
}

void DataSourceImpl::Stop() {
  AutoLock auto_lock(lock_);
  if (stopped_)
    return;
  stopped_ = true;

  // Wakes up demuxer waiting on |read_event_| in Read().
  read_event_.Signal();
  // Wakes up demuxer waiting on |seek_event_| in SetPosition().
  seek_event_.Signal();
  // Wakes up demuxer waiting on |download_event_| in Read() or SetPosition().
  download_event_.Signal();

  render_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &DataSourceImpl::OnDestroy));
}

bool DataSourceImpl::Initialize(const std::string& url) {
  media_format_.SetAsString(media::MediaFormat::kMimeType,
                            media::mime_type::kApplicationOctetStream);
  media_format_.SetAsString(media::MediaFormat::kURL, url);
  render_loop_->PostTask(FROM_HERE,
      NewRunnableMethod(this, &DataSourceImpl::OnInitialize, url));
  return true;
}

size_t DataSourceImpl::Read(uint8* data, size_t size) {
  DCHECK(stream_.get());
  // Wait until we have downloaded the requested bytes.
  while (true) {
    {
      AutoLock auto_lock(lock_);
      if (stopped_ || download_completed_ ||
          position_ + size <= downloaded_bytes_)
        break;
    }
    download_event_.Wait();
  }

  last_read_size_ = media::DataSource::kReadError;
  if (logging::DEBUG_MODE) {
    AutoLock auto_lock(lock_);
    DCHECK(stopped_ || download_completed_ ||
           position_ + size <= downloaded_bytes_);
  }

  // Post a task to IO message loop to perform the actual reading.
  bool task_posted = false;
  {
    AutoLock auto_lock(lock_);
    if (!stopped_) {
      io_loop_->PostTask(FROM_HERE,
          NewRunnableMethod(this, &DataSourceImpl::OnReadFileStream,
                            data, size));
      task_posted = true;
    }
  }

  if (task_posted)
    read_event_.Wait();

  {
    AutoLock auto_lock(lock_);
    if (!stopped_)
      return last_read_size_;
    return media::DataSource::kReadError;
  }
}

bool DataSourceImpl::GetPosition(int64* position_out) {
  AutoLock auto_lock(lock_);
  *position_out = position_;
  return true;
}

bool DataSourceImpl::SetPosition(int64 position) {
  DCHECK(stream_.get());
  while (true) {
    {
      AutoLock auto_lock(lock_);
      if (stopped_ || download_completed_ || position < downloaded_bytes_)
        break;
    }
    download_event_.Wait();
  }

  if (logging::DEBUG_MODE) {
    AutoLock auto_lock(lock_);
    DCHECK(stopped_ || download_completed_ || position < downloaded_bytes_);
  }

  // Perform the seek operation on IO message loop.
  bool task_posted = false;
  {
    AutoLock auto_lock(lock_);
    if (!stopped_) {
      io_loop_->PostTask(FROM_HERE,
          NewRunnableMethod(this,
              &DataSourceImpl::OnSeekFileStream, net::FROM_BEGIN, position));
      task_posted =  true;
    }
  }
  if (task_posted)
    seek_event_.Wait();

  if (logging::DEBUG_MODE) {
    AutoLock auto_lock_(lock_);
    DCHECK(stopped_ || position == position_);
  }
  return true;
}

bool DataSourceImpl::GetSize(int64* size_out) {
  AutoLock auto_lock(lock_);
  if (total_bytes_known_) {
    *size_out = total_bytes_;
    return true;
  }
  *size_out = 0;
  return false;
}

bool DataSourceImpl::IsSeekable() {
  // If URI is file then it is seekable.
  // TODO(hclam): make other protocols seekable.
  return uri_.find("file:///") == 0;
}

void DataSourceImpl::OnCreateFileStream(base::PlatformFile file) {
  AutoLock auto_lock(lock_);
  if (stopped_)
    return;
  stream_.reset(
      new net::FileStream(
          file, base::PLATFORM_FILE_READ | base::PLATFORM_FILE_ASYNC));
  // TODO(hclam): maybe we should check the validity of the file handle.
  host_->InitializationComplete();
}

void DataSourceImpl::OnReadFileStream(uint8* data, size_t size) {
  int error = net::ERR_IO_PENDING;
  {
    AutoLock auto_lock(lock_);
    if (!stopped_) {
      // net::FileStream::Read wants a char*, not uint8*.
      char* c_data = reinterpret_cast<char*>(data);
      COMPILE_ASSERT(sizeof(*c_data) == sizeof(*data), data_not_sizeof_char);
      error = stream_->Read(c_data, size, &read_callback_);
    }
  }

  // Since the file handle is asynchronous, return value other than
  // ERROR_IO_PENDING is an error.
  if (error != net::ERR_IO_PENDING) {
    HandleError(media::PIPELINE_ERROR_READ);
  }
}

void DataSourceImpl::OnSeekFileStream(net::Whence whence, int64 position) {
  {
    AutoLock auto_lock(lock_);
    if (!stopped_)
      position_ = stream_->Seek(whence, position);
  }
  seek_event_.Signal();
}

void DataSourceImpl::OnDidFileStreamRead(int size) {
  if (size < 0) {
    HandleError(media::PIPELINE_ERROR_READ);
  } else {
    AutoLock auto_lock(lock_);
    position_ += size;
  }
  last_read_size_ = size;
  read_event_.Signal();
}

void DataSourceImpl::OnInitialize(std::string uri) {
  uri_ = uri;
  // Create the resource loader bridge.
  resource_loader_bridge_.reset(
      RenderThread::current()->resource_dispatcher()->CreateBridge(
      "GET",
      GURL(uri),
      GURL(uri),
      GURL(),         // TODO(hclam): provide referer here.
      "null",         // TODO(abarth): provide frame_origin
      "null",         // TODO(abarth): provide main_frame_origin
      std::string(),  // Provide no header.
      // Prefer to load from cache, also enable downloading the file, the
      // resource will be saved to a single response data file if it's possible.
      net::LOAD_PREFERRING_CACHE | net::LOAD_ENABLE_DOWNLOAD_FILE,
      base::GetCurrentProcId(),
      ResourceType::MEDIA,
      0,
      // TODO(michaeln): delegate->mediaplayer->frame->
      //                    app_cache_context()->context_id()
      // For now don't service media resource requests from the appcache.
      WebAppCacheContext::kNoAppCacheContextId,
      delegate_->view()->routing_id()));
  // Start the resource loading.
  resource_loader_bridge_->Start(this);
}

void DataSourceImpl::OnDestroy() {
  DCHECK(MessageLoop::current() == render_loop_);
  resource_loader_bridge_->Cancel();
  resource_loader_bridge_.reset();
}

void DataSourceImpl::OnDownloadProgress(uint64 position, uint64 size) {
  {
    AutoLock auto_lock(lock_);
    downloaded_bytes_ = position;
    if (!total_bytes_known_) {
      if (size == kuint64max) {
        // If we receive an invalid value for size, we keep on updating the
        // total number of bytes.
        total_bytes_ = position;
      } else {
        total_bytes_ = size;
        total_bytes_known_ = true;
      }
    }
  }
  download_event_.Signal();
}

void DataSourceImpl::OnUploadProgress(uint64 position, uint64 size) {
  // We don't care about upload progress.
}

void DataSourceImpl::OnReceivedRedirect(const GURL& new_url) {
  // TODO(hclam): what to do here? fire another resource request or show an
  // error?
}

void DataSourceImpl::OnReceivedResponse(
    const webkit_glue::ResourceLoaderBridge::ResponseInfo& info,
    bool content_filtered) {
#if defined(OS_POSIX)
  base::PlatformFile response_data_file = info.response_data_file.fd;
#elif defined(OS_WIN)
  base::PlatformFile response_data_file = info.response_data_file;
#endif

  if (response_data_file != base::kInvalidPlatformFileValue) {
    DCHECK(!position_ && !downloaded_bytes_);
    if (info.content_length != -1) {
      total_bytes_known_ = true;
      total_bytes_ = info.content_length;
    }

    {
      // Post a task to the IO message loop to create the file stream.
      // We don't want to post any more tasks once we are stopped.
      AutoLock auto_lock(lock_);
      if (!stopped_) {
        io_loop_->PostTask(FROM_HERE,
            NewRunnableMethod(this, &DataSourceImpl::OnCreateFileStream,
                              response_data_file));
      }
    }
  } else {
    // TODO(hclam): handle the fallback case of using memory buffer here.
    HandleError(media::PIPELINE_ERROR_NETWORK);
  }
}

void DataSourceImpl::OnReceivedData(const char* data, int len) {
  // TODO(hclam): we will get this method call when browser process fails
  // to provide us with a file handle, come up with some fallback mechanism.
}

void DataSourceImpl::OnCompletedRequest(const URLRequestStatus& status,
                                        const std::string& security_info) {
  {
    AutoLock auto_lock(lock_);
    total_bytes_known_ = true;
    download_completed_ = true;
  }
  if (status.status() != URLRequestStatus::SUCCESS) {
    HandleError(media::PIPELINE_ERROR_NETWORK);
  }
}

void DataSourceImpl::HandleError(media::PipelineError error) {
  AutoLock auto_lock(lock_);
  if (!stopped_) {
    host_->Error(error);
  }
}

std::string DataSourceImpl::GetURLForDebugging() {
  return uri_;
}

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