// Copyright (c) 2020 The Chromium Embedded Framework 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 "tests/ceftests/test_request.h"

#include <algorithm>
#include <memory>

#include "include/cef_stream.h"
#include "include/cef_urlrequest.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
#include "include/wrapper/cef_stream_resource_handler.h"

namespace test_request {

namespace {

// Implementation of CefURLRequestClient that stores response information.
class RequestClient : public CefURLRequestClient, public State {
 public:
  RequestClient(const bool has_credentials,
                const std::string& username,
                const std::string& password,
                RequestDoneCallback done_callback)
      : has_credentials_(has_credentials),
        username_(username),
        password_(password),
        done_callback_(std::move(done_callback)) {
    DCHECK(!done_callback_.is_null());
  }

  void OnUploadProgress(CefRefPtr<CefURLRequest> request,
                        int64_t current,
                        int64_t total) override {
    upload_progress_ct_++;
    upload_total_ = total;
  }

  void OnDownloadProgress(CefRefPtr<CefURLRequest> request,
                          int64_t current,
                          int64_t total) override {
    response_ = request->GetResponse();
    DCHECK(response_.get());
    DCHECK(response_->IsReadOnly());
    download_progress_ct_++;
    download_total_ = total;
  }

  void OnDownloadData(CefRefPtr<CefURLRequest> request,
                      const void* data,
                      size_t data_length) override {
    response_ = request->GetResponse();
    DCHECK(response_.get());
    DCHECK(response_->IsReadOnly());
    download_data_ct_++;
    download_data_ += std::string(static_cast<const char*>(data), data_length);
  }

  bool GetAuthCredentials(bool isProxy,
                          const CefString& host,
                          int port,
                          const CefString& realm,
                          const CefString& scheme,
                          CefRefPtr<CefAuthCallback> callback) override {
    auth_credentials_ct_++;
    if (has_credentials_) {
      callback->Continue(username_, password_);
      return true;
    }
    return false;
  }

  void OnRequestComplete(CefRefPtr<CefURLRequest> request) override {
    request_complete_ct_++;

    request_ = request->GetRequest();
    DCHECK(request_->IsReadOnly());
    status_ = request->GetRequestStatus();
    error_code_ = request->GetRequestError();
    response_was_cached_ = request->ResponseWasCached();
    response_ = request->GetResponse();
    if (response_) {
      DCHECK(response_->IsReadOnly());
    }

    std::move(done_callback_).Run(*this);
  }

 private:
  const bool has_credentials_;
  const std::string username_;
  const std::string password_;
  RequestDoneCallback done_callback_;

  IMPLEMENT_REFCOUNTING(RequestClient);
  DISALLOW_COPY_AND_ASSIGN(RequestClient);
};

// Implementation that collects all cookies, and optionally deletes them.
class CookieVisitor : public CefCookieVisitor {
 public:
  CookieVisitor(bool deleteCookies, CookieDoneCallback callback)
      : delete_cookies_(deleteCookies), callback_(std::move(callback)) {
    DCHECK(!callback_.is_null());
  }

  ~CookieVisitor() override {
    CEF_REQUIRE_UI_THREAD();
    std::move(callback_).Run(cookies_);
  }

  bool Visit(const CefCookie& cookie,
             int count,
             int total,
             bool& deleteCookie) override {
    CEF_REQUIRE_UI_THREAD();
    cookies_.push_back(cookie);
    if (delete_cookies_) {
      deleteCookie = true;
    }
    return true;
  }

 private:
  CookieVector cookies_;
  bool delete_cookies_;
  CookieDoneCallback callback_;

  IMPLEMENT_REFCOUNTING(CookieVisitor);
  DISALLOW_COPY_AND_ASSIGN(CookieVisitor);
};

}  // namespace

void Send(const SendConfig& config, RequestDoneCallback callback) {
  DCHECK(config.request_);
  CefRefPtr<RequestClient> client =
      new RequestClient(config.has_credentials_, config.username_,
                        config.password_, std::move(callback));
  if (config.frame_) {
    config.frame_->CreateURLRequest(config.request_, client.get());
  } else {
    CefURLRequest::Create(config.request_, client.get(),
                          config.request_context_);
  }
}

std::string GetPathURL(const std::string& url) {
  const size_t index1 = url.find('?');
  const size_t index2 = url.find('#');
  size_t index = std::string::npos;
  if (index1 != std::string::npos && index2 != std::string::npos) {
    index = std::min(index1, index2);
  } else if (index1 != std::string::npos) {
    index = index1;
  } else if (index2 != std::string::npos) {
    index = index2;
  }
  if (index != std::string::npos) {
    return url.substr(0, index);
  }
  return url;
}

CefRefPtr<CefResourceHandler> CreateResourceHandler(
    CefRefPtr<CefResponse> response,
    const std::string& response_data) {
  CefRefPtr<CefStreamReader> stream;
  if (!response_data.empty()) {
    stream = CefStreamReader::CreateForData(
        static_cast<void*>(const_cast<char*>(response_data.c_str())),
        response_data.length());
  }

  CefResponse::HeaderMap headerMap;
  response->GetHeaderMap(headerMap);

  return new CefStreamResourceHandler(
      response->GetStatus(), response->GetStatusText(), response->GetMimeType(),
      headerMap, stream);
}

void GetAllCookies(CefRefPtr<CefCookieManager> manager,
                   bool deleteCookies,
                   CookieDoneCallback callback) {
  bool result = manager->VisitAllCookies(
      new CookieVisitor(deleteCookies, std::move(callback)));
  DCHECK(result);
}

// Retrieves URL cookies from |manager| and executes |callback| upon completion.
// If |deleteCookies| is true the cookies will also be deleted.
void GetUrlCookies(CefRefPtr<CefCookieManager> manager,
                   const CefString& url,
                   bool includeHttpOnly,
                   bool deleteCookies,
                   CookieDoneCallback callback) {
  bool result = manager->VisitUrlCookies(
      url, includeHttpOnly,
      new CookieVisitor(deleteCookies, std::move(callback)));
  DCHECK(result);
}

}  // namespace test_request
