/* 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 h2_proxy_session_h
#define h2_proxy_session_h

#define H2_ALEN(a)          (sizeof(a)/sizeof((a)[0]))

#include <nghttp2/nghttp2.h>

struct h2_proxy_iqueue;
struct h2_proxy_ihash_t;

typedef enum {
    H2_STREAM_ST_IDLE,
    H2_STREAM_ST_OPEN,
    H2_STREAM_ST_RESV_LOCAL,
    H2_STREAM_ST_RESV_REMOTE,
    H2_STREAM_ST_CLOSED_INPUT,
    H2_STREAM_ST_CLOSED_OUTPUT,
    H2_STREAM_ST_CLOSED,
} h2_proxy_stream_state_t;

typedef enum {
    H2_PROXYS_ST_INIT,             /* send initial SETTINGS, etc. */
    H2_PROXYS_ST_DONE,             /* finished, connection close */
    H2_PROXYS_ST_IDLE,             /* no streams to process */
    H2_PROXYS_ST_BUSY,             /* read/write without stop */
    H2_PROXYS_ST_WAIT,             /* waiting for tasks reporting back */
    H2_PROXYS_ST_LOCAL_SHUTDOWN,   /* we announced GOAWAY */
    H2_PROXYS_ST_REMOTE_SHUTDOWN,  /* client announced GOAWAY */
} h2_proxys_state;

typedef enum {
    H2_PROXYS_EV_INIT,             /* session was initialized */
    H2_PROXYS_EV_LOCAL_GOAWAY,     /* we send a GOAWAY */
    H2_PROXYS_EV_REMOTE_GOAWAY,    /* remote send us a GOAWAY */
    H2_PROXYS_EV_CONN_ERROR,       /* connection error */
    H2_PROXYS_EV_PROTO_ERROR,      /* protocol error */
    H2_PROXYS_EV_CONN_TIMEOUT,     /* connection timeout */
    H2_PROXYS_EV_NO_IO,            /* nothing has been read or written */
    H2_PROXYS_EV_STREAM_SUBMITTED, /* stream has been submitted */
    H2_PROXYS_EV_STREAM_DONE,      /* stream has been finished */
    H2_PROXYS_EV_STREAM_RESUMED,   /* stream signalled availability of headers/data */
    H2_PROXYS_EV_DATA_READ,        /* connection data has been read */
    H2_PROXYS_EV_NGH2_DONE,        /* nghttp2 wants neither read nor write anything */
    H2_PROXYS_EV_PRE_CLOSE,        /* connection will close after this */
} h2_proxys_event_t;


typedef struct h2_proxy_session h2_proxy_session;
typedef void h2_proxy_request_done(h2_proxy_session *s, request_rec *r,
                                   apr_status_t status, int touched);

struct h2_proxy_session {
    const char *id;
    conn_rec *c;
    proxy_conn_rec *p_conn;
    proxy_server_conf *conf;
    apr_pool_t *pool;
    nghttp2_session *ngh2;   /* the nghttp2 session itself */
    
    unsigned int aborted : 1;
    unsigned int check_ping : 1;
    unsigned int h2_front : 1; /* if front-end connection is HTTP/2 */

    h2_proxy_request_done *done;
    void *user_data;
    
    unsigned char window_bits_stream;
    unsigned char window_bits_connection;

    h2_proxys_state state;
    apr_interval_time_t wait_timeout;

    struct h2_proxy_ihash_t *streams;
    struct h2_proxy_iqueue *suspended;
    apr_size_t remote_max_concurrent;
    int last_stream_id;     /* last stream id processed by backend, or 0 */
    apr_time_t last_frame_received;
    
    apr_bucket_brigade *input;
    apr_bucket_brigade *output;
};

h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
                                         proxy_server_conf *conf,
                                         int h2_front, 
                                         unsigned char window_bits_connection,
                                         unsigned char window_bits_stream,
                                         h2_proxy_request_done *done);

apr_status_t h2_proxy_session_submit(h2_proxy_session *s, const char *url,
                                     request_rec *r, int standalone);
                       
/** 
 * Perform a step in processing the proxy session. Will return aftert
 * one read/write cycle and indicate session status by status code.
 * @param s the session to process
 * @return APR_EAGAIN  when processing needs to be invoked again
 *         APR_SUCCESS when all streams have been processed, session still live
 *         APR_EOF     when the session has been terminated
 */
apr_status_t h2_proxy_session_process(h2_proxy_session *s);

void h2_proxy_session_cancel_all(h2_proxy_session *s);

void h2_proxy_session_cleanup(h2_proxy_session *s, h2_proxy_request_done *done);

void h2_proxy_session_update_window(h2_proxy_session *s, 
                                    conn_rec *c, apr_off_t bytes);

#define H2_PROXY_REQ_URL_NOTE   "h2-proxy-req-url"

#endif /* h2_proxy_session_h */
