// 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.

#ifndef QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_
#define QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_

#include <list>
#include <map>
#include <memory>
#include <vector>

#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/http/spdy_utils.h"
#include "quiche/quic/platform/api/quic_mutex.h"
#include "quiche/quic/tools/quic_backend_response.h"
#include "quiche/quic/tools/quic_simple_server_backend.h"
#include "quiche/quic/tools/quic_url.h"
#include "quiche/spdy/core/http2_header_block.h"
#include "quiche/spdy/core/spdy_framer.h"

namespace quic {

// In-memory cache for HTTP responses.
// Reads from disk cache generated by:
// `wget -p --save_headers <url>`
class QuicMemoryCacheBackend : public QuicSimpleServerBackend {
 public:
  // Class to manage loading a resource file into memory.  There are
  // two uses: called by InitializeBackend to load resources
  // from files, and recursively called when said resources specify
  // server push associations.
  class ResourceFile {
   public:
    explicit ResourceFile(const std::string& file_name);
    ResourceFile(const ResourceFile&) = delete;
    ResourceFile& operator=(const ResourceFile&) = delete;
    virtual ~ResourceFile();

    void Read();

    // |base| is |file_name_| with |cache_directory| prefix stripped.
    void SetHostPathFromBase(absl::string_view base);

    const std::string& file_name() { return file_name_; }

    absl::string_view host() { return host_; }

    absl::string_view path() { return path_; }

    const spdy::Http2HeaderBlock& spdy_headers() { return spdy_headers_; }

    absl::string_view body() { return body_; }

    const std::vector<absl::string_view>& push_urls() { return push_urls_; }

   private:
    void HandleXOriginalUrl();
    absl::string_view RemoveScheme(absl::string_view url);

    std::string file_name_;
    std::string file_contents_;
    absl::string_view body_;
    spdy::Http2HeaderBlock spdy_headers_;
    absl::string_view x_original_url_;
    std::vector<absl::string_view> push_urls_;
    std::string host_;
    std::string path_;
  };

  QuicMemoryCacheBackend();
  QuicMemoryCacheBackend(const QuicMemoryCacheBackend&) = delete;
  QuicMemoryCacheBackend& operator=(const QuicMemoryCacheBackend&) = delete;
  ~QuicMemoryCacheBackend() override;

  // Retrieve a response from this cache for a given host and path..
  // If no appropriate response exists, nullptr is returned.
  const QuicBackendResponse* GetResponse(absl::string_view host,
                                         absl::string_view path) const;

  // Adds a simple response to the cache.  The response headers will
  // only contain the "content-length" header with the length of |body|.
  void AddSimpleResponse(absl::string_view host, absl::string_view path,
                         int response_code, absl::string_view body);

  // Add a simple response to the cache as AddSimpleResponse() does, and add
  // some server push resources(resource path, corresponding response status and
  // path) associated with it.
  // Push resource implicitly come from the same host.
  // TODO(b/171463363): Remove.
  void AddSimpleResponseWithServerPushResources(
      absl::string_view host, absl::string_view path, int response_code,
      absl::string_view body,
      std::list<QuicBackendResponse::ServerPushInfo> push_resources);

  // Add a response to the cache.
  void AddResponse(absl::string_view host, absl::string_view path,
                   spdy::Http2HeaderBlock response_headers,
                   absl::string_view response_body);

  // Add a response, with trailers, to the cache.
  void AddResponse(absl::string_view host, absl::string_view path,
                   spdy::Http2HeaderBlock response_headers,
                   absl::string_view response_body,
                   spdy::Http2HeaderBlock response_trailers);

  // Add a response, with 103 Early Hints, to the cache.
  void AddResponseWithEarlyHints(
      absl::string_view host, absl::string_view path,
      spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
      const std::vector<spdy::Http2HeaderBlock>& early_hints);

  // Simulate a special behavior at a particular path.
  void AddSpecialResponse(
      absl::string_view host, absl::string_view path,
      QuicBackendResponse::SpecialResponseType response_type);

  void AddSpecialResponse(
      absl::string_view host, absl::string_view path,
      spdy::Http2HeaderBlock response_headers, absl::string_view response_body,
      QuicBackendResponse::SpecialResponseType response_type);

  // Finds a response with the given host and path, and assign it a simulated
  // delay. Returns true if the requisite response was found and the delay was
  // set.
  bool SetResponseDelay(absl::string_view host, absl::string_view path,
                        QuicTime::Delta delay);

  // Sets a default response in case of cache misses.  Takes ownership of
  // 'response'.
  void AddDefaultResponse(QuicBackendResponse* response);

  // Once called, URLs which have a numeric path will send a dynamically
  // generated response of that many bytes.
  void GenerateDynamicResponses();

  void EnableWebTransport();

  // Find all the server push resources associated with |request_url|.
  // TODO(b/171463363): Remove.
  std::list<QuicBackendResponse::ServerPushInfo> GetServerPushResources(
      std::string request_url);

  // Implements the functions for interface QuicSimpleServerBackend
  // |cache_cirectory| can be generated using `wget -p --save-headers <url>`.
  bool InitializeBackend(const std::string& cache_directory) override;
  bool IsBackendInitialized() const override;
  void FetchResponseFromBackend(
      const spdy::Http2HeaderBlock& request_headers,
      const std::string& request_body,
      QuicSimpleServerBackend::RequestHandler* quic_stream) override;
  void CloseBackendResponseStream(
      QuicSimpleServerBackend::RequestHandler* quic_stream) override;
  WebTransportResponse ProcessWebTransportRequest(
      const spdy::Http2HeaderBlock& request_headers,
      WebTransportSession* session) override;
  bool SupportsWebTransport() override { return enable_webtransport_; }

 private:
  void AddResponseImpl(absl::string_view host, absl::string_view path,
                       QuicBackendResponse::SpecialResponseType response_type,
                       spdy::Http2HeaderBlock response_headers,
                       absl::string_view response_body,
                       spdy::Http2HeaderBlock response_trailers,
                       const std::vector<spdy::Http2HeaderBlock>& early_hints);

  std::string GetKey(absl::string_view host, absl::string_view path) const;

  // Add some server push urls with given responses for specified
  // request if these push resources are not associated with this request yet.
  // TODO(b/171463363): Remove.
  void MaybeAddServerPushResources(
      absl::string_view request_host, absl::string_view request_path,
      std::list<QuicBackendResponse::ServerPushInfo> push_resources);

  // Check if push resource(push_host/push_path) associated with given request
  // url already exists in server push map.
  // TODO(b/171463363): Remove.
  bool PushResourceExistsInCache(std::string original_request_url,
                                 QuicBackendResponse::ServerPushInfo resource);

  // Cached responses.
  absl::flat_hash_map<std::string, std::unique_ptr<QuicBackendResponse>>
      responses_ QUIC_GUARDED_BY(response_mutex_);

  // The default response for cache misses, if set.
  std::unique_ptr<QuicBackendResponse> default_response_
      QUIC_GUARDED_BY(response_mutex_);

  // The generate bytes response, if set.
  std::unique_ptr<QuicBackendResponse> generate_bytes_response_
      QUIC_GUARDED_BY(response_mutex_);

  // A map from request URL to associated server push responses (if any).
  // TODO(b/171463363): Remove.
  std::multimap<std::string, QuicBackendResponse::ServerPushInfo>
      server_push_resources_ QUIC_GUARDED_BY(response_mutex_);

  // Protects against concurrent access from test threads setting responses, and
  // server threads accessing those responses.
  mutable QuicMutex response_mutex_;
  bool cache_initialized_;

  bool enable_webtransport_ = false;
};

}  // namespace quic

#endif  // QUICHE_QUIC_TOOLS_QUIC_MEMORY_CACHE_BACKEND_H_
