#include "aos_log.h"
#include "aos_http_io.h"
#include "aos_define.h"
#include <apr_thread_mutex.h>
#include <apr_file_io.h>

aos_pool_t *aos_global_pool = NULL;
apr_file_t *aos_stderr_file = NULL;

aos_http_request_options_t *aos_default_http_request_options = NULL;
aos_http_transport_options_t *aos_default_http_transport_options = NULL;

aos_http_transport_create_pt aos_http_transport_create = aos_curl_http_transport_create;
aos_http_transport_perform_pt aos_http_transport_perform = aos_curl_http_transport_perform;

static apr_thread_mutex_t* requestStackMutexG = NULL;
static CURL *requestStackG[AOS_REQUEST_STACK_SIZE];
static int requestStackCountG;
static char aos_user_agent[256];


static aos_http_transport_options_t *aos_http_transport_options_create(aos_pool_t *p);

CURL *aos_request_get()
{
    CURL *request = NULL;
    
    apr_thread_mutex_lock(requestStackMutexG);
    if (requestStackCountG > 0) {
        request = requestStackG[--requestStackCountG];
    }
    apr_thread_mutex_unlock(requestStackMutexG);

    // If we got one, deinitialize it for re-use
    if (request) {
        curl_easy_reset(request);
    }
    else {
        request = curl_easy_init();
    }

    return request;
}

void request_release(CURL *request)
{
    apr_thread_mutex_lock(requestStackMutexG);

    // If the request stack is full, destroy this one
    // else put this one at the front of the request stack; we do this because
    // we want the most-recently-used curl handle to be re-used on the next
    // request, to maximize our chances of re-using a TCP connection before it
    // times out
    if (requestStackCountG == AOS_REQUEST_STACK_SIZE) {
        apr_thread_mutex_unlock(requestStackMutexG);
        curl_easy_cleanup(request);
    }
    else {
        requestStackG[requestStackCountG++] = request;
        apr_thread_mutex_unlock(requestStackMutexG);
    }
}

void aos_set_default_request_options(aos_http_request_options_t *op)
{
    aos_default_http_request_options = op;
}

void aos_set_default_transport_options(aos_http_transport_options_t *op)
{
    aos_default_http_transport_options = op;
}

aos_http_request_options_t *aos_http_request_options_create(aos_pool_t *p)
{
    aos_http_request_options_t *options;
    
    options = (aos_http_request_options_t *)aos_pcalloc(p, sizeof(aos_http_request_options_t));
    options->speed_limit = AOS_MIN_SPEED_LIMIT;
    options->speed_time = AOS_MIN_SPEED_TIME;
    options->connect_timeout = AOS_CONNECT_TIMEOUT;
    options->dns_cache_timeout = AOS_DNS_CACHE_TIMOUT;
    options->max_memory_size = AOS_MAX_MEMORY_SIZE;
    options->enable_crc = AOS_TRUE;
    options->proxy_auth = NULL;
    options->proxy_host = NULL;

    return options;
}

aos_http_transport_options_t *aos_http_transport_options_create(aos_pool_t *p)
{
    return (aos_http_transport_options_t *)aos_pcalloc(p, sizeof(aos_http_transport_options_t));
}

aos_http_controller_t *aos_http_controller_create(aos_pool_t *p, int owner)
{
    int s;
    aos_http_controller_t *ctl;

    if(p == NULL) {
        if ((s = aos_pool_create(&p, NULL)) != APR_SUCCESS) {
            aos_fatal_log("aos_pool_create failure.");
            return NULL;
        }
    }

    ctl = (aos_http_controller_t *)aos_pcalloc(p, sizeof(aos_http_controller_ex_t));
    ctl->pool = p;
    ctl->owner = owner;
    ctl->options = aos_default_http_request_options;

    return ctl;
}

aos_http_request_t *aos_http_request_create(aos_pool_t *p)
{
    aos_http_request_t *req;

    req = (aos_http_request_t *)aos_pcalloc(p, sizeof(aos_http_request_t));
    req->method = HTTP_GET;
    req->headers = aos_table_make(p, 5);
    req->query_params = aos_table_make(p, 3);
    aos_list_init(&req->body);
    req->type = BODY_IN_MEMORY;
    req->body_len = 0;
    req->pool = p;
    req->read_body = aos_read_http_body_memory;

    return req;
}

aos_http_response_t *aos_http_response_create(aos_pool_t *p)
{
    aos_http_response_t *resp;

    resp = (aos_http_response_t *)aos_pcalloc(p, sizeof(aos_http_response_t));
    resp->status = -1;
    resp->headers = aos_table_make(p, 10);
    aos_list_init(&resp->body);
    resp->type = BODY_IN_MEMORY;
    resp->body_len = 0;
    resp->pool = p;
    resp->write_body = aos_write_http_body_memory;

    return resp;
}

int aos_read_http_body_memory(aos_http_request_t *req, char *buffer, int len)
{
    int wsize;
    int bytes = 0;
    aos_buf_t *b;
    aos_buf_t *n;
    
    aos_list_for_each_entry_safe(aos_buf_t, b, n, &req->body, node) {
        wsize = aos_buf_size(b);
        if (wsize == 0) {
            aos_list_del(&b->node);
            continue;
        }
        wsize = aos_min(len - bytes, wsize);
        if (wsize == 0) {
            break;
        }
        memcpy(buffer + bytes, b->pos, wsize);
        b->pos += wsize;
        bytes += wsize;
        if (b->pos == b->last) {
            aos_list_del(&b->node);
        }
    }

    return bytes;
}

int aos_read_http_body_file(aos_http_request_t *req, char *buffer, int len)
{
    int s;
    char buf[256];
    apr_size_t nbytes = len;
    apr_size_t bytes_left;
    
    if (req->file_buf == NULL || req->file_buf->file == NULL) {
        aos_error_log("request body arg invalid file_buf NULL.");
        return AOSE_INVALID_ARGUMENT;
    }

    if (req->file_buf->file_pos >= req->file_buf->file_last) {
        aos_debug_log("file read finish.");
        return 0;
    }

    bytes_left = (apr_size_t)(req->file_buf->file_last - req->file_buf->file_pos);
    if (nbytes > bytes_left) {
        nbytes = bytes_left;
    }

    if ((s = apr_file_read(req->file_buf->file, buffer, &nbytes)) != APR_SUCCESS) {
        aos_error_log("apr_file_read filure, code:%d %s.", s, apr_strerror(s, buf, sizeof(buf)));
        return AOSE_FILE_READ_ERROR;
    }
    req->file_buf->file_pos += nbytes;
    return nbytes;
}

int aos_write_http_body_memory(aos_http_response_t *resp, const char *buffer, int len)
{
    aos_buf_t *b;

    b = aos_create_buf(resp->pool, len);
    memcpy(b->pos, buffer, len);
    b->last += len;
    aos_list_add_tail(&b->node, &resp->body);
    resp->body_len += len;

    return len;
}

int aos_write_http_body_file(aos_http_response_t *resp, const char *buffer, int len)
{
    int elen;
    int s;
    char buf[256];
    apr_size_t nbytes = len;
    
    if (resp->file_buf == NULL) {
        resp->file_buf = aos_create_file_buf(resp->pool);
    }
    
    if (resp->file_buf->file == NULL) {
        if (resp->file_path == NULL) {
            aos_error_log("resp body file arg NULL.");
            return AOSE_INVALID_ARGUMENT;
        }
        aos_trace_log("open file %s.", resp->file_path);
        if ((elen = aos_open_file_for_write(resp->pool, resp->file_path, resp->file_buf)) != AOSE_OK) {
            return elen;
        }
    }

    assert(resp->file_buf->file != NULL);
    if ((s = apr_file_write(resp->file_buf->file, buffer, &nbytes)) != APR_SUCCESS) {
        aos_error_log("apr_file_write fialure, code:%d %s.", s, apr_strerror(s, buf, sizeof(buf)));
        return AOSE_FILE_WRITE_ERROR;
    }
    
    resp->file_buf->file_last += nbytes;
    resp->body_len += nbytes;

    return nbytes;
}

int aos_http_io_initialize(const char *user_agent_info, int flags)
{
    CURLcode ecode;
    int s;
    char buf[256];
    aos_http_request_options_t *req_options;
    aos_http_transport_options_t *trans_options;

    if ((ecode = curl_global_init(CURL_GLOBAL_ALL &
           ~((flags & AOS_INIT_WINSOCK) ? 0: CURL_GLOBAL_WIN32))) != CURLE_OK) 
    {
        aos_error_log("curl_global_init failure, code:%d %s.\n", ecode, curl_easy_strerror(ecode));
        return AOSE_INTERNAL_ERROR;
    }

    if ((s = apr_initialize()) != APR_SUCCESS) {
        aos_error_log("apr_initialize failue.\n");
        return AOSE_INTERNAL_ERROR;
    }

    if (!user_agent_info || !*user_agent_info) {
        user_agent_info = "Unknown";
    }

    if ((s = aos_pool_create(&aos_global_pool, NULL)) != APR_SUCCESS) {
        aos_error_log("aos_pool_create failure, code:%d %s.\n", s, apr_strerror(s, buf, sizeof(buf)));
        return AOSE_INTERNAL_ERROR;
    }

    if ((s = apr_thread_mutex_create(&requestStackMutexG, APR_THREAD_MUTEX_DEFAULT, aos_global_pool)) != APR_SUCCESS) {
        aos_error_log("apr_thread_mutex_create failure, code:%d %s.\n", s, apr_strerror(s, buf, sizeof(buf)));
        return AOSE_INTERNAL_ERROR;
    }
    requestStackCountG = 0;

    apr_snprintf(aos_user_agent, sizeof(aos_user_agent)-1, "%s(Compatible %s)", 
                 AOS_VER, user_agent_info);

    req_options = aos_http_request_options_create(aos_global_pool);
    trans_options = aos_http_transport_options_create(aos_global_pool);
    trans_options->user_agent = aos_user_agent;

    aos_set_default_request_options(req_options);
    aos_set_default_transport_options(trans_options);

    return AOSE_OK;
}

void aos_http_io_deinitialize()
{
    apr_thread_mutex_destroy(requestStackMutexG);

    while (requestStackCountG--) {
        curl_easy_cleanup(requestStackG[requestStackCountG]);
    }

    if (aos_stderr_file != NULL) {
        apr_file_close(aos_stderr_file);
        aos_stderr_file = NULL;
    }
    if (aos_global_pool != NULL) {
        aos_pool_destroy(aos_global_pool);
        aos_global_pool = NULL;
    }
    apr_terminate();
}

int aos_http_send_request(aos_http_controller_t *ctl, aos_http_request_t *req, aos_http_response_t *resp)
{
    aos_http_transport_t *t;

    t = aos_http_transport_create(ctl->pool);
    t->req = req;
    t->resp = resp;
    t->controller = (aos_http_controller_ex_t *)ctl;
    
    return aos_http_transport_perform(t);
}

