// Copyright 2014 The Chromium OS 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 LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_
#define LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_

#include <map>
#include <string>
#include <utility>

#include <base/memory/weak_ptr.h>
#include <brillo/brillo_export.h>
#include <brillo/http/curl_api.h>
#include <brillo/http/http_transport.h>

namespace brillo {
namespace http {
namespace curl {

class Connection;

///////////////////////////////////////////////////////////////////////////////
// An implementation of http::Transport that uses libcurl for
// HTTP communications. This class (as http::Transport base)
// is used by http::Request and http::Response classes to provide HTTP
// functionality to the clients.
// See http_transport.h for more details.
///////////////////////////////////////////////////////////////////////////////
class BRILLO_EXPORT Transport : public http::Transport {
 public:
  // Constructs the transport using the current message loop for async
  // operations.
  explicit Transport(const std::shared_ptr<CurlInterface>& curl_interface);
  // Creates a transport object using a proxy.
  // |proxy| is of the form [protocol://][user:password@]host[:port].
  // If not defined, protocol is assumed to be http://.
  Transport(const std::shared_ptr<CurlInterface>& curl_interface,
            const std::string& proxy);
  ~Transport() override;

  // Overrides from http::Transport.
  std::shared_ptr<http::Connection> CreateConnection(
      const std::string& url,
      const std::string& method,
      const HeaderList& headers,
      const std::string& user_agent,
      const std::string& referer,
      brillo::ErrorPtr* error) override;

  void RunCallbackAsync(const base::Location& from_here,
                        const base::Closure& callback) override;

  RequestID StartAsyncTransfer(http::Connection* connection,
                               const SuccessCallback& success_callback,
                               const ErrorCallback& error_callback) override;

  bool CancelRequest(RequestID request_id) override;

  void SetDefaultTimeout(base::TimeDelta timeout) override;

  void SetLocalIpAddress(const std::string& ip_address) override;

  // Helper methods to convert CURL error codes (CURLcode and CURLMcode)
  // into brillo::Error object.
  static void AddEasyCurlError(brillo::ErrorPtr* error,
                               const base::Location& location,
                               CURLcode code,
                               CurlInterface* curl_interface);

  static void AddMultiCurlError(brillo::ErrorPtr* error,
                                const base::Location& location,
                                CURLMcode code,
                                CurlInterface* curl_interface);

 private:
  // Forward-declaration of internal implementation structures.
  struct AsyncRequestData;
  class SocketPollData;

  // Initializes CURL for async operation.
  bool SetupAsyncCurl(brillo::ErrorPtr* error);

  // Stops CURL's async operations.
  void ShutDownAsyncCurl();

  // Handles all pending async messages from CURL.
  void ProcessAsyncCurlMessages();

  // Processes the transfer completion message (success or failure).
  void OnTransferComplete(http::curl::Connection* connection,
                          CURLcode code);

  // Cleans up internal data for a completed/canceled asynchronous operation
  // on a connection.
  void CleanAsyncConnection(http::curl::Connection* connection);

  // Called after a timeout delay requested by CURL has elapsed.
  void OnTimer();

  // Callback for CURL to handle curl_socket_callback() notifications.
  // The parameters correspond to those of curl_socket_callback().
  static int MultiSocketCallback(CURL* easy,
                                 curl_socket_t s,
                                 int what,
                                 void* userp,
                                 void* socketp);

  // Callback for CURL to handle curl_multi_timer_callback() notifications.
  // The parameters correspond to those of curl_multi_timer_callback().
  // CURL actually uses "long" types in callback signatures, so we must comply.
  static int MultiTimerCallback(CURLM* multi,
                                long timeout_ms,  // NOLINT(runtime/int)
                                void* userp);

  std::shared_ptr<CurlInterface> curl_interface_;
  std::string proxy_;
  // CURL "multi"-handle for processing requests on multiple connections.
  CURLM* curl_multi_handle_{nullptr};
  // A map to find a corresponding Connection* using a request ID.
  std::map<RequestID, Connection*> request_id_map_;
  // Stores the connection-specific asynchronous data (such as the success
  // and error callbacks that need to be called at the end of the async
  // operation).
  std::map<Connection*, std::unique_ptr<AsyncRequestData>> async_requests_;
  // Internal data associated with in-progress asynchronous operations.
  std::map<std::pair<CURL*, curl_socket_t>, SocketPollData*> poll_data_map_;
  // The last request ID used for asynchronous operations.
  RequestID last_request_id_{0};
  // The connection timeout for the requests made.
  base::TimeDelta connection_timeout_;
  std::string ip_address_;

  base::WeakPtrFactory<Transport> weak_ptr_factory_for_timer_{this};
  base::WeakPtrFactory<Transport> weak_ptr_factory_{this};
  DISALLOW_COPY_AND_ASSIGN(Transport);
};

}  // namespace curl
}  // namespace http
}  // namespace brillo

#endif  // LIBBRILLO_BRILLO_HTTP_HTTP_TRANSPORT_CURL_H_