/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
 *
 * Licensed 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.
 */

#ifndef __mod_h2__h2_task__
#define __mod_h2__h2_task__

/**
 * A h2_task fakes a HTTP/1.1 request from the data in a HTTP/2 stream 
 * (HEADER+CONT.+DATA) the module recieves.
 *
 * In order to answer a HTTP/2 stream, we want all Apache httpd infrastructure
 * to be involved as usual, as if this stream can as a separate HTTP/1.1
 * request. The basic trickery to do so was derived from google's mod_spdy
 * source. Basically, we fake a new conn_rec object, even with its own
 * socket and give it to ap_process_connection().
 *
 * Since h2_task instances are executed in separate threads, we may have
 * different lifetimes than our h2_stream or h2_session instances. Basically,
 * we would like to be as standalone as possible.
 *
 * Finally, to keep certain connection level filters, such as ourselves and
 * especially mod_ssl ones, from messing with our data, we need a filter
 * of our own to disble those.
 */

struct apr_thread_cond_t;
struct h2_conn;
struct h2_mplx;
struct h2_task;
struct h2_resp_head;
struct h2_worker;

typedef struct h2_task h2_task;

struct h2_task {
    /** Links to the rest of the tasks */
    APR_RING_ENTRY(h2_task) link;

    const char *id;
    int stream_id;
    struct h2_mplx *mplx;
    
    volatile apr_uint32_t has_started;
    volatile apr_uint32_t has_finished;
    
    const char *method;
    const char *scheme;
    const char *authority;
    const char *path;
    apr_table_t *headers;
    int input_eos;

    struct conn_rec *c;
};

typedef struct h2_task_env h2_task_env;

struct h2_task_env {
    const char *id;
    int stream_id;
    struct h2_mplx *mplx;
    
    apr_pool_t *pool;              /* pool for task lifetime things */
    apr_bucket_alloc_t *bucket_alloc;
    
    const char *method;
    const char *scheme;
    const char *authority;
    const char *path;
    apr_table_t *headers;
    int input_eos;
    
    int serialize_headers;

    struct conn_rec c;
    struct h2_task_input *input;
    struct h2_task_output *output;
    
    struct apr_thread_cond_t *io;   /* used to wait for events on */
};

/**
 * The magic pointer value that indicates the head of a h2_task list
 * @param  b The task list
 * @return The magic pointer value
 */
#define H2_TASK_LIST_SENTINEL(b)	APR_RING_SENTINEL((b), h2_task, link)

/**
 * Determine if the task list is empty
 * @param b The list to check
 * @return true or false
 */
#define H2_TASK_LIST_EMPTY(b)	APR_RING_EMPTY((b), h2_task, link)

/**
 * Return the first task in a list
 * @param b The list to query
 * @return The first task in the list
 */
#define H2_TASK_LIST_FIRST(b)	APR_RING_FIRST(b)

/**
 * Return the last task in a list
 * @param b The list to query
 * @return The last task int he list
 */
#define H2_TASK_LIST_LAST(b)	APR_RING_LAST(b)

/**
 * Insert a single task at the front of a list
 * @param b The list to add to
 * @param e The task to insert
 */
#define H2_TASK_LIST_INSERT_HEAD(b, e) do {				\
    h2_task *ap__b = (e);                                        \
    APR_RING_INSERT_HEAD((b), ap__b, h2_task, link);	\
} while (0)

/**
 * Insert a single task at the end of a list
 * @param b The list to add to
 * @param e The task to insert
 */
#define H2_TASK_LIST_INSERT_TAIL(b, e) do {				\
    h2_task *ap__b = (e);					\
    APR_RING_INSERT_TAIL((b), ap__b, h2_task, link);	\
} while (0)

/**
 * Get the next task in the list
 * @param e The current task
 * @return The next task
 */
#define H2_TASK_NEXT(e)	APR_RING_NEXT((e), link)
/**
 * Get the previous task in the list
 * @param e The current task
 * @return The previous task
 */
#define H2_TASK_PREV(e)	APR_RING_PREV((e), link)

/**
 * Remove a task from its list
 * @param e The task to remove
 */
#define H2_TASK_REMOVE(e)	APR_RING_REMOVE((e), link)


h2_task *h2_task_create(long session_id, int stream_id, 
                        apr_pool_t *pool, struct h2_mplx *mplx,
                        conn_rec *c);

apr_status_t h2_task_destroy(h2_task *task);

void h2_task_set_request(h2_task *task, 
                         const char *method, 
                         const char *scheme, 
                         const char *authority, 
                         const char *path, 
                         apr_table_t *headers, int eos);


apr_status_t h2_task_do(h2_task *task, struct h2_worker *worker);
apr_status_t h2_task_process_request(h2_task_env *env);

int h2_task_has_started(h2_task *task);
void h2_task_set_started(h2_task *task);
int h2_task_has_finished(h2_task *task);
void h2_task_set_finished(h2_task *task);

void h2_task_register_hooks(void);
void h2_task_die(h2_task_env *env, int status, request_rec *r);

#endif /* defined(__mod_h2__h2_task__) */
