/*
 *  Copyright (c) 2017, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 *
 */
#include <proxygen/lib/http/codec/compress/test/HTTPArchive.h>

#include <algorithm>
#include <folly/io/IOBuf.h>
#include <folly/json.h>
#include <fstream>
#include <glog/logging.h>
#include <ios>
#include <string>

using folly::IOBuf;
using std::ifstream;
using std::ios;
using std::string;
using std::unique_ptr;
using std::vector;

namespace {

folly::Optional<std::chrono::steady_clock::time_point>
parseHTTPArchiveTime(const std::string& s) {
  struct tm tm = {0};

  if (s.empty()) {
    return folly::none;
  }

  uint32_t ms = 0;
  // Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
  if (sscanf(s.c_str(), "%d-%d-%dT%d:%d:%d.%dZ",
             &tm.tm_year,
             &tm.tm_mon,
             &tm.tm_mday,
             &tm.tm_hour,
             &tm.tm_min,
             &tm.tm_sec,
             &ms) != 7) {
    return folly::none;
  }
  auto res = mktime(&tm);
  return std::chrono::steady_clock::time_point(
    std::chrono::seconds(res) + std::chrono::milliseconds(ms));
}

proxygen::HTTPMessage extractMessage(folly::dynamic& obj,
                                     const std::string& timeStr,
                                     bool request) {
  proxygen::HTTPMessage msg;
  auto& headersObj = obj["headers"];
  for (size_t i = 0; i < headersObj.size(); i++) {
    string name = headersObj[i]["name"].asString();
    std::transform(name.begin(), name.end(), name.begin(), ::tolower);
    if (name[0] == ':') {
      if (name == ":host" || name == ":authority") {
        name = "host";
      } else {
        continue;
      }
    }
    msg.getHeaders().add(
        name,
        headersObj[i]["value"].asString());
  }
  try {
    if (request) {
      msg.setURL(obj["url"].asString());
      msg.setMethod(obj["method"].asString());
      auto t = parseHTTPArchiveTime(timeStr);
      if (t.hasValue()) {
        msg.setStartTime(t.value());
      }
    } else {
      msg.setStatusCode(obj["status"].asInt());
      msg.setStatusMessage(obj["statusText"].asString());
    }

    string proto;
    string version;
    folly::split('/', obj["httpVersion"].asString(), proto, version);
    msg.setVersionString(version);
  } catch (...) {
  }

  return msg;
}

proxygen::HTTPMessage extractMessageFromPublic(folly::dynamic& obj) {
  proxygen::HTTPMessage msg;
  auto& headersObj = obj["headers"];
  for (size_t i = 0; i < headersObj.size(); i++) {
    auto& headerObj = headersObj[i];
    for (auto& k: headerObj.keys()) {
      string name = k.asString();
      string value = headerObj[name].asString();
      std::transform(name.begin(), name.end(), name.begin(), ::tolower);
      msg.getHeaders().add(name, value);
    }
  }
  return msg;
}
}

namespace proxygen {

std::unique_ptr<IOBuf> readFileToIOBuf(const std::string& filename) {
  // read the contents of the file
  ifstream file(filename);
  if (!file.is_open()) {
    LOG(ERROR) << "could not open file '" << filename << "'";
    return nullptr;
  }
  file.seekg(0, ios::end);
  int64_t size = file.tellg();
  if (size < 0) {
    LOG(ERROR) << "failed to fetch the position at the end of the file";
    return nullptr;
  }
  file.seekg(0, ios::beg);
  unique_ptr<IOBuf> buffer = IOBuf::create(size + 1);
  file.read((char *)buffer->writableData(), size);
  buffer->writableData()[size] = 0;
  buffer->append(size + 1);
  if (!file) {
    LOG(ERROR) << "error occurred, was able to read only "
               << file.gcount() << " bytes out of " << size;
    return nullptr;
  }
  return buffer;
}

unique_ptr<HTTPArchive> HTTPArchive::fromFile(const string& filename) {
  unique_ptr<HTTPArchive> har = std::make_unique<HTTPArchive>();
  auto buffer = readFileToIOBuf(filename);
  if (!buffer) {
    return nullptr;
  }
  folly::dynamic jsonObj = folly::parseJson((const char *)buffer->data());
  auto entries = jsonObj["log"]["entries"];
  // go over all the transactions
  for (size_t i = 0; i < entries.size(); i++) {
    HTTPMessage msg = extractMessage(entries[i]["request"],
                                     entries[i]["startedDateTime"].asString(),
                                     true);
    if (msg.getHeaders().size() != 0) {
      har->requests.push_back(msg);
    }
    msg = extractMessage(entries[i]["response"], "", false);
    if (msg.getHeaders().size() != 0) {
      har->responses.push_back(msg);
    }
  }

  return har;
}

uint32_t HTTPArchive::getSize(const HTTPMessage &msg) {
  uint32_t size = 0;

  msg.getHeaders().forEach([&size] (const string& name, const string& value) {
      size += name.size() + value.size() + 2;
    });
  return size;
}

uint32_t HTTPArchive::getSize(const vector<HPACKHeader> &headers) {
  uint32_t size = 0;

  for (const auto& header : headers) {
    size += header.name.size() + header.value.size() + 2;
  }
  return size;
}

unique_ptr<HTTPArchive> HTTPArchive::fromPublicFile(const string& filename) {
  unique_ptr<HTTPArchive> har = std::make_unique<HTTPArchive>();
  auto buffer = readFileToIOBuf(filename);
  if (!buffer) {
    return nullptr;
  }
  folly::dynamic jsonObj = folly::parseJson((const char *)buffer->data());
  auto entries = jsonObj["cases"];
  // go over all the transactions
  for (size_t i = 0; i < entries.size(); i++) {
    HTTPMessage msg = extractMessageFromPublic(entries[i]);
    if (msg.getHeaders().size() != 0) {
      har->requests.emplace_back(msg);
    }
  }

  return har;
}

std::vector<std::vector<HPACKHeader>> HTTPArchive::convertToHPACK(
  const std::vector<HTTPMessage>& msgs) {
  std::vector<std::vector<HPACKHeader>> result;
  for (const HTTPMessage& msg: msgs) {
    std::vector<HPACKHeader> headers;
    msg.getHeaders().forEach(
      [&headers] (const string& name, const string& value) {
        headers.emplace_back(name, value);
      });
    result.emplace_back(std::move(headers));
  }
  return result;
}

}
