#include "RemoteRequestHandler.h"
#include "../RpcExecutor.h"
#include "../callback/RemoteAuthCallback.h"
#include "../callback/RemoteCallback.h"
#include "RemoteRequest.h"
#include "RemoteResourceRequestHandler.h"
#include "../router/MessageRoutersManager.h"
#include "../browser/RemoteFrame.h"
#include "../browser/RemoteBrowser.h"
#include "../ServerHandlerContext.h"

#include "include/cef_ssl_info.h"

std::string err2str(cef_errorcode_t errorcode);
namespace {
  std::string tstatus2str(cef_termination_status_t status);
  const bool doTrace = getBoolEnv("CEF_SERVER_TRACE_RemoteRequestHandler");
}

RemoteRequestHandler::RemoteRequestHandler(std::shared_ptr<ServerHandlerContext> ctx, std::shared_ptr<MessageRoutersManager> routersManager) : myCtx(ctx), myRoutersManager(routersManager) {
  TRACE();
}

RemoteRequestHandler::~RemoteRequestHandler() {
  TRACE();
  // simple protection for leaking via callbacks
  for (auto c: myCallbacks)
    RemoteCallback::dispose(c);
  for (auto c: myAuthCallbacks)
    RemoteAuthCallback::dispose(c);
}

///
/// Called on the UI thread before browser navigation. Return true to cancel
/// the navigation or false to allow the navigation to proceed. The |request|
/// object cannot be modified in this callback.
/// CefLoadHandler::OnLoadingStateChange will be called twice in all cases.
/// If the navigation is allowed CefLoadHandler::OnLoadStart and
/// CefLoadHandler::OnLoadEnd will be called. If the navigation is canceled
/// CefLoadHandler::OnLoadError will be called with an |errorCode| value of
/// ERR_ABORTED. The |user_gesture| value will be true if the browser
/// navigated via explicit user gesture (e.g. clicking a link) or false if it
/// navigated automatically (e.g. via the DomContentLoaded event).
///
/*--cef()--*/
bool RemoteRequestHandler::OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
                    CefRefPtr<CefFrame> frame,
                    CefRefPtr<CefRequest> request,
                    bool user_gesture,
                    bool is_redirect
) {
  TRACE();
  FIND_BID_OR_RETURN_VAL(false);
  // Forward request to ClientHandler to make the message_router_ happy.
  myRoutersManager->OnBeforeBrowse(browser, frame);

  RemoteRequest::Holder req(request);
  RemoteFrame::Holder frm(frame);
  return myCtx->javaService()->exec<bool>([&](JavaService s){
    return s->RequestHandler_OnBeforeBrowse(bid, frm.serverId(), req.serverId(), user_gesture, is_redirect);
  }, false);
}

bool RemoteRequestHandler::OnOpenURLFromTab(CefRefPtr<CefBrowser> browser,
                      CefRefPtr<CefFrame> frame,
                      const CefString& target_url,
                      WindowOpenDisposition target_disposition,
                      bool user_gesture
) {
  TRACE();
  FIND_BID_OR_RETURN_VAL(false);
  RemoteFrame::Holder frm(frame);
  return myCtx->javaService()->exec<bool>([&](JavaService s){
    return s->RequestHandler_OnOpenURLFromTab(bid, frm.serverId(), target_url.ToString(), user_gesture);
  }, false);
}

///
/// Called on the browser process IO thread before a resource request is
/// initiated. The |browser| and |frame| values represent the source of the
/// request. |request| represents the request contents and cannot be modified
/// in this callback. |is_navigation| will be true if the resource request is
/// a navigation. |is_download| will be true if the resource request is a
/// download. |request_initiator| is the origin (scheme + domain) of the page
/// that initiated the request. Set |disable_default_handling| to true to
/// disable default handling of the request, in which case it will need to be
/// handled via CefResourceRequestHandler::GetResourceHandler or it will be
/// canceled. To allow the resource load to proceed with default handling
/// return NULL. To specify a handler for the resource return a
/// CefResourceRequestHandler object. If this callback returns NULL the same
/// method will be called on the associated CefRequestContextHandler, if any.
///
CefRefPtr<CefResourceRequestHandler> RemoteRequestHandler::GetResourceRequestHandler(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefRequest> request,
    bool is_navigation,
    bool is_download,
    const CefString& request_initiator,
    bool& disable_default_handling
) {
  // Called on the browser process IO thread before a resource request is initiated.
  TRACE();
  FIND_BID_OR_RETURN_VAL(nullptr);
  LogNdc ndc(__FILE_NAME__, __FUNCTION__, "ChromeIO");
  RemoteRequest::Holder req(request);
  RemoteFrame::Holder frm(frame);
  thrift_codegen::RObject peer;
  myCtx->javaServiceIO()->exec([&](JavaService s){
    s->RequestHandler_GetResourceRequestHandler(
        peer, bid, frm.serverId(), req.serverId(), is_navigation, is_download, request_initiator.ToString());
  });

  disable_default_handling = peer.__isset.flags ? peer.flags != 0 : false;
  return !peer.isNull ? new RemoteResourceRequestHandler(bid, myCtx, peer) : nullptr;
}

///
/// Called on the IO thread when the browser needs credentials from the user.
/// |origin_url| is the origin making this authentication request. |isProxy|
/// indicates whether the host is a proxy server. |host| contains the hostname
/// and |port| contains the port number. |realm| is the realm of the challenge
/// and may be empty. |scheme| is the authentication scheme used, such as
/// "basic" or "digest", and will be empty if the source of the request is an
/// FTP server. Return true to continue the request and call
/// CefAuthCallback::Continue() either in this method or at a later time when
/// the authentication information is available. Return false to cancel the
/// request immediately.
///
/*--cef(optional_param=realm,optional_param=scheme)--*/
bool RemoteRequestHandler::GetAuthCredentials(CefRefPtr<CefBrowser> browser,
                        const CefString& origin_url,
                        bool isProxy,
                        const CefString& host,
                        int port,
                        const CefString& realm,
                        const CefString& scheme,
                        CefRefPtr<CefAuthCallback> callback
) {
  TRACE();
  FIND_BID_OR_RETURN_VAL(false);
  thrift_codegen::RObject rc = RemoteAuthCallback::wrapDelegate(callback)->serverId();
  const bool handled = myCtx->javaServiceIO()->exec<bool>([&](JavaService s){
      return s->RequestHandler_GetAuthCredentials(bid, origin_url.ToString(), isProxy, host.ToString(), port, realm.ToString(), scheme.ToString(), rc);
  }, false);
  if (!handled)
    RemoteAuthCallback::dispose(rc.objId);
  else
    myAuthCallbacks.insert(rc.objId); // Callback will be disposed with RemoteRequestHandler (just for insurance)
  return handled;
}

void writeSSLData(std::string & out, CefRefPtr<CefSSLInfo> sslInfo);

///
/// Called on the UI thread to handle requests for URLs with an invalid
/// SSL certificate. Return true and call CefCallback methods either in this
/// method or at a later time to continue or cancel the request. Return false
/// to cancel the request immediately. If
/// cef_settings_t.ignore_certificate_errors is set all invalid certificates
/// will be accepted without calling this method.
///
/*--cef()--*/
bool RemoteRequestHandler::OnCertificateError(CefRefPtr<CefBrowser> browser,
                        cef_errorcode_t cert_error,
                        const CefString& request_url,
                        CefRefPtr<CefSSLInfo> ssl_info,
                        CefRefPtr<CefCallback> callback
) {
  TRACE();
  FIND_BID_OR_RETURN_VAL(false);
  std::shared_ptr<RemoteCallback> rc = RemoteCallback::wrapDelegate(callback);
  std::string buf;
  writeSSLData(buf, ssl_info);
  if (buf.capacity() > 1024*128)
    Log::warn("Large SSL certificate data: %d bytes. Consider to use shared memory for IPC transport.", buf.capacity());
  const bool handled = myCtx->javaService()->exec<bool>([&](JavaService s){
      return s->RequestHandler_OnCertificateError(bid, err2str(cert_error), request_url, buf, rc->serverId());
  }, false);
  if (!handled)
    RemoteCallback::dispose(rc->getId());
  else
    myCallbacks.insert(rc->getId()); // Callback will be disposed with RemoteRequestHandler (just for insurance)
  return handled;
}

size_t getAligned(size_t bytesCount) {
  const int diff = bytesCount % 4;
  return diff == 0 ? bytesCount : bytesCount + 4 - diff;
}

void writeSSLData(std::string & out, CefRefPtr<CefSSLInfo> sslInfo) {
  CefRefPtr<CefX509Certificate> cert = sslInfo->GetX509Certificate();

  // 1. Collect
  CefX509Certificate::IssuerChainBinaryList der_chain;
  cert->GetDEREncodedIssuerChain(der_chain);
  der_chain.insert(der_chain.begin(), cert->GetDEREncoded());

  // 2. Calculate size
  size_t totalBinaryDataSize = 0;
  for (const auto& it: der_chain) {
    const size_t size = it->GetSize();
    totalBinaryDataSize += getAligned(size);
  }

  // 3. Write
  const size_t reserved = totalBinaryDataSize + 4/*status mask*/ + 4/*items count*/ + 4*der_chain.size();
  out.resize(reserved);
  int32_t* p = (int32_t*)out.data();
  *(p++) = sslInfo->GetCertStatus();
  *(p++) = (int32_t)der_chain.size();
  //Log::trace("writeSSLData: chain size = %d, status = %d.", der_chain.size(), sslInfo->GetCertStatus());

  if (der_chain.size() == 0)
    return;

  for (const auto& der_cert: der_chain) {
    const size_t bytesCount = der_cert->GetSize();
    //Log::trace("writeSSLData: write chunk of size %d bytes, pos=%d.", bytesCount, ((char*)p) - out.data());
    *(p++) = (int32_t)bytesCount;
    if (bytesCount == 0)
      continue;

    der_cert->GetData(p, bytesCount, 0);
    p += getAligned(bytesCount)/4;
  }
}

void RemoteRequestHandler::OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser, TerminationStatus status, int error_code, const CefString& error_string) {
  TRACE();
  FIND_BID_OR_RETURN();
  // Forward request to ClientHandler to make the message_router_ happy.
  myRoutersManager->OnRenderProcessTerminated(browser);
  myCtx->javaService()->exec([&](JavaService s){
    s->RequestHandler_OnRenderProcessTerminated(bid, tstatus2str(status), error_code, error_string);
  });
}

namespace {
  std::pair<cef_errorcode_t, std::string> errorcodes [] = {
      #define NET_ERROR(label, value) {ERR_##label,"ERR_"#label},
      #include "include/base/internal/cef_net_error_list.h"
      #undef NET_ERROR
      {ERR_NONE, "ERR_NONE"}
  };

  std::pair<cef_termination_status_t, std::string> terminationStatuses [] = {
      {TS_ABNORMAL_TERMINATION, "TS_ABNORMAL_TERMINATION"},
      {TS_PROCESS_WAS_KILLED, "TS_PROCESS_WAS_KILLED"},
      {TS_PROCESS_CRASHED, "TS_PROCESS_CRASHED"},
      {TS_PROCESS_OOM, "TS_PROCESS_OOM"},
      {TS_LAUNCH_FAILED, "TS_LAUNCH_FAILED"},
      {TS_INTEGRITY_FAILURE, "TS_INTEGRITY_FAILURE"},
      {TS_NUM_VALUES, "TS_NUM_VALUES"}
  };

  std::string tstatus2str(cef_termination_status_t status) {
    for (auto p: terminationStatuses) {
      if (p.first == status)
        return p.second;
    }
    return string_format("unknown_termination_status_%d", status);
  }
}

std::string err2str(cef_errorcode_t errorcode) {
    for (auto p: errorcodes) {
      if (p.first == errorcode)
        return p.second;
    }
    Log::error("Can't find cef_errorcode_t: %d", errorcode);
    return string_format("unknown_errorcode_%d", errorcode);
}

cef_errorcode_t str2err(std::string err) {
    for (auto p: errorcodes) {
      if (p.second.compare(err) == 0)
        return p.first;
    }
    Log::error("Can't find cef_errorcode_t: %s", err.c_str());
    return ERR_NONE;
}
