/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-2015 Facebook, Inc. (http://www.facebook.com)     |
   | Copyright (c) 1997-2010 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/
#include "hphp/runtime/ext/server/ext_server.h"

#include <folly/Conv.h>

#include "hphp/runtime/server/satellite-server.h"
#include "hphp/runtime/server/pagelet-server.h"
#include "hphp/runtime/server/xbox-server.h"
#include "hphp/runtime/server/http-protocol.h"
#include "hphp/runtime/base/builtin-functions.h"
#include "hphp/runtime/base/runtime-option.h"
#include "hphp/runtime/base/string-buffer.h"
#include "hphp/runtime/base/thread-info.h"
#include "hphp/runtime/server/rpc-request-handler.h"

#define DANGLING_HEADER "HPHP_DANGLING"

namespace HPHP {

static class ServerExtension final : public Extension {
public:
  ServerExtension() : Extension("server", NO_EXTENSION_VERSION_YET) {}
  void moduleInit() override {
    HHVM_RC_INT_SAME(PAGELET_NOT_READY);
    HHVM_RC_INT_SAME(PAGELET_READY);
    HHVM_RC_INT_SAME(PAGELET_DONE);

    HHVM_FE(hphp_thread_type);
    HHVM_FE(dangling_server_proxy_old_request);
    HHVM_FE(pagelet_server_is_enabled);
    HHVM_FE(pagelet_server_task_start);
    HHVM_FE(pagelet_server_task_status);
    HHVM_FE(pagelet_server_task_result);
    HHVM_FE(pagelet_server_tasks_started);
    HHVM_FE(pagelet_server_flush);
    HHVM_FE(xbox_send_message);
    HHVM_FE(xbox_post_message);
    HHVM_FE(xbox_task_start);
    HHVM_FE(xbox_task_status);
    HHVM_FE(xbox_task_result);
    HHVM_FE(xbox_process_call_message);
    HHVM_FE(xbox_get_thread_timeout);
    HHVM_FE(xbox_set_thread_timeout);
    HHVM_FE(xbox_schedule_thread_reset);
    HHVM_FE(xbox_get_thread_time);

    loadSystemlib();
  }
} s_server_extension;

int64_t HHVM_FUNCTION(hphp_thread_type) {
  Transport *transport = g_context->getTransport();
  return transport ? static_cast<int64_t>(transport->getThreadType()) : -1;
}

///////////////////////////////////////////////////////////////////////////////
// dangling server

bool HHVM_FUNCTION(dangling_server_proxy_old_request) {
  static bool s_detected_dangling_server = true;

  if (!s_detected_dangling_server ||
      SatelliteServerInfo::DanglingServerPort == 0) {
    return false;
  }

  Transport *transport = g_context->getTransport();
  if (transport == NULL) {
    return false;
  }
  if (!transport->getHeader(DANGLING_HEADER).empty()) {
    // if we are processing a dangling server request, do not do it again
    return false;
  }

  std::string url = "http://localhost:" +
    folly::to<std::string>(SatelliteServerInfo::DanglingServerPort) +
    transport->getServerObject();

  int code = 0;
  std::string error;
  StringBuffer response;
  HeaderMap headers;
  headers[DANGLING_HEADER].push_back("1");
  if (!HttpProtocol::ProxyRequest(transport, false, url, code, error,
                                  response, &headers)) {
    s_detected_dangling_server = false;
    return false;
  }
  transport->setResponse(code, "dangling_server_proxy_old_request");
  g_context->write(response.detach());
  return true;
}

///////////////////////////////////////////////////////////////////////////////
// Pagelet Server

bool HHVM_FUNCTION(pagelet_server_is_enabled) {
  return PageletServer::Enabled();
}

const StaticString s_Host("Host");

Resource HHVM_FUNCTION(pagelet_server_task_start,
                       const String& url,
                       const Array& headers /* = null_array */,
                       const String& post_data /* = null_string */,
                       const Array& files /* = null_array */) {
  String remote_host;
  Transport *transport = g_context->getTransport();
  int timeout = ThreadInfo::s_threadInfo->m_reqInjectionData.getRemainingTime();
  if (transport) {
    remote_host = transport->getRemoteHost();
    if (!headers.exists(s_Host) && RuntimeOption::SandboxMode) {
      Array tmp = headers;
      tmp.set(s_Host, transport->getHeader("Host"));
      return PageletServer::TaskStart(url, tmp, remote_host,
                                      post_data, files, timeout);
    }
  }
  return PageletServer::TaskStart(url, headers, remote_host,
                                  post_data, files, timeout);
}

int64_t HHVM_FUNCTION(pagelet_server_task_status,
                      const Resource& task) {
  return PageletServer::TaskStatus(task);
}

String HHVM_FUNCTION(pagelet_server_task_result,
                     const Resource& task,
                     VRefParam headers,
                     VRefParam code,
                     int64_t timeout_ms /* = 0 */) {
  Array rheaders;
  int rcode;
  String response = PageletServer::TaskResult(task, rheaders, rcode,
                                              timeout_ms);
  headers.assignIfRef(rheaders);
  code.assignIfRef(rcode);
  return response;
}

int64_t HHVM_FUNCTION(pagelet_server_tasks_started) {
  return g_context->getPageletTasksStarted();
}

void HHVM_FUNCTION(pagelet_server_flush) {
  ExecutionContext *context = g_context.getNoCheck();
  Transport *transport = context->getTransport();
  if (transport &&
      transport->getThreadType() == Transport::ThreadType::PageletThread) {
    // this method is only meaningful in a pagelet thread
    context->obFlushAll();
    String content = context->obDetachContents();
    std::string s(content.data(), content.size());
    if (!s.empty()) {
      PageletServer::AddToPipeline(s);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// xbox

bool HHVM_FUNCTION(xbox_send_message,
                   const String& msg,
                   VRefParam retRef,
                   int64_t timeout_ms,
                   const String& host /* = "localhost" */) {
  Array ret;
  auto b = XboxServer::SendMessage(msg, ret, timeout_ms, host);
  retRef.assignIfRef(ret);
  return b;
}

bool HHVM_FUNCTION(xbox_post_message,
                   const String& msg,
                   const String& host /* = "localhost" */) {
  return XboxServer::PostMessage(msg, host);
}

Resource HHVM_FUNCTION(xbox_task_start,
                       const String& message) {
  return XboxServer::TaskStart(message);
}

bool HHVM_FUNCTION(xbox_task_status,
                   const Resource& task) {
  return XboxServer::TaskStatus(task);
}

int64_t HHVM_FUNCTION(xbox_task_result,
                      const Resource& task,
                      int64_t timeout_ms,
                      VRefParam ret) {
  return XboxServer::TaskResult(task, timeout_ms, ret.getVariantOrNull());
}

Variant HHVM_FUNCTION(xbox_process_call_message,
                      const String& msg) {
  Variant v = unserialize_from_string(msg);
  if (!v.isArray()) {
    raise_error("Error decoding xbox call message");
  }
  Array arr = v.toArray();
  if (arr.size() != 2 || !arr.exists(0) || !arr.exists(1)) {
    raise_error("Error decoding xbox call message");
  }
  Variant fn = arr.rvalAt(0);
  if (fn.isArray()) {
    Array farr = fn.toArray();
    if (!array_is_valid_callback(farr)) {
      raise_error("Error decoding xbox call message");
    }
  } else if (!fn.isString()) {
    raise_error("Error decoding xbox call message");
  }
  Variant args = arr.rvalAt(1);
  if (!args.isArray()) {
    raise_error("Error decoding xbox call message");
  }
  return vm_call_user_func(fn, args.toArray());
}

int64_t HHVM_FUNCTION(xbox_get_thread_timeout) {
  auto server_info = XboxServer::GetServerInfo();
  if (server_info) {
    return server_info->getMaxDuration();
  }
  throw Exception("Not an xbox worker!");
}

void HHVM_FUNCTION(xbox_set_thread_timeout,
                   int timeout) {
  if (timeout < 0) {
    raise_warning("Cannot set timeout/duration to a negative number.");
    return;
  }
  auto server_info = XboxServer::GetServerInfo();
  if (server_info) {
    server_info->setMaxDuration(timeout);
  } else {
    throw Exception("Not an xbox worker!");
  }
}

void HHVM_FUNCTION(xbox_schedule_thread_reset) {
  RPCRequestHandler *handler = XboxServer::GetRequestHandler();
  if (handler) {
    handler->setReset();
  } else {
    throw Exception("Not an xbox worker!");
  }
}

int64_t HHVM_FUNCTION(xbox_get_thread_time) {
  RPCRequestHandler *handler = XboxServer::GetRequestHandler();
  if (handler) {
    return time(nullptr) - handler->getLastResetTime();
  }
  throw Exception("Not an xbox worker!");
}

///////////////////////////////////////////////////////////////////////////////
}
