// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_DOWNLOAD_INTERNAL_BACKGROUND_SERVICE_IN_MEMORY_DOWNLOAD_H_
#define COMPONENTS_DOWNLOAD_INTERNAL_BACKGROUND_SERVICE_IN_MEMORY_DOWNLOAD_H_

#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "components/download/internal/background_service/blob_task_proxy.h"
#include "components/download/public/background_service/blob_context_getter_factory.h"
#include "components/download/public/background_service/download_params.h"
#include "components/download/public/background_service/url_loader_factory_getter.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom-forward.h"

class GURL;

namespace net {
struct NetworkTrafficAnnotationTag;
}  // namespace net

namespace storage {
class BlobDataHandle;
}  // namespace storage

namespace download {

struct RequestParams;

// Class to start a single download and hold in-memory download data.
// Used by download service in Incognito mode, where download files shouldn't
// be persisted to disk.
//
// Life cycle: The object is created before sending the network request.
// Call Start() to retrieve the blob storage context and send the network
// request.
class InMemoryDownload {
 public:
  class Delegate {
   public:
    // Report download progress with in-memory download backend.
    virtual void OnDownloadStarted(InMemoryDownload* download) = 0;
    virtual void OnDownloadProgress(InMemoryDownload* download) = 0;
    virtual void OnDownloadComplete(InMemoryDownload* download) = 0;
    virtual void OnUploadProgress(InMemoryDownload* download) = 0;

    // Retrieves the blob storage context getter.
    virtual void RetrieveBlobContextGetter(
        BlobContextGetterCallback callback) = 0;
    // Retrieves browser context specific URLLoaderFactory
    virtual void RetrievedURLLoaderFactory(
        URLLoaderFactoryGetterCallback callback) = 0;

   protected:
    virtual ~Delegate() = default;
  };

  // Factory to create in memory download.
  class Factory {
   public:
    virtual std::unique_ptr<InMemoryDownload> Create(
        const std::string& guid,
        const RequestParams& request_params,
        scoped_refptr<network::ResourceRequestBody> request_body,
        const net::NetworkTrafficAnnotationTag& traffic_annotation,
        Delegate* delegate) = 0;

    virtual ~Factory() = default;
  };

  // States of the download.
  enum class State {
    // The object is just created.
    INITIAL,

    // Waiting to retrieve URLLoaderFactory.
    RETRIEVE_URL_LOADER_FACTIORY,

    // Waiting to retrieve BlobStorageContextGetter.
    RETRIEVE_BLOB_CONTEXT,

    // Download is in progress, including the following procedures.
    // 1. Send the network request and transfer data from network.
    // 2. Save the data to blob storage.
    IN_PROGRESS,

    // The download can fail due to:
    // 1. network layer failure or unsuccessful HTTP server response code.
    // 2. Blob system failures after blob construction is done.
    FAILED,

    // Download is completed, and data is successfully saved as a blob.
    // Guarantee the blob is fully constructed.
    COMPLETE,
  };

  InMemoryDownload(const InMemoryDownload&) = delete;
  InMemoryDownload& operator=(const InMemoryDownload&) = delete;

  virtual ~InMemoryDownload();

  // Send the download request.
  virtual void Start() = 0;

  // Pause the download request.
  virtual void Pause() = 0;

  // Resume the download request.
  virtual void Resume() = 0;

  // Get a copy of blob data handle.
  virtual std::unique_ptr<storage::BlobDataHandle> ResultAsBlob() const = 0;

  // Returns the estimate of dynamically allocated memory in bytes.
  virtual size_t EstimateMemoryUsage() const = 0;

  const std::string& guid() const { return guid_; }
  uint64_t bytes_downloaded() const { return bytes_downloaded_; }
  State state() const { return state_; }
  bool paused() const { return paused_; }
  const base::Time& completion_time() const { return completion_time_; }
  const std::vector<GURL>& url_chain() const { return url_chain_; }
  scoped_refptr<const net::HttpResponseHeaders> response_headers() const {
    return response_headers_;
  }
  uint64_t bytes_uploaded() const { return bytes_uploaded_; }

 protected:
  InMemoryDownload(const std::string& guid);

  // GUID of the download.
  const std::string guid_;

  State state_;

  // If the download is paused.
  bool paused_;

  // Completion time of download when data is saved as blob.
  base::Time completion_time_;

  // The URL request chain of this download.
  std::vector<GURL> url_chain_;

  // HTTP response headers.
  scoped_refptr<const net::HttpResponseHeaders> response_headers_;

  uint64_t bytes_downloaded_;

  uint64_t bytes_uploaded_;
};

// Implementation of InMemoryDownload and uses SimpleURLLoader as network
// backend.
// Threading contract:
// 1. This object lives on the main thread.
// 2. Reading/writing IO buffer from network is done on another thread,
// based on |request_context_getter_|. When complete, main thread is notified.
// 3. After network IO is done, Blob related work is done on IO thread with
// |blob_task_proxy_|, then notify the result to main thread.
class InMemoryDownloadImpl : public network::SimpleURLLoaderStreamConsumer,
                             public InMemoryDownload {
 public:
  InMemoryDownloadImpl(
      const std::string& guid,
      const RequestParams& request_params,
      scoped_refptr<network::ResourceRequestBody> request_body,
      const net::NetworkTrafficAnnotationTag& traffic_annotation,
      Delegate* delegate,
      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);

  InMemoryDownloadImpl(const InMemoryDownloadImpl&) = delete;
  InMemoryDownloadImpl& operator=(const InMemoryDownloadImpl&) = delete;

  ~InMemoryDownloadImpl() override;

 private:
  // InMemoryDownload implementation.
  void Start() override;
  void Pause() override;
  void Resume() override;

  // Called when browser context specific URLLoaderFactory is ready to use.
  void OnRetrievedURLLoaderFactory(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);

  // Called when the BlobStorageContextGetter is ready to use.
  void OnRetrievedBlobContextGetter(BlobContextGetter blob_context_getter);

  std::unique_ptr<storage::BlobDataHandle> ResultAsBlob() const override;
  size_t EstimateMemoryUsage() const override;

  // network::SimpleURLLoaderStreamConsumer implementation.
  void OnDataReceived(std::string_view string_piece,
                      base::OnceClosure resume) override;
  void OnComplete(bool success) override;
  void OnRetry(base::OnceClosure start_retry) override;

  // Saves the download data into blob storage.
  void SaveAsBlob();
  void OnSaveBlobDone(std::unique_ptr<storage::BlobDataHandle> blob_handle,
                      storage::BlobStatus status);

  // Notifies the delegate about completion. Can be called multiple times and
  // |completion_notified_| will ensure the delegate only receive one completion
  // call.
  void NotifyDelegateDownloadComplete();

  // Sends a new network request.
  void SendRequest();

  // Called when the server redirects to another URL.
  void OnRedirect(const GURL& url_before_redirect,
                  const net::RedirectInfo& redirect_info,
                  const network::mojom::URLResponseHead& response_head,
                  std::vector<std::string>* to_be_removed_headers);

  // Called when the response of the final URL is received.
  void OnResponseStarted(const GURL& final_url,
                         const network::mojom::URLResponseHead& response_head);

  void OnUploadProgress(uint64_t position, uint64_t total);

  // Resets local states.
  void Reset();

  // Request parameters of the download.
  const RequestParams request_params_;

  // The request body to upload (if any).
  scoped_refptr<network::ResourceRequestBody> request_body_;

  // Traffic annotation of the request.
  const net::NetworkTrafficAnnotationTag traffic_annotation_;

  // Used to send requests to servers.
  std::unique_ptr<network::SimpleURLLoader> loader_;

  // Used to handle network response.
  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;

  // Worker that does blob related task on IO thread.
  std::unique_ptr<BlobTaskProxy> blob_task_proxy_;

  // Owned blob data handle, so that blob system keeps at least one reference
  // count of the underlying data.
  std::unique_ptr<storage::BlobDataHandle> blob_data_handle_;

  // Used to access blob storage context.
  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;

  raw_ptr<Delegate, DanglingUntriaged> delegate_;

  // Data downloaded from network, should be moved to avoid extra copy.
  std::string data_;

  // Cached callback to let network backend continue to pull data.
  base::OnceClosure resume_callback_;

  // Ensures Delegate::OnDownloadComplete is only called once.
  bool completion_notified_;

  // If |OnResponseStarted| is called.
  bool started_;

  // Bounded to main thread task runner.
  base::WeakPtrFactory<InMemoryDownloadImpl> weak_ptr_factory_{this};
};

}  // namespace download

#endif  // COMPONENTS_DOWNLOAD_INTERNAL_BACKGROUND_SERVICE_IN_MEMORY_DOWNLOAD_H_
