/*
 * Copyright 2014 Cloud Linux Zug GmbH
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * http://cloudlinux.com/docs/LICENSE.TXT
 *
 * The part the support library for lsapi & proxy_lsapi Apache modules
 * author Alexander Demeshko <ademeshko@cloudlinux.com>
 *
 */
 
#include <lscapi_util.h>

#include <util_script.h>
#include <http_request.h>

struct hdrs_context {
    const lscapi_resphdr_info_t *hdrsInfoPtr;
    int curNum;
    int curHdrSize;
    apr_off_t curOff;
    request_rec *r;
    lsapi_svr_conf_t *svrcfg;
};

static int getsfunc_HDRBUF(char *w, int len, void *phdrCon) {
    struct hdrs_context *c = (struct hdrs_context*)phdrCon;

    if(c->curNum >= c->hdrsInfoPtr->hdrNum) { // normal exit
        w[0] = '\0';
        return 1;
    }

    if(c->curOff > c->hdrsInfoPtr->dataLen) {
        if(c->svrcfg->debug_enabled) {
            lscapi_rlog(APLOG_NOTICE, 0, c->r,
                        "Wrong packet format: offset %" APR_OFF_T_FMT " is more than datalen %"APR_OFF_T_FMT, 
                        c->curOff, c->hdrsInfoPtr->dataLen);
        }
        return -1;
    }

    if(!c->curHdrSize) {
        c->curHdrSize = c->hdrsInfoPtr->hdrSizes[c->curNum];
    }


    if(c->curHdrSize < len) {
        memcpy(w, c->hdrsInfoPtr->buf + c->curOff, c->curHdrSize);
        c->curNum++;
        c->curOff += c->curHdrSize;
        c->curHdrSize = 0;
    } else {
        memcpy(w, c->hdrsInfoPtr->buf + c->curOff, len-1);
        w[len-1] = '\0';
        c->curOff += len-1;
        c->curHdrSize -= len-1;
    }

    return 1;
}

#ifdef LSCAPI_WITH_DUMP_HEADER
static void DumpHeaders(request_rec *r, const lscapi_resphdr_info_t *hdrsInfoPtr) {
    static unsigned long serialNo;
    char fname[256];
    
    
    snprintf(fname, sizeof fname, "/tmp/%s-%08lu.log", r->hostname, serialNo);
    serialNo++;

    FILE *fptr = fopen(fname, "wb");
    if(fptr) {
        lscapi_rlog(APLOG_ERR, 0, r,
                    "Dump headers into (%s)", fname);
        fwrite(hdrsInfoPtr->buf, 1, hdrsInfoPtr->dataLen, fptr);
        fclose(fptr);
    }
}
#endif

static int ParseHeaders(request_rec *r, lsapi_svr_conf_t *svrcfg, const lscapi_resphdr_info_t *hdrsInfoPtr) {
    char buffer[MAX_STRING_LEN];
    struct hdrs_context context;
    context.hdrsInfoPtr = hdrsInfoPtr;
    context.curHdrSize = 0;
    context.curOff = sizeof(lsapi_resp_info) + sizeof(uint16_t)*hdrsInfoPtr->hdrNum;
    context.curNum = 0;
    context.svrcfg = svrcfg;
    context.r = r;

    int res = ap_scan_script_header_err_core(r, buffer, getsfunc_HDRBUF, 
                                                (void *) &context);
    if(res != OK) {
        apr_bucket *tmp_b = apr_bucket_eos_create(r->connection->bucket_alloc);
        apr_bucket_brigade *ob = apr_brigade_create(r->pool, r->connection->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(ob, tmp_b);
        r->status = res;
        ap_pass_brigade(r->output_filters, ob);
        apr_brigade_cleanup(ob);
        apr_brigade_destroy(ob);
    }

    return res;
}

apr_status_t lscapi_do_request(lsapi_conn_t *conn, lsapi_svr_conf_t *svrcfg)
{
    char errbuf[256];
    int rc;
    int php_fatal_error = 0;
    request_rec *r = conn->r;
    unsigned eventMask;
    
    eventMask = 0;
    rc = lscapi_send_request(conn->backend, &eventMask, errbuf, sizeof errbuf);
    if (rc != 0) {
        lscapi_rlog(APLOG_ERR, 0, r, "Error sending request: %s", errbuf);
        lscapi_set_error(conn->lscapi); 
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if(eventMask & LSCAPI_BACKEND_LOG_RECEIVED) {
        if(eventMask & LSCAPI_BACKEND_LOG_FATAL) {
            lscapi_rlog(APLOG_ERR, 0, r, "Backend fatal error: %s", lscapi_get_backend_log(conn->lscapi) );
            php_fatal_error = 1;
            r->status = HTTP_INTERNAL_SERVER_ERROR;
        } else if(svrcfg->debug_enabled) {
            lscapi_rlog(APLOG_NOTICE, 0, r, "Backend log: %s", lscapi_get_backend_log(conn->lscapi) );
        }
    }

    lscapi_resphdr_info_t hdrsInfo;

    eventMask = 0;
    rc = lscapi_receive_response_header(conn->backend, &hdrsInfo, &eventMask, errbuf, sizeof errbuf);
    if(rc != 0) {
        lscapi_rlog(APLOG_ERR, 0, r, "Error receiving response header: %s", errbuf);
        lscapi_set_error(conn->lscapi); 
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if(eventMask & LSCAPI_BACKEND_LOG_RECEIVED) {
        if(eventMask & LSCAPI_BACKEND_LOG_FATAL) {
            lscapi_rlog(APLOG_ERR, 0, r, "Backend fatal error: %s", lscapi_get_backend_log(conn->lscapi) );
            php_fatal_error = 1;
            r->status = HTTP_INTERNAL_SERVER_ERROR;
        } else if(svrcfg->debug_enabled) {
            lscapi_rlog(APLOG_NOTICE, 0, r, "Backend log: %s", lscapi_get_backend_log(conn->lscapi) );
        }
    }

    rc = ParseHeaders(r, svrcfg, &hdrsInfo);
    if(rc != OK) {
        
        if(rc == HTTP_NOT_MODIFIED) {
            r->status = rc;
            return OK;
        }
            
        lscapi_rlog(APLOG_ERR, 0, r, "ParseHeaders failed. Response size %"APR_SIZE_T_FMT, 
                    hdrsInfo.dataLen);
#ifdef LSCAPI_WITH_DUMP_HEADER
        if(svrcfg->hostname_on_debug) {
            if(!strcmp(r->hostname, svrcfg->hostname_on_debug))
            DumpHeaders(r, &hdrsInfo);
        }
#endif
        lscapi_set_error(conn->lscapi);
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    if(!php_fatal_error) {
        r->status = hdrsInfo.respStatus ? hdrsInfo.respStatus : HTTP_OK;
    }

    const char *location = apr_table_get(r->headers_out, "Location");

    if (location && location[0] == '/' && r->status == HTTP_OK) {
        // This redirect needs to be a GET no matter what the original method was.
        r->method = "GET";
        r->method_number = M_GET;

        apr_table_unset(r->headers_in, "Content-Length");

        ap_internal_redirect_handler(location, r);
        return OK;
    }
    else if (location && r->status == HTTP_OK) {
        return HTTP_MOVED_TEMPORARILY;
    }

    char buf[LSAPI_MAX_DATA_PACKET_LEN];
    apr_bucket_brigade *ob = apr_brigade_create(r->pool, r->connection->bucket_alloc);

    while(1) {

        size_t bufLen = LSAPI_MAX_DATA_PACKET_LEN;
        eventMask = 0;
        rc = lscapi_receive_response_chunk(conn->backend, buf, &bufLen, &eventMask, errbuf, sizeof errbuf);
        if(rc != 0) {
            lscapi_rlog(APLOG_ERR, 0, r, "Error receiving response: %s", errbuf);
            lscapi_set_error(conn->lscapi); 
            return HTTP_INTERNAL_SERVER_ERROR;
        }

        if(eventMask & LSCAPI_BACKEND_LOG_RECEIVED) {
            if(eventMask & LSCAPI_BACKEND_LOG_FATAL) {
                lscapi_rlog(APLOG_ERR, 0, r, "Backend fatal error: %s", lscapi_get_backend_log(conn->lscapi) );
                php_fatal_error = 1;
                r->status = HTTP_INTERNAL_SERVER_ERROR;
            } else if(svrcfg->debug_enabled) {
                lscapi_rlog(APLOG_NOTICE, 0, r, "Backend log: %s", lscapi_get_backend_log(conn->lscapi) );
            }
        }
        
        if(eventMask & LSCAPI_RESPONSE_FINISHED) {
            apr_bucket *eosB = apr_bucket_eos_create(r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(ob, eosB);
            ap_pass_brigade(r->output_filters, ob);
            break;
        }

        apr_bucket *b = apr_bucket_transient_create(buf, bufLen, r->connection->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(ob, b);
        ap_pass_brigade(r->output_filters, ob);

    } //while(!done)

    //apr_brigade_cleanup(ib);
    apr_brigade_cleanup(ob);

    if(php_fatal_error) {
        return r->status;
    }
    
    return OK;
}
