/*
 * 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_log(APLOG_NOTICE, c->r,
                        "Wrong packet format: offset %" APR_OFF_T_FMT " is more than datalen %lu", 
                        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;
}

static apr_status_t 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) {
        if(svrcfg->debug_enabled) {
            lscapi_log(APLOG_NOTICE, r,
                      "Parsing headers error: %d %s", res, buffer);
        }
        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 APR_EGENERAL;
    }

    return APR_SUCCESS;
}



//#pragma GCC visibility push(default)

apr_status_t lscapi_do_request(lsapi_conn_t *conn, lsapi_svr_conf_t *svrcfg)
{
    char errbuf[256];
    int rc;
    request_rec *r = conn->r;

    unsigned eventMask;
    
    // TODO: Input filters as in mod_cgi. But how we can use their model? - do { read_from_client(); write_to_script() }while(...);
    
    rc = lscapi_send_request(conn->backend, &eventMask, errbuf, sizeof errbuf);
    if (rc != 0) {
        if(svrcfg->debug_enabled) {
            lscapi_log(APLOG_NOTICE, r,
                       "Error sending request: %s", errbuf);
        }
        return HTTP_SERVICE_UNAVAILABLE;
    }

    if(eventMask & LSCAPI_BACKEND_LOG_RECEIVED) {
        if(eventMask & LSCAPI_BACKEND_LOG_FATAL) {
            if(svrcfg->debug_enabled) {
                lscapi_log(APLOG_NOTICE, r,
                          "backend fatal error received on sending request: (%s)", 
                            lscapi_get_backend_log(conn->lscapi) );
            }

            // Comment out this in order to get client code 200 which was generated by backend
/*
            char buffer[MAX_STRING_LEN];
            const char *termch;
            int termarg;
            const char headers[] = "Content-type: text/html\n";
            
            ap_scan_script_header_err_strs_ex(r, buffer, APLOG_MODULE_INDEX, &termch, &termarg,
                                              headers, NULL );

            apr_bucket_brigade *ob = apr_brigade_create(r->pool, r->connection->bucket_alloc);
            apr_bucket *b;
            b = apr_bucket_transient_create("<HTML><BODY>\n<BR/>\n", 19, r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(ob, b);
            b = apr_bucket_transient_create(conn->backendLog, conn->logLen, r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(ob, b);
            b = apr_bucket_transient_create("</BODY></HTML>", 14, r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(ob, b);
            apr_bucket *eosB = apr_bucket_eos_create(r->connection->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(ob, eosB);
            ap_pass_brigade(r->output_filters, ob);
            apr_brigade_cleanup(ob);
            return HTTP_BAD_REQUEST;
*/
        } else {
            if(svrcfg->debug_enabled) {
                lscapi_log(APLOG_NOTICE, r,
                            "backend log received on sending request: (%s)", 
                            lscapi_get_backend_log(conn->lscapi) );
                
            }
        }
    }

    int status  = lscapi_poll_lsphp(conn->backend, APR_POLLIN);
    if(!(status & APR_POLLIN)) {
        if(svrcfg->debug_enabled) {
            lscapi_log(APLOG_NOTICE, r, "backend is not ready: %d", status);
        }
        return HTTP_SERVICE_UNAVAILABLE;
    }

    lscapi_resphdr_info_t hdrsInfo;

    rc = lscapi_receive_response_header(conn->backend, &hdrsInfo, &eventMask, errbuf, sizeof errbuf);
    if(rc != 0) {
        if(svrcfg->debug_enabled) {
            lscapi_log(APLOG_NOTICE, r,
                       "error receiving response header: %s", errbuf);
        }
        return HTTP_SERVICE_UNAVAILABLE;
    }

    if(eventMask & LSCAPI_BACKEND_LOG_RECEIVED) {
        if(svrcfg->debug_enabled) {
            lscapi_log(APLOG_NOTICE, r,
                      "When receiving response header Backend errlog received: (%s)", 
                      lscapi_get_backend_log(conn->lscapi) );
        }
    }

    if(ParseHeaders(r, svrcfg, &hdrsInfo) != APR_SUCCESS) {
        lscapi_set_error(conn->lscapi); 
        return HTTP_SERVICE_UNAVAILABLE;
    }
    r->status = hdrsInfo.respStatus ? hdrsInfo.respStatus : HTTP_OK;

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

    if (location && location[0] == '/' && r->status == 200) {
        // 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 == 200) {
        return HTTP_MOVED_TEMPORARILY;
    }

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

    while(1) {

        status  = lscapi_poll_lsphp(conn->backend, APR_POLLIN);
        if(!(status & APR_POLLIN)) {
            if(svrcfg->debug_enabled) {
                lscapi_log(APLOG_NOTICE, r, "Backend is not ready: %d", status);
            }
            return HTTP_SERVICE_UNAVAILABLE;
        }

        size_t bufLen = LSAPI_MAX_DATA_PACKET_LEN;
        rc = lscapi_receive_response_chunk(conn->backend, buf, &bufLen, &eventMask, errbuf, sizeof errbuf);
        if(rc != 0) {
            if(svrcfg->debug_enabled) {
                lscapi_log(APLOG_NOTICE, r,
                        "Error receiving response: %s", errbuf);
            }
            return HTTP_SERVICE_UNAVAILABLE;
        }

        if(eventMask & LSCAPI_BACKEND_LOG_RECEIVED) {
            if(svrcfg->debug_enabled) {
                lscapi_log(APLOG_NOTICE, r,
                        "When receiving response Backend errlog received: (%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);

    return OK;
}

//#pragma GCC visibility pop
