/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include "http_wrapper.h"
#include "protocol/protocol_request.h"

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "iotcs.h"
#include "external/http_parser/http_parser.h"
#include "iotcs/iotcs_private.h"
#include "util/util_thread_private.h"
#include "util/util_buffer.h"
#include "util/util.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_PROTO
#include "log/log_template.h"

#include "iotcs_port_ssl.h"

typedef struct {
    protocol_response *response;
    int bytes_written;
    int last_was_field;
    int min_accept_bytes_processing;
    int content_length_processing;
    int content_type_processing;
} http_response_handle;

#define MIN_ACCEPT_BYTES_HEADER "X-min-acceptBytes"
#define CONTENT_TYPE_HEADER "Content-Type"
#define CONTENT_LENGTH_HEADER "Content-Length"

int header_field_cb(http_parser *parser, const char *p, size_t len) {
    http_response_handle *response_handle = (http_response_handle*) parser->data;
    response_handle->min_accept_bytes_processing =
            (!response_handle->response->min_accept_bytes && 0 == strncmp(MIN_ACCEPT_BYTES_HEADER, p, len));
    response_handle->content_type_processing =
            (!response_handle->response->content_type && 0 == strncmp(CONTENT_TYPE_HEADER, p, len));
    response_handle->content_length_processing =
            (!response_handle->response->content_length && 0 == strncmp(CONTENT_LENGTH_HEADER, p, len));
    response_handle->last_was_field = 1;
    return 0;
}

int header_value_cb(http_parser *parser, const char *p, size_t len) {
    http_response_handle *response_handle = (http_response_handle*) parser->data;
    if (response_handle->min_accept_bytes_processing) {
        if (response_handle->last_was_field) {
            char* value = (char*) p;
            /* we know what we are doing! */;
            value[len] = '\0';
            response_handle->response->min_accept_bytes = value;
        } else {
            LOG_ERR("Multiline %s header value is not supported", MIN_ACCEPT_BYTES_HEADER);
            response_handle->response->min_accept_bytes = NULL;
            response_handle->min_accept_bytes_processing = 0;
        }
    }
    if (response_handle->content_length_processing) {
        if (response_handle->last_was_field) {
            char* value = (char*) p;
            /* we know what we are doing! */;
            value[len] = '\0';
            response_handle->response->content_length = value;
        } else {
            LOG_ERR("Multiline %s header value is not supported", CONTENT_LENGTH_HEADER);
            response_handle->response->content_length = NULL;
            response_handle->content_length_processing = 0;
        }
    }
    if (response_handle->content_type_processing) {
        if (response_handle->last_was_field) {
            char* value = (char*) p;
            /* we know what we are doing! */;
            value[len] = '\0';
            response_handle->response->content_type = value;
        } else {
            LOG_ERR("Multiline %s header value is not supported", CONTENT_TYPE_HEADER);
            response_handle->response->content_type = NULL;
            response_handle->content_type_processing = 0;
        }
    }
    response_handle->last_was_field = 0;
    return 0;
}

int headers_complete_cb(http_parser *p) {
    http_response_handle *response_handle = (http_response_handle*) p->data;

    response_handle->response->status_code = p->status_code;
    return 0;
}

int body_cb(http_parser *p, const char *buf, size_t len) {
    http_response_handle *response_handle = (http_response_handle*) p->data;

    if (response_handle->response->body == NULL) {
        response_handle->response->body = (char*) buf; /* we know what we are doing! */
        response_handle->bytes_written = len;
    } else {
        memmove(response_handle->response->body + response_handle->bytes_written, buf, len);
        response_handle->bytes_written += len;
    }
    return 0;
}

http_parser_settings response_settings = {
    .on_body = body_cb,
    .on_headers_complete = headers_complete_cb,
    .on_header_field = header_field_cb,
    .on_header_value = header_value_cb
};

iotcs_result http_parse(protocol_response *response, char *buf, size_t len) {
    http_parser parser;
    http_response_handle response_handle;
    http_parser_init(&parser, HTTP_RESPONSE);
    parser.data = (void*) &response_handle;
    response_handle.bytes_written = 0;
    response_handle.last_was_field = 0;
    response_handle.min_accept_bytes_processing = 0;
    response_handle.content_length_processing = 0;
    response_handle.content_type_processing = 0;
    response_handle.response = response;
    response_handle.response->body = NULL;
    response_handle.response->min_accept_bytes = NULL;
    response_handle.response->content_length = NULL;
    response_handle.response->content_type = NULL;
    response_handle.response->status_code = PROTOCOL_RESPONSE_CODE_BAD_REQUEST;
    if (http_parser_execute(&parser, &response_settings, buf, len) != len) {
        return IOTCS_RESULT_FAIL;
    }
    /* handle situation of HTTP response without body */
    if (NULL == response_handle.response->body) {
        response_handle.response->body = buf;
        assert(response_handle.bytes_written == 0);
    }
    response_handle.response->body[response_handle.bytes_written] = '\0';
    return IOTCS_RESULT_OK;
}

#ifdef IOTCS_LONG_POLLING

/*
 * Send HTTP request and parse response.
 */
static iotcs_result send_lp_request(protocol_request request, protocol_response* response, int32_t timeout_ms) {
    int cx = 0;
    int tmp_rv = 0;
    int auth_required = 0;
    int bytes_read = 0;
    char* tmp_buffer = NULL;
    char* tmp_response_buffer = NULL;
    iotcs_result rv = IOTCS_RESULT_FAIL;
    int receive_timeout;

    if (!request.method || !request.url) {
        return IOTCS_RESULT_FAIL;
    }

    tmp_buffer = util_get_payload_buffer_lp();
    tmp_response_buffer = util_get_response_buffer_lp();

    GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer, IOTCS_LONG_POLLING_BUFFER_SIZE, "%s %s HTTP/1.1\r\n",
            protocol_request_methods_to_string(request.method), request.url)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += tmp_rv;
    if (request.headers.host != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Host", request.headers.host)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.content_type != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Content-Type", request.headers.content_type)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.accept != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Accept", request.headers.accept)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.connection != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Connection", request.headers.connection)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.x_activationId != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                "%s: %s\r\n", "X-ActivationId", request.headers.x_activationId)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
        auth_required = 1;
    }
    if (request.headers.x_endpointId != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                "%s: %s\r\n", "X-EndpointId", request.headers.x_endpointId)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
        auth_required = 1;
    }

    if (auth_required) {
        if (cl_get_access_token_ptr()->value != NULL) {
            GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                    "%s: %s\r\n", "Authorization", cl_get_access_token_ptr()->value)), REQUEST_STRING_LENGTH_ERR_MSG);
            cx += tmp_rv;
        } else {
            return IOTCS_RESULT_FAIL;
        }
    }

    if (request.body != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, IOTCS_LONG_POLLING_BUFFER_SIZE - cx,
                "Content-Length: %d\r\n\r\n", (int) strlen(request.body))), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }

    /*We don't expect here negative value*/
    if (timeout_ms > 0) {
        /*
         * Add an offset to the transport level timeout
         * as a failover mechanism, just in case.
         */
        timeout_ms += IOTCS_LP_TIMEOUT_OFFSET_MS;
        receive_timeout = timeout_ms >= IOTCS_LP_SMALL_TIMEOUT_MS ? IOTCS_LP_SMALL_TIMEOUT_MS : timeout_ms;
    } else {
        receive_timeout = 0;
    }

    GOTO_ERR(tmp_buffer[0] == '\0');
    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Send LP");
    GOTO_ERR(iotcs_port_ssl_connect_lp(receive_timeout) != IOTCS_RESULT_OK);
    LOG_INFOSN(tmp_buffer, strlen(tmp_buffer));
    LOG_INFOSN(request.body, strlen((char*) request.body));
    GOTO_ERR(iotcs_port_ssl_write_lp(tmp_buffer, strlen(tmp_buffer)) != IOTCS_RESULT_OK);
    GOTO_ERR(iotcs_port_ssl_write_lp((char*) request.body, strlen((char*) request.body)) != IOTCS_RESULT_OK);
    LOG_INFOS("<<<<<<<<<<<<<<<<<<<<< Send LP");
    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Recv LP");

    if (timeout_ms == 0) {
        /*Just do one read*/
        rv = iotcs_port_ssl_read_lp(tmp_response_buffer, UTIL_LP_RESPONSE_SIZE, &bytes_read);
    } else {
        int buf_len = UTIL_LP_RESPONSE_SIZE;
        tmp_buffer = tmp_response_buffer;
        do {
            if (cl_is_long_polling_receive_should_be_stopped()) {
                rv = IOTCS_RESULT_FAIL;
                break;
            }
            if (cl_is_long_polling_receive_should_be_interrupted()) {
                rv = IOTCS_RESULT_FAIL;
                break;
            }

            LOG_DBG("remain lp timeout %d ms", timeout_ms);

            bytes_read = 0;
            if (IOTCS_RESULT_OK == (rv = iotcs_port_ssl_read_lp(tmp_buffer, buf_len, &bytes_read))) {
                break; /* socket is closed - no more data expected */
            } else if (rv != IOTCS_RESULT_SSL_WANT_READ) {
                break; /* error on read */
            }
            /* rv == IOTCS_RESULT_SSL_WANT_READ */
            tmp_buffer += bytes_read;
            buf_len -= bytes_read;
            timeout_ms -= receive_timeout;
            rv = IOTCS_RESULT_FAIL; /* If timeout has expired - we will treat it as FAIL */
        } while (timeout_ms > 0);
    }
    LOG_INFOSN(tmp_response_buffer, bytes_read);
    LOG_INFOS("<<<<<<<<<<<<<<<<<<<<< Recv LP");

    GOTO_ERR(iotcs_port_ssl_disconnect_lp() != IOTCS_RESULT_OK);

    if (rv != IOTCS_RESULT_OK) {
        return IOTCS_RESULT_FAIL;
    }

#ifdef IOTCS_MESSAGE_DISPATCHER
    cl_message_dispatcher_counters_add_bytes_received(bytes_read);
#endif
    GOTO_ERR(http_parse(response, tmp_response_buffer, bytes_read) != IOTCS_RESULT_OK);
    return IOTCS_RESULT_OK;
error:
    iotcs_port_ssl_disconnect_lp();
    return IOTCS_RESULT_FAIL;
}
#endif

/*
 * Send HTTP request and parse response.
 */
static iotcs_result send_request(protocol_request request, protocol_response* response) {
    int cx = 0;
    int tmp_rv = 0;
    int auth_required = 0;
    int bytes_read;
    char* tmp_buffer = NULL;
    char* tmp_response_buffer = NULL;

    if (!request.method || !request.url) {
        return IOTCS_RESULT_FAIL;
    }
    UTIL_LOCK_RWSSL();

    tmp_buffer = util_get_http_header_buffer();
    tmp_response_buffer = util_get_response_buffer();

    GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer, UTIL_HTTP_HEADER_BUFFER_SIZE, "%s %s HTTP/1.1\r\n",
            protocol_request_methods_to_string(request.method), request.url)), REQUEST_STRING_LENGTH_ERR_MSG);
    cx += tmp_rv;
    if (request.headers.host != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Host", request.headers.host)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.content_type != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Content-Type", request.headers.content_type)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.accept != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Accept", request.headers.accept)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.connection != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                "%s: %s\r\n", "Connection", request.headers.connection)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }
    if (request.headers.x_activationId != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                "%s: %s\r\n", "X-ActivationId", request.headers.x_activationId)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
        auth_required = 1;
    }
    if (request.headers.x_endpointId != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                "%s: %s\r\n", "X-EndpointId", request.headers.x_endpointId)), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
        auth_required = 1;
    }

    if (auth_required) {
        if (cl_get_access_token_ptr()->value != NULL) {
            GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                    "%s: %s\r\n", "Authorization", cl_get_access_token_ptr()->value)), REQUEST_STRING_LENGTH_ERR_MSG);
            cx += tmp_rv;
        } else {
            return IOTCS_RESULT_FAIL;
        }
    }

    if (request.body != NULL) {
        GOTO_ERR_MSG(0 > (tmp_rv = util_safe_snprintf(tmp_buffer + cx, UTIL_HTTP_HEADER_BUFFER_SIZE - cx,
                "Content-Length: %d\r\n\r\n", (int) strlen(request.body))), REQUEST_STRING_LENGTH_ERR_MSG);
        cx += tmp_rv;
    }

    GOTO_ERR(tmp_buffer[0] == '\0');
#ifdef IOTCS_MESSAGE_DISPATCHER
    cl_message_dispatcher_counters_add_bytes_sent(cx);
#endif

    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Send");
    GOTO_ERR(iotcs_port_ssl_connect() != IOTCS_RESULT_OK);
    LOG_INFOSN(tmp_buffer, strlen(tmp_buffer));
    LOG_INFOSN(request.body, strlen((char*) request.body));
    GOTO_ERR(iotcs_port_ssl_write(tmp_buffer, strlen(tmp_buffer)) != IOTCS_RESULT_OK);
    GOTO_ERR(iotcs_port_ssl_write((char*) request.body, strlen((char*) request.body)) != IOTCS_RESULT_OK);
    LOG_INFOS("<<<<<<<<<<<<<<<<<<<<< Send");
    LOG_INFOS(">>>>>>>>>>>>>>>>>>>>> Recv");
    GOTO_ERR(iotcs_port_ssl_read(tmp_response_buffer, UTIL_RESPONSE_BUFFER_LENGTH, &bytes_read) != IOTCS_RESULT_OK);
    LOG_INFOSN(tmp_response_buffer, bytes_read);
    LOG_INFOS("<<<<<<<<<<<<<<<<<<<<< Recv");
    GOTO_ERR(iotcs_port_ssl_disconnect() != IOTCS_RESULT_OK);
    UTIL_UNLOCK_RWSSL();
#ifdef IOTCS_MESSAGE_DISPATCHER
    cl_message_dispatcher_counters_add_bytes_received(bytes_read);
#endif
    GOTO_ERR(http_parse(response, tmp_response_buffer, bytes_read) != IOTCS_RESULT_OK);
    return IOTCS_RESULT_OK;
error:
    iotcs_port_ssl_disconnect();
    UTIL_UNLOCK_RWSSL();
    return IOTCS_RESULT_FAIL;
}

iotcs_result http_proceed_request(
        protocol_request request,
        protocol_response* response) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    response->body = NULL;
    response->min_accept_bytes = NULL;

    result = send_request(request, response);

    if ((result == IOTCS_RESULT_OK) && (response->status_code == PROTOCOL_RESPONSE_CODE_UNAUTHORIZED)) {
        result = IOTCS_RESULT_CANNOT_AUTHORIZE;
    }

    return result;
}

#ifdef IOTCS_LONG_POLLING

iotcs_result http_proceed_request_lp(
        protocol_request request,
        protocol_response* response,
        int32_t timeout) {
    iotcs_result result = IOTCS_RESULT_FAIL;
    response->body = NULL;
    response->min_accept_bytes = NULL;

    result = send_lp_request(request, response, timeout);

    if ((result == IOTCS_RESULT_OK) && (response->status_code == PROTOCOL_RESPONSE_CODE_UNAUTHORIZED)) {
        result = IOTCS_RESULT_CANNOT_AUTHORIZE;
    }

    return result;
}
#endif

void http_check_min_accept_bytes(protocol_response* response) {
    if (response->min_accept_bytes) {
        LOG_ERR("The server request of %s bytes exceeds the client buffer of %d bytes",
                response->min_accept_bytes, IOTCS_MAX_ACCEPTED_BYTES);
    }
}
