/**
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

/**
 * @file AsyncHttpFetch.cc
 */

#include "atscppapi/AsyncHttpFetch.h"
#include <ts/ts.h>
#include <arpa/inet.h>
#include "logging_internal.h"
#include "utils_internal.h"

#include <cstdio>

using namespace atscppapi;
using std::string;

/**
 * @private
 */
struct atscppapi::AsyncHttpFetchState : noncopyable {
  Request request_;
  Response response_;
  string request_body_;
  AsyncHttpFetch::Result result_;
  const void *body_;
  size_t body_size_;
  TSMBuffer hdr_buf_;
  TSMLoc hdr_loc_;
  shared_ptr<AsyncDispatchControllerBase> dispatch_controller_;

  AsyncHttpFetchState(const string &url_str, HttpMethod http_method, string request_body)
    : request_(url_str, http_method, HTTP_VERSION_1_0), request_body_(request_body),
      result_(AsyncHttpFetch::RESULT_FAILURE), body_(NULL), body_size_(0), hdr_buf_(NULL), hdr_loc_(NULL) { }
  
  ~AsyncHttpFetchState() {
    if (hdr_loc_) {
      TSMLoc null_parent_loc = NULL;
      TSHandleMLocRelease(hdr_buf_, null_parent_loc, hdr_loc_);
    }
    if (hdr_buf_) {
      TSMBufferDestroy(hdr_buf_);
    }
  }
};

namespace {

const unsigned int LOCAL_IP_ADDRESS = 0x0100007f;
const int LOCAL_PORT = 8080;

static int handleFetchEvents(TSCont cont, TSEvent event, void *edata) {
  LOG_DEBUG("Fetch result returned event = %d, edata = %p", event, edata);
  AsyncHttpFetch *fetch_provider = static_cast<AsyncHttpFetch *>(TSContDataGet(cont));
  AsyncHttpFetchState *state = utils::internal::getAsyncHttpFetchState(*fetch_provider);
  
  if (event == static_cast<int>(AsyncHttpFetch::RESULT_SUCCESS)) {
    TSHttpTxn txn = static_cast<TSHttpTxn>(edata);
    int data_len;
    const char *data_start = TSFetchRespGet(txn, &data_len);
    const char *data_end = data_start + data_len;
    
    TSHttpParser parser = TSHttpParserCreate();
    state->hdr_buf_ = TSMBufferCreate();
    state->hdr_loc_ = TSHttpHdrCreate(state->hdr_buf_);
    TSHttpHdrTypeSet(state->hdr_buf_, state->hdr_loc_, TS_HTTP_TYPE_RESPONSE);
    if (TSHttpHdrParseResp(parser, state->hdr_buf_, state->hdr_loc_, &data_start, data_end) == TS_PARSE_DONE) {
      TSHttpStatus status = TSHttpHdrStatusGet(state->hdr_buf_, state->hdr_loc_);
      state->body_ = data_start; // data_start will now be pointing to body
      state->body_size_ = data_end - data_start;
      utils::internal::initResponse(state->response_, state->hdr_buf_, state->hdr_loc_);
      LOG_DEBUG("Fetch result had a status code of %d with a body length of %ld", status, state->body_size_);
    } else {
      LOG_ERROR("Unable to parse response; Request URL [%s]; transaction %p",
                state->request_.getUrl().getUrlString().c_str(), txn);
      event = static_cast<TSEvent>(AsyncHttpFetch::RESULT_FAILURE);
    }
    TSHttpParserDestroy(parser);
  }
  state->result_ = static_cast<AsyncHttpFetch::Result>(event);
  if (!state->dispatch_controller_->dispatch()) {
    LOG_DEBUG("Unable to dispatch result from AsyncFetch because promise has died.");
  }

  utils::internal::deleteAsyncHttpFetch(fetch_provider); // we must always cleans up when we're done.
  TSContDestroy(cont);
  return 0;
}

}

AsyncHttpFetch::AsyncHttpFetch(const string &url_str, const string &request_body) {
  init(url_str, HTTP_METHOD_POST, request_body);
}

AsyncHttpFetch::AsyncHttpFetch(const string &url_str, HttpMethod http_method) {
  init(url_str, http_method, "");
}

void AsyncHttpFetch::init(const string &url_str, HttpMethod http_method, const string &request_body) {
  LOG_DEBUG("Created new AsyncHttpFetch object %p", this);
  state_ = new AsyncHttpFetchState(url_str, http_method, request_body);
}

void AsyncHttpFetch::run() {
  state_->dispatch_controller_ = getDispatchController(); // keep a copy in state so that cont handler can use it

  TSCont fetchCont = TSContCreate(handleFetchEvents, TSMutexCreate());
  TSContDataSet(fetchCont, static_cast<void *>(this)); // Providers have to clean themselves up when they are done.

  TSFetchEvent event_ids;
  event_ids.success_event_id = RESULT_SUCCESS;
  event_ids.failure_event_id = RESULT_FAILURE;
  event_ids.timeout_event_id = RESULT_TIMEOUT;

  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = LOCAL_IP_ADDRESS;
  addr.sin_port = LOCAL_PORT;

  string request_str(HTTP_METHOD_STRINGS[state_->request_.getMethod()]);
  request_str += ' ';
  request_str += state_->request_.getUrl().getUrlString();
  request_str += ' ';
  request_str += HTTP_VERSION_STRINGS[state_->request_.getVersion()];
  request_str += "\r\n";
  Headers &headers = state_->request_.getHeaders();
  if (headers.size()) {
    // remove the possibility of keep-alive
    headers.erase("Connection");
    headers.erase("Proxy-Connection");
  }
  if (!state_->request_body_.empty()) {
    char size_buf[128];
    snprintf(size_buf, sizeof(size_buf), "%zu", state_->request_body_.size());
    state_->request_.getHeaders().set("Content-Length", size_buf);
  }
  request_str += headers.wireStr();
  request_str += "\r\n";
  request_str += state_->request_body_;

  LOG_DEBUG("Issing TSFetchUrl with request\n[%s]", request_str.c_str());
  TSFetchUrl(request_str.c_str(), request_str.size(), reinterpret_cast<struct sockaddr const *>(&addr), fetchCont,
             AFTER_BODY, event_ids);
}

Headers &AsyncHttpFetch::getRequestHeaders() {
  return state_->request_.getHeaders();
}

AsyncHttpFetch::Result AsyncHttpFetch::getResult() const {
  return state_->result_;
}

const Url &AsyncHttpFetch::getRequestUrl() const {
  return state_->request_.getUrl();
}

const Response &AsyncHttpFetch::getResponse() const {
  return state_->response_;
}

void AsyncHttpFetch::getResponseBody(const void *&body, size_t &body_size) const {
  body = state_->body_;
  body_size = state_->body_size_;
}

AsyncHttpFetch::~AsyncHttpFetch() {
  delete state_;
}
