// Copyright 2014 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 "components/domain_reliability/uploader.h"

#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/scoped_vector.h"
#include "base/metrics/sparse_histogram.h"
#include "base/stl_util.h"
#include "base/supports_user_data.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.h"

namespace domain_reliability {

namespace {

const char* kJsonMimeType = "application/json; charset=utf-8";

class UploadUserData : public base::SupportsUserData::Data {
 public:
  static net::URLFetcher::CreateDataCallback CreateCreateDataCallback() {
    return base::Bind(&UploadUserData::CreateUploadUserData);
  }

  static const void* kUserDataKey;

 private:
  static base::SupportsUserData::Data* CreateUploadUserData() {
    return new UploadUserData();
  }
};

const void* UploadUserData::kUserDataKey =
    static_cast<const void*>(&UploadUserData::kUserDataKey);

class DomainReliabilityUploaderImpl
    : public DomainReliabilityUploader, net::URLFetcherDelegate {
 public:
  DomainReliabilityUploaderImpl(const scoped_refptr<
      net::URLRequestContextGetter>& url_request_context_getter)
      : url_request_context_getter_(url_request_context_getter),
        discard_uploads_(true) {}

  virtual ~DomainReliabilityUploaderImpl() {
    // Delete any in-flight URLFetchers.
    STLDeleteContainerPairFirstPointers(
        upload_callbacks_.begin(), upload_callbacks_.end());
  }

  // DomainReliabilityUploader implementation:
  virtual void UploadReport(
      const std::string& report_json,
      const GURL& upload_url,
      const DomainReliabilityUploader::UploadCallback& callback) OVERRIDE {
    VLOG(1) << "Uploading report to " << upload_url;
    VLOG(2) << "Report JSON: " << report_json;

    if (discard_uploads_) {
      VLOG(1) << "Discarding report instead of uploading.";
      callback.Run(true);
      return;
    }

    net::URLFetcher* fetcher =
        net::URLFetcher::Create(0, upload_url, net::URLFetcher::POST, this);
    fetcher->SetRequestContext(url_request_context_getter_.get());
    fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
                          net::LOAD_DO_NOT_SAVE_COOKIES);
    fetcher->SetUploadData(kJsonMimeType, report_json);
    fetcher->SetAutomaticallyRetryOn5xx(false);
    fetcher->SetURLRequestUserData(
        UploadUserData::kUserDataKey,
        UploadUserData::CreateCreateDataCallback());
    fetcher->Start();

    upload_callbacks_[fetcher] = callback;
  }

  virtual void set_discard_uploads(bool discard_uploads) OVERRIDE {
    discard_uploads_ = discard_uploads;
    VLOG(1) << "Setting discard_uploads to " << discard_uploads;
  }

  // net::URLFetcherDelegate implementation:
  virtual void OnURLFetchComplete(
      const net::URLFetcher* fetcher) OVERRIDE {
    DCHECK(fetcher);

    UploadCallbackMap::iterator callback_it = upload_callbacks_.find(fetcher);
    DCHECK(callback_it != upload_callbacks_.end());

    VLOG(1) << "Upload finished with " << fetcher->GetResponseCode();

    UMA_HISTOGRAM_SPARSE_SLOWLY("DomainReliability.UploadResponseCode",
                                fetcher->GetResponseCode());

    bool success = fetcher->GetResponseCode() == 200;
    callback_it->second.Run(success);

    delete callback_it->first;
    upload_callbacks_.erase(callback_it);
  }

 private:
  using DomainReliabilityUploader::UploadCallback;
  typedef std::map<const net::URLFetcher*, UploadCallback> UploadCallbackMap;

  scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
  UploadCallbackMap upload_callbacks_;
  bool discard_uploads_;
};

}  // namespace

DomainReliabilityUploader::DomainReliabilityUploader() {}
DomainReliabilityUploader::~DomainReliabilityUploader() {}

// static
scoped_ptr<DomainReliabilityUploader> DomainReliabilityUploader::Create(
    const scoped_refptr<net::URLRequestContextGetter>&
        url_request_context_getter) {
  return scoped_ptr<DomainReliabilityUploader>(
      new DomainReliabilityUploaderImpl(url_request_context_getter));
}

// static
bool DomainReliabilityUploader::URLRequestIsUpload(
    const net::URLRequest& request) {
  return request.GetUserData(UploadUserData::kUserDataKey) != NULL;
}

}  // namespace domain_reliability