// Copyright 2018 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/download/internal/background_service/in_memory_download_driver.h"

#include "components/download/internal/background_service/in_memory_download.h"
#include "services/network/public/cpp/resource_request_body.h"

namespace download {

namespace {

DriverEntry::State ToDriverEntryState(InMemoryDownload::State state) {
  switch (state) {
    case InMemoryDownload::State::INITIAL:
      return DriverEntry::State::IN_PROGRESS;
    case InMemoryDownload::State::IN_PROGRESS:
      return DriverEntry::State::IN_PROGRESS;
    case InMemoryDownload::State::FAILED:
      return DriverEntry::State::INTERRUPTED;
    case InMemoryDownload::State::COMPLETE:
      return DriverEntry::State::COMPLETE;
  }
  NOTREACHED();
  return DriverEntry::State::UNKNOWN;
}

// Helper function to create download driver entry based on in memory download.
DriverEntry CreateDriverEntry(const InMemoryDownload& download) {
  DriverEntry entry;
  entry.guid = download.guid();
  entry.state = ToDriverEntryState(download.state());
  entry.paused = download.paused();
  entry.done = entry.state == DriverEntry::State::COMPLETE ||
               entry.state == DriverEntry::State::CANCELLED;
  entry.bytes_downloaded = download.bytes_downloaded();
  entry.url_chain = download.url_chain();
  entry.response_headers = download.response_headers();
  if (entry.response_headers) {
    entry.expected_total_size = entry.response_headers->GetContentLength();
  }
  // Currently incognito mode network backend can't resume in the middle.
  entry.can_resume = false;

  if (download.state() == InMemoryDownload::State::COMPLETE) {
    auto blob_handle = download.ResultAsBlob();
    if (blob_handle)
      entry.blob_handle = base::Optional<storage::BlobDataHandle>(*blob_handle);
  }
  return entry;
}

}  // namespace

InMemoryDownloadFactory::InMemoryDownloadFactory(
    network::mojom::URLLoaderFactory* url_loader_factory,
    BlobTaskProxy::BlobContextGetter blob_context_getter,
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
    : url_loader_factory_(url_loader_factory),
      blob_context_getter_(blob_context_getter),
      io_task_runner_(io_task_runner) {}

InMemoryDownloadFactory::~InMemoryDownloadFactory() = default;

std::unique_ptr<InMemoryDownload> InMemoryDownloadFactory::Create(
    const std::string& guid,
    const RequestParams& request_params,
    scoped_refptr<network::ResourceRequestBody> request_body,
    const net::NetworkTrafficAnnotationTag& traffic_annotation,
    InMemoryDownload::Delegate* delegate) {
  DCHECK(url_loader_factory_);
  return std::make_unique<InMemoryDownloadImpl>(
      guid, request_params, std::move(request_body), traffic_annotation,
      delegate, url_loader_factory_, blob_context_getter_, io_task_runner_);
}

InMemoryDownloadDriver::InMemoryDownloadDriver(
    std::unique_ptr<InMemoryDownload::Factory> download_factory)
    : client_(nullptr), download_factory_(std::move(download_factory)) {}

InMemoryDownloadDriver::~InMemoryDownloadDriver() = default;

void InMemoryDownloadDriver::Initialize(DownloadDriver::Client* client) {
  DCHECK(!client_) << "Initialize can be called only once.";
  client_ = client;
  DCHECK(client_);
  client_->OnDriverReady(true);
}

void InMemoryDownloadDriver::HardRecover() {
  client_->OnDriverHardRecoverComplete(true);
}

bool InMemoryDownloadDriver::IsReady() const {
  return true;
}

void InMemoryDownloadDriver::Start(
    const RequestParams& request_params,
    const std::string& guid,
    const base::FilePath& file_path,
    scoped_refptr<network::ResourceRequestBody> request_body,
    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
  std::unique_ptr<InMemoryDownload> download = download_factory_->Create(
      guid, request_params, std::move(request_body), traffic_annotation, this);
  InMemoryDownload* download_ptr = download.get();
  DCHECK(downloads_.find(guid) == downloads_.end()) << "Existing GUID found.";
  downloads_.emplace(guid, std::move(download));

  download_ptr->Start();
}

void InMemoryDownloadDriver::Remove(const std::string& guid, bool remove_file) {
  downloads_.erase(guid);
}

void InMemoryDownloadDriver::Pause(const std::string& guid) {
  auto it = downloads_.find(guid);
  if (it != downloads_.end())
    it->second->Pause();
}

void InMemoryDownloadDriver::Resume(const std::string& guid) {
  auto it = downloads_.find(guid);
  if (it != downloads_.end())
    it->second->Resume();
}

base::Optional<DriverEntry> InMemoryDownloadDriver::Find(
    const std::string& guid) {
  base::Optional<DriverEntry> entry;
  auto it = downloads_.find(guid);
  if (it != downloads_.end())
    entry = CreateDriverEntry(*it->second);
  return entry;
}

std::set<std::string> InMemoryDownloadDriver::GetActiveDownloads() {
  std::set<std::string> downloads;
  for (const auto& it : downloads_) {
    if (it.second->state() == InMemoryDownload::State::INITIAL ||
        it.second->state() == InMemoryDownload::State::IN_PROGRESS) {
      downloads.emplace(it.first);
    }
  }
  return downloads;
}

size_t InMemoryDownloadDriver::EstimateMemoryUsage() const {
  size_t memory_usage = 0u;
  for (const auto& it : downloads_) {
    memory_usage += it.second->EstimateMemoryUsage();
  }
  return memory_usage;
}

void InMemoryDownloadDriver::OnDownloadStarted(InMemoryDownload* download) {
  DCHECK(client_);
  client_->OnDownloadCreated(CreateDriverEntry(*download));
}

void InMemoryDownloadDriver::OnDownloadProgress(InMemoryDownload* download) {
  DCHECK(client_);
  client_->OnDownloadUpdated(CreateDriverEntry(*download));
}

void InMemoryDownloadDriver::OnDownloadComplete(InMemoryDownload* download) {
  DCHECK(download);
  DCHECK(client_);
  DriverEntry entry = CreateDriverEntry(*download);
  switch (download->state()) {
    case InMemoryDownload::State::FAILED:
      // URLFetcher retries for network failures.
      client_->OnDownloadFailed(entry, FailureType::NOT_RECOVERABLE);
      // Should immediately return in case |client_| removes |download| in
      // OnDownloadFailed.
      return;
    case InMemoryDownload::State::COMPLETE:
      client_->OnDownloadSucceeded(entry);
      // Should immediately return in case |client_| removes |download| in
      // OnDownloadSucceeded.
      return;
    case InMemoryDownload::State::INITIAL:
    case InMemoryDownload::State::IN_PROGRESS:
      NOTREACHED();
      return;
  }
}

void InMemoryDownloadDriver::OnUploadProgress(InMemoryDownload* download) {
  DCHECK(download);
  DCHECK(client_);
  client_->OnUploadProgress(download->guid(), download->bytes_uploaded());
}

}  // namespace download
