// Copyright 2017 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 "content/browser/background_fetch/background_fetch_job_controller.h"

#include <utility>

#include "content/browser/background_fetch/background_fetch_request_manager.h"
#include "content/public/browser/browser_thread.h"

namespace content {

BackgroundFetchJobController::BackgroundFetchJobController(
    BackgroundFetchDelegateProxy* delegate_proxy,
    const BackgroundFetchRegistrationId& registration_id,
    const BackgroundFetchOptions& options,
    const SkBitmap& icon,
    uint64_t bytes_downloaded,
    BackgroundFetchRequestManager* request_manager,
    ProgressCallback progress_callback,
    BackgroundFetchScheduler::FinishedCallback finished_callback)
    : BackgroundFetchScheduler::Controller(registration_id,
                                           std::move(finished_callback)),
      options_(options),
      icon_(icon),
      complete_requests_downloaded_bytes_cache_(bytes_downloaded),
      request_manager_(request_manager),
      delegate_proxy_(delegate_proxy),
      progress_callback_(std::move(progress_callback)),
      weak_ptr_factory_(this) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
}

void BackgroundFetchJobController::InitializeRequestStatus(
    int completed_downloads,
    int total_downloads,
    const std::vector<std::string>& outstanding_guids,
    const std::string& ui_title) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // Don't allow double initialization.
  DCHECK_GT(total_downloads, 0);
  DCHECK_EQ(total_downloads_, 0);

  completed_downloads_ = completed_downloads;
  total_downloads_ = total_downloads;

  // TODO(nator): Update this when we support uploads.
  int total_downloads_size = options_.download_total;

  auto fetch_description = std::make_unique<BackgroundFetchDescription>(
      registration_id().unique_id(), ui_title, registration_id().origin(),
      icon_, completed_downloads, total_downloads,
      complete_requests_downloaded_bytes_cache_, total_downloads_size,
      outstanding_guids);

  delegate_proxy_->CreateDownloadJob(GetWeakPtr(),
                                     std::move(fetch_description));
}

BackgroundFetchJobController::~BackgroundFetchJobController() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
}

bool BackgroundFetchJobController::HasMoreRequests() {
  return completed_downloads_ < total_downloads_;
}

void BackgroundFetchJobController::StartRequest(
    scoped_refptr<BackgroundFetchRequestInfo> request) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK_LT(completed_downloads_, total_downloads_);
  DCHECK(request);

  active_request_download_bytes_[request->download_guid()] = 0;

  delegate_proxy_->StartRequest(registration_id().unique_id(),
                                registration_id().origin(), request);
}

void BackgroundFetchJobController::DidStartRequest(
    const scoped_refptr<BackgroundFetchRequestInfo>& request) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // TODO(delphick): Either add CORS check here or remove this function and do
  // the CORS check in BackgroundFetchDelegateImpl (since
  // download::Client::OnDownloadStarted returns a value that can abort the
  // download).
}

void BackgroundFetchJobController::DidUpdateRequest(
    const scoped_refptr<BackgroundFetchRequestInfo>& request,
    uint64_t bytes_downloaded) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  const std::string& download_guid = request->download_guid();
  if (active_request_download_bytes_[download_guid] == bytes_downloaded)
    return;

  active_request_download_bytes_[download_guid] = bytes_downloaded;

  progress_callback_.Run(registration_id().unique_id(), options_.download_total,
                         complete_requests_downloaded_bytes_cache_ +
                             GetInProgressDownloadedBytes());
}

void BackgroundFetchJobController::DidCompleteRequest(
    const scoped_refptr<BackgroundFetchRequestInfo>& request) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  active_request_download_bytes_.erase(request->download_guid());
  complete_requests_downloaded_bytes_cache_ += request->GetFileSize();
  ++completed_downloads_;

  request_manager_->MarkRequestAsComplete(registration_id(), request.get());
}

void BackgroundFetchJobController::UpdateUI(const std::string& title) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  delegate_proxy_->UpdateUI(registration_id().unique_id(), title);
}

uint64_t BackgroundFetchJobController::GetInProgressDownloadedBytes() {
  uint64_t sum = 0;
  for (const auto& entry : active_request_download_bytes_)
    sum += entry.second;
  return sum;
}

void BackgroundFetchJobController::Abort(
    BackgroundFetchReasonToAbort reason_to_abort) {
  delegate_proxy_->Abort(registration_id().unique_id());

  std::vector<std::string> aborted_guids;
  for (const auto& pair : active_request_download_bytes_)
    aborted_guids.push_back(pair.first);
  request_manager_->OnJobAborted(registration_id(), std::move(aborted_guids));
  if (reason_to_abort != BackgroundFetchReasonToAbort::ABORTED_BY_DEVELOPER) {
    // Don't call Finish() here, so that we don't mark data for deletion while
    // there are active fetches.
    // Once the controller finishes processing, this function will be called
    // again. (BackgroundFetchScheduler's finished_callback_ will call
    // BackgroundFetchJobController::Abort() with |cancel_download| set to
    // true.)
    Finish(reason_to_abort);
  }
}

}  // namespace content
