// 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 "google_apis/drive/gdata_wapi_requests.h"

#include "base/location.h"
#include "base/sequenced_task_runner.h"
#include "base/task_runner_util.h"
#include "base/values.h"
#include "google_apis/drive/gdata_wapi_parser.h"
#include "google_apis/drive/gdata_wapi_url_generator.h"
#include "google_apis/drive/request_sender.h"
#include "google_apis/drive/request_util.h"
#include "third_party/libxml/chromium/libxml_utils.h"

using net::URLFetcher;

namespace google_apis {

namespace {

// Parses the JSON value to ResourceList.
scoped_ptr<ResourceList> ParseResourceListOnBlockingPool(
    scoped_ptr<base::Value> value) {
  DCHECK(value);

  return ResourceList::ExtractAndParse(*value);
}

// Runs |callback| with |error| and |resource_list|, but replace the error code
// with GDATA_PARSE_ERROR, if there was a parsing error.
void DidParseResourceListOnBlockingPool(
    const GetResourceListCallback& callback,
    GDataErrorCode error,
    scoped_ptr<ResourceList> resource_list) {
  DCHECK(!callback.is_null());

  // resource_list being NULL indicates there was a parsing error.
  if (!resource_list)
    error = GDATA_PARSE_ERROR;

  callback.Run(error, resource_list.Pass());
}

// Parses the JSON value to ResourceList on the blocking pool and runs
// |callback| on the UI thread once parsing is done.
void ParseResourceListAndRun(
    scoped_refptr<base::TaskRunner> blocking_task_runner,
    const GetResourceListCallback& callback,
    GDataErrorCode error,
    scoped_ptr<base::Value> value) {
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(error, scoped_ptr<ResourceList>());
    return;
  }

  base::PostTaskAndReplyWithResult(
      blocking_task_runner,
      FROM_HERE,
      base::Bind(&ParseResourceListOnBlockingPool, base::Passed(&value)),
      base::Bind(&DidParseResourceListOnBlockingPool, callback, error));
}

// Parses the JSON value to AccountMetadata and runs |callback| on the UI
// thread once parsing is done.
void ParseAccounetMetadataAndRun(const GetAccountMetadataCallback& callback,
                                 GDataErrorCode error,
                                 scoped_ptr<base::Value> value) {
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(error, scoped_ptr<AccountMetadata>());
    return;
  }

  // Parsing AccountMetadata is cheap enough to do on UI thread.
  scoped_ptr<AccountMetadata> entry =
      google_apis::AccountMetadata::CreateFrom(*value);
  if (!entry) {
    callback.Run(GDATA_PARSE_ERROR, scoped_ptr<AccountMetadata>());
    return;
  }

  callback.Run(error, entry.Pass());
}

// Parses the |value| to ResourceEntry with error handling.
// This is designed to be used for ResumeUploadRequest and
// GetUploadStatusRequest.
scoped_ptr<ResourceEntry> ParseResourceEntry(scoped_ptr<base::Value> value) {
  scoped_ptr<ResourceEntry> entry;
  if (value.get()) {
    entry = ResourceEntry::ExtractAndParse(*value);

    // Note: |value| may be NULL, in particular if the callback is for a
    // failure.
    if (!entry.get())
      LOG(WARNING) << "Invalid entry received on upload.";
  }

  return entry.Pass();
}

// Extracts the open link url from the JSON Feed. Used by AuthorizeApp().
void ParseOpenLinkAndRun(const std::string& app_id,
                         const AuthorizeAppCallback& callback,
                         GDataErrorCode error,
                         scoped_ptr<base::Value> value) {
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(error, GURL());
    return;
  }

  // Parsing ResourceEntry is cheap enough to do on UI thread.
  scoped_ptr<ResourceEntry> resource_entry = ParseResourceEntry(value.Pass());
  if (!resource_entry) {
    callback.Run(GDATA_PARSE_ERROR, GURL());
    return;
  }

  // Look for the link to open the file with the app with |app_id|.
  const ScopedVector<Link>& resource_links = resource_entry->links();
  GURL open_link;
  for (size_t i = 0; i < resource_links.size(); ++i) {
    const Link& link = *resource_links[i];
    if (link.type() == Link::LINK_OPEN_WITH && link.app_id() == app_id) {
      open_link = link.href();
      break;
    }
  }

  if (open_link.is_empty())
    error = GDATA_OTHER_ERROR;

  callback.Run(error, open_link);
}

}  // namespace

//============================ GetResourceListRequest ========================

GetResourceListRequest::GetResourceListRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const GURL& override_url,
    int64 start_changestamp,
    const std::string& search_string,
    const std::string& directory_resource_id,
    const GetResourceListCallback& callback)
    : GetDataRequest(
          sender,
          base::Bind(&ParseResourceListAndRun,
                     make_scoped_refptr(sender->blocking_task_runner()),
                     callback)),
      url_generator_(url_generator),
      override_url_(override_url),
      start_changestamp_(start_changestamp),
      search_string_(search_string),
      directory_resource_id_(directory_resource_id) {
  DCHECK(!callback.is_null());
}

GetResourceListRequest::~GetResourceListRequest() {}

GURL GetResourceListRequest::GetURL() const {
  return url_generator_.GenerateResourceListUrl(override_url_,
                                                start_changestamp_,
                                                search_string_,
                                                directory_resource_id_);
}

//============================ SearchByTitleRequest ==========================

SearchByTitleRequest::SearchByTitleRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const std::string& title,
    const std::string& directory_resource_id,
    const GetResourceListCallback& callback)
    : GetDataRequest(
          sender,
          base::Bind(&ParseResourceListAndRun,
                     make_scoped_refptr(sender->blocking_task_runner()),
                     callback)),
      url_generator_(url_generator),
      title_(title),
      directory_resource_id_(directory_resource_id) {
  DCHECK(!callback.is_null());
}

SearchByTitleRequest::~SearchByTitleRequest() {}

GURL SearchByTitleRequest::GetURL() const {
  return url_generator_.GenerateSearchByTitleUrl(
      title_, directory_resource_id_);
}

//============================ GetResourceEntryRequest =======================

GetResourceEntryRequest::GetResourceEntryRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const std::string& resource_id,
    const GURL& embed_origin,
    const GetDataCallback& callback)
    : GetDataRequest(sender, callback),
      url_generator_(url_generator),
      resource_id_(resource_id),
      embed_origin_(embed_origin) {
  DCHECK(!callback.is_null());
}

GetResourceEntryRequest::~GetResourceEntryRequest() {}

GURL GetResourceEntryRequest::GetURL() const {
  return url_generator_.GenerateEditUrlWithEmbedOrigin(
      resource_id_, embed_origin_);
}

//========================= GetAccountMetadataRequest ========================

GetAccountMetadataRequest::GetAccountMetadataRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const GetAccountMetadataCallback& callback,
    bool include_installed_apps)
    : GetDataRequest(sender,
                     base::Bind(&ParseAccounetMetadataAndRun, callback)),
      url_generator_(url_generator),
      include_installed_apps_(include_installed_apps) {
  DCHECK(!callback.is_null());
}

GetAccountMetadataRequest::~GetAccountMetadataRequest() {}

GURL GetAccountMetadataRequest::GetURL() const {
  return url_generator_.GenerateAccountMetadataUrl(include_installed_apps_);
}

//=========================== DeleteResourceRequest ==========================

DeleteResourceRequest::DeleteResourceRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const EntryActionCallback& callback,
    const std::string& resource_id,
    const std::string& etag)
    : EntryActionRequest(sender, callback),
      url_generator_(url_generator),
      resource_id_(resource_id),
      etag_(etag) {
  DCHECK(!callback.is_null());
}

DeleteResourceRequest::~DeleteResourceRequest() {}

GURL DeleteResourceRequest::GetURL() const {
  return url_generator_.GenerateEditUrl(resource_id_);
}

URLFetcher::RequestType DeleteResourceRequest::GetRequestType() const {
  return URLFetcher::DELETE_REQUEST;
}

std::vector<std::string>
DeleteResourceRequest::GetExtraRequestHeaders() const {
  std::vector<std::string> headers;
  headers.push_back(util::GenerateIfMatchHeader(etag_));
  return headers;
}

//========================== CreateDirectoryRequest ==========================

CreateDirectoryRequest::CreateDirectoryRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const GetDataCallback& callback,
    const std::string& parent_resource_id,
    const std::string& directory_title)
    : GetDataRequest(sender, callback),
      url_generator_(url_generator),
      parent_resource_id_(parent_resource_id),
      directory_title_(directory_title) {
  DCHECK(!callback.is_null());
}

CreateDirectoryRequest::~CreateDirectoryRequest() {}

GURL CreateDirectoryRequest::GetURL() const {
  return url_generator_.GenerateContentUrl(parent_resource_id_);
}

URLFetcher::RequestType
CreateDirectoryRequest::GetRequestType() const {
  return URLFetcher::POST;
}

bool CreateDirectoryRequest::GetContentData(std::string* upload_content_type,
                                            std::string* upload_content) {
  upload_content_type->assign("application/atom+xml");
  XmlWriter xml_writer;
  xml_writer.StartWriting();
  xml_writer.StartElement("entry");
  xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");

  xml_writer.StartElement("category");
  xml_writer.AddAttribute("scheme",
                          "http://schemas.google.com/g/2005#kind");
  xml_writer.AddAttribute("term",
                          "http://schemas.google.com/docs/2007#folder");
  xml_writer.EndElement();  // Ends "category" element.

  xml_writer.WriteElement("title", directory_title_);

  xml_writer.EndElement();  // Ends "entry" element.
  xml_writer.StopWriting();
  upload_content->assign(xml_writer.GetWrittenString());
  DVLOG(1) << "CreateDirectory data: " << *upload_content_type << ", ["
           << *upload_content << "]";
  return true;
}

//=========================== RenameResourceRequest ==========================

RenameResourceRequest::RenameResourceRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const EntryActionCallback& callback,
    const std::string& resource_id,
    const std::string& new_title)
    : EntryActionRequest(sender, callback),
      url_generator_(url_generator),
      resource_id_(resource_id),
      new_title_(new_title) {
  DCHECK(!callback.is_null());
}

RenameResourceRequest::~RenameResourceRequest() {}

URLFetcher::RequestType RenameResourceRequest::GetRequestType() const {
  return URLFetcher::PUT;
}

std::vector<std::string>
RenameResourceRequest::GetExtraRequestHeaders() const {
  std::vector<std::string> headers;
  headers.push_back(util::kIfMatchAllHeader);
  return headers;
}

GURL RenameResourceRequest::GetURL() const {
  return url_generator_.GenerateEditUrl(resource_id_);
}

bool RenameResourceRequest::GetContentData(std::string* upload_content_type,
                                           std::string* upload_content) {
  upload_content_type->assign("application/atom+xml");
  XmlWriter xml_writer;
  xml_writer.StartWriting();
  xml_writer.StartElement("entry");
  xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");

  xml_writer.WriteElement("title", new_title_);

  xml_writer.EndElement();  // Ends "entry" element.
  xml_writer.StopWriting();
  upload_content->assign(xml_writer.GetWrittenString());
  DVLOG(1) << "RenameResourceRequest data: " << *upload_content_type << ", ["
           << *upload_content << "]";
  return true;
}

//=========================== AuthorizeAppRequest ==========================

AuthorizeAppRequest::AuthorizeAppRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const AuthorizeAppCallback& callback,
    const std::string& resource_id,
    const std::string& app_id)
    : GetDataRequest(sender,
                     base::Bind(&ParseOpenLinkAndRun, app_id, callback)),
      url_generator_(url_generator),
      resource_id_(resource_id),
      app_id_(app_id) {
  DCHECK(!callback.is_null());
}

AuthorizeAppRequest::~AuthorizeAppRequest() {}

URLFetcher::RequestType AuthorizeAppRequest::GetRequestType() const {
  return URLFetcher::PUT;
}

std::vector<std::string>
AuthorizeAppRequest::GetExtraRequestHeaders() const {
  std::vector<std::string> headers;
  headers.push_back(util::kIfMatchAllHeader);
  return headers;
}

bool AuthorizeAppRequest::GetContentData(std::string* upload_content_type,
                                         std::string* upload_content) {
  upload_content_type->assign("application/atom+xml");
  XmlWriter xml_writer;
  xml_writer.StartWriting();
  xml_writer.StartElement("entry");
  xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
  xml_writer.AddAttribute("xmlns:docs", "http://schemas.google.com/docs/2007");
  xml_writer.WriteElement("docs:authorizedApp", app_id_);

  xml_writer.EndElement();  // Ends "entry" element.
  xml_writer.StopWriting();
  upload_content->assign(xml_writer.GetWrittenString());
  DVLOG(1) << "AuthorizeAppRequest data: " << *upload_content_type << ", ["
           << *upload_content << "]";
  return true;
}

GURL AuthorizeAppRequest::GetURL() const {
  return url_generator_.GenerateEditUrl(resource_id_);
}

//======================= AddResourceToDirectoryRequest ======================

AddResourceToDirectoryRequest::AddResourceToDirectoryRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const EntryActionCallback& callback,
    const std::string& parent_resource_id,
    const std::string& resource_id)
    : EntryActionRequest(sender, callback),
      url_generator_(url_generator),
      parent_resource_id_(parent_resource_id),
      resource_id_(resource_id) {
  DCHECK(!callback.is_null());
}

AddResourceToDirectoryRequest::~AddResourceToDirectoryRequest() {}

GURL AddResourceToDirectoryRequest::GetURL() const {
  return url_generator_.GenerateContentUrl(parent_resource_id_);
}

URLFetcher::RequestType
AddResourceToDirectoryRequest::GetRequestType() const {
  return URLFetcher::POST;
}

bool AddResourceToDirectoryRequest::GetContentData(
    std::string* upload_content_type, std::string* upload_content) {
  upload_content_type->assign("application/atom+xml");
  XmlWriter xml_writer;
  xml_writer.StartWriting();
  xml_writer.StartElement("entry");
  xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");

  xml_writer.WriteElement(
      "id", url_generator_.GenerateEditUrlWithoutParams(resource_id_).spec());

  xml_writer.EndElement();  // Ends "entry" element.
  xml_writer.StopWriting();
  upload_content->assign(xml_writer.GetWrittenString());
  DVLOG(1) << "AddResourceToDirectoryRequest data: " << *upload_content_type
           << ", [" << *upload_content << "]";
  return true;
}

//==================== RemoveResourceFromDirectoryRequest ====================

RemoveResourceFromDirectoryRequest::RemoveResourceFromDirectoryRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const EntryActionCallback& callback,
    const std::string& parent_resource_id,
    const std::string& document_resource_id)
    : EntryActionRequest(sender, callback),
      url_generator_(url_generator),
      resource_id_(document_resource_id),
      parent_resource_id_(parent_resource_id) {
  DCHECK(!callback.is_null());
}

RemoveResourceFromDirectoryRequest::~RemoveResourceFromDirectoryRequest() {
}

GURL RemoveResourceFromDirectoryRequest::GetURL() const {
  return url_generator_.GenerateResourceUrlForRemoval(
      parent_resource_id_, resource_id_);
}

URLFetcher::RequestType
RemoveResourceFromDirectoryRequest::GetRequestType() const {
  return URLFetcher::DELETE_REQUEST;
}

std::vector<std::string>
RemoveResourceFromDirectoryRequest::GetExtraRequestHeaders() const {
  std::vector<std::string> headers;
  headers.push_back(util::kIfMatchAllHeader);
  return headers;
}

//======================= InitiateUploadNewFileRequest =======================

InitiateUploadNewFileRequest::InitiateUploadNewFileRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const InitiateUploadCallback& callback,
    const std::string& content_type,
    int64 content_length,
    const std::string& parent_resource_id,
    const std::string& title)
    : InitiateUploadRequestBase(sender, callback, content_type, content_length),
      url_generator_(url_generator),
      parent_resource_id_(parent_resource_id),
      title_(title) {
}

InitiateUploadNewFileRequest::~InitiateUploadNewFileRequest() {}

GURL InitiateUploadNewFileRequest::GetURL() const {
  return url_generator_.GenerateInitiateUploadNewFileUrl(parent_resource_id_);
}

net::URLFetcher::RequestType
InitiateUploadNewFileRequest::GetRequestType() const {
  return net::URLFetcher::POST;
}

bool InitiateUploadNewFileRequest::GetContentData(
    std::string* upload_content_type,
    std::string* upload_content) {
  upload_content_type->assign("application/atom+xml");
  XmlWriter xml_writer;
  xml_writer.StartWriting();
  xml_writer.StartElement("entry");
  xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
  xml_writer.AddAttribute("xmlns:docs",
                          "http://schemas.google.com/docs/2007");
  xml_writer.WriteElement("title", title_);
  xml_writer.EndElement();  // Ends "entry" element.
  xml_writer.StopWriting();
  upload_content->assign(xml_writer.GetWrittenString());
  DVLOG(1) << "InitiateUploadNewFile: " << *upload_content_type << ", ["
           << *upload_content << "]";
  return true;
}

//===================== InitiateUploadExistingFileRequest ====================

InitiateUploadExistingFileRequest::InitiateUploadExistingFileRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const InitiateUploadCallback& callback,
    const std::string& content_type,
    int64 content_length,
    const std::string& resource_id,
    const std::string& etag)
    : InitiateUploadRequestBase(sender, callback, content_type, content_length),
      url_generator_(url_generator),
      resource_id_(resource_id),
      etag_(etag) {
}

InitiateUploadExistingFileRequest::~InitiateUploadExistingFileRequest() {}

GURL InitiateUploadExistingFileRequest::GetURL() const {
  return url_generator_.GenerateInitiateUploadExistingFileUrl(resource_id_);
}

net::URLFetcher::RequestType
InitiateUploadExistingFileRequest::GetRequestType() const {
  return net::URLFetcher::PUT;
}

bool InitiateUploadExistingFileRequest::GetContentData(
    std::string* upload_content_type,
    std::string* upload_content) {
  // According to the document there is no need to send the content-type.
  // However, the server would return 500 server error without the
  // content-type.
  // As its workaround, send "text/plain" content-type here.
  *upload_content_type = "text/plain";
  *upload_content = "";
  return true;
}

std::vector<std::string>
InitiateUploadExistingFileRequest::GetExtraRequestHeaders() const {
  std::vector<std::string> headers(
      InitiateUploadRequestBase::GetExtraRequestHeaders());
  headers.push_back(util::GenerateIfMatchHeader(etag_));
  return headers;
}

//============================ ResumeUploadRequest ===========================

ResumeUploadRequest::ResumeUploadRequest(
    RequestSender* sender,
    const UploadRangeCallback& callback,
    const ProgressCallback& progress_callback,
    const GURL& upload_location,
    int64 start_position,
    int64 end_position,
    int64 content_length,
    const std::string& content_type,
    const base::FilePath& local_file_path)
    : ResumeUploadRequestBase(sender,
                              upload_location,
                              start_position,
                              end_position,
                              content_length,
                              content_type,
                              local_file_path),
      callback_(callback),
      progress_callback_(progress_callback) {
  DCHECK(!callback_.is_null());
}

ResumeUploadRequest::~ResumeUploadRequest() {}

void ResumeUploadRequest::OnRangeRequestComplete(
    const UploadRangeResponse& response, scoped_ptr<base::Value> value) {
  callback_.Run(response, ParseResourceEntry(value.Pass()));
}

void ResumeUploadRequest::OnURLFetchUploadProgress(
    const URLFetcher* source, int64 current, int64 total) {
  if (!progress_callback_.is_null())
    progress_callback_.Run(current, total);
}

//========================== GetUploadStatusRequest ==========================

GetUploadStatusRequest::GetUploadStatusRequest(
    RequestSender* sender,
    const UploadRangeCallback& callback,
    const GURL& upload_url,
    int64 content_length)
    : GetUploadStatusRequestBase(sender, upload_url, content_length),
      callback_(callback) {
  DCHECK(!callback.is_null());
}

GetUploadStatusRequest::~GetUploadStatusRequest() {}

void GetUploadStatusRequest::OnRangeRequestComplete(
    const UploadRangeResponse& response, scoped_ptr<base::Value> value) {
  callback_.Run(response, ParseResourceEntry(value.Pass()));
}

//========================== DownloadFileRequest ==========================

DownloadFileRequest::DownloadFileRequest(
    RequestSender* sender,
    const GDataWapiUrlGenerator& url_generator,
    const DownloadActionCallback& download_action_callback,
    const GetContentCallback& get_content_callback,
    const ProgressCallback& progress_callback,
    const std::string& resource_id,
    const base::FilePath& output_file_path)
    : DownloadFileRequestBase(
          sender,
          download_action_callback,
          get_content_callback,
          progress_callback,
          url_generator.GenerateDownloadFileUrl(resource_id),
          output_file_path) {
}

DownloadFileRequest::~DownloadFileRequest() {
}

}  // namespace google_apis