/* 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.
 */

#include <assert.h>
#include <stddef.h>

#include <apr_strings.h>

#include <httpd.h>
#include <http_core.h>
#include <http_connection.h>
#include <http_log.h>

#include "h2_private.h"
#include "h2_session.h"
#include "h2_stream.h"
#include "h2_task.h"
#include "h2_stream_set.h"

#define H2_STREAM_IDX(list, i) ((h2_stream**)(list)->elts)[i]

struct h2_stream_set {
    apr_array_header_t *list;
};

h2_stream_set *h2_stream_set_create(apr_pool_t *pool)
{
    h2_stream_set *sp = apr_pcalloc(pool, sizeof(h2_stream_set));
    if (sp) {
        sp->list = apr_array_make(pool, 100, sizeof(h2_stream*));
        if (!sp->list) {
            return NULL;
        }
    }
    return sp;
}

void h2_stream_set_destroy(h2_stream_set *sp)
{
    (void)sp;
}

static int h2_stream_id_cmp(const void *s1, const void *s2)
{
    h2_stream **pstream1 = (h2_stream **)s1;
    h2_stream **pstream2 = (h2_stream **)s2;
    return (*pstream1)->id - (*pstream2)->id;
}

h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id)
{
    /* we keep the array sorted by id, so lookup can be done
     * by bsearch.
     */
    h2_stream key;
    h2_stream *pkey, **ps;
    memset(&key, 0, sizeof(key));
    key.id = stream_id;
    pkey = &key;
    ps = bsearch(&pkey, sp->list->elts, sp->list->nelts, 
                             sp->list->elt_size, h2_stream_id_cmp);
    return ps? *ps : NULL;
}

static void h2_stream_set_sort(h2_stream_set *sp)
{
    qsort(sp->list->elts, sp->list->nelts, sp->list->elt_size, 
          h2_stream_id_cmp);
}

apr_status_t h2_stream_set_add(h2_stream_set *sp, h2_stream *stream)
{
    h2_stream *existing = h2_stream_set_get(sp, stream->id);
    if (!existing) {
        int last;
        APR_ARRAY_PUSH(sp->list, h2_stream*) = stream;
        /* Normally, streams get added in ascending order if id. We
         * keep the array sorted, so we just need to check of the newly
         * appended stream has a lower id than the last one. if not,
         * sorting is not necessary.
         */
        last = sp->list->nelts - 1;
        if (last > 0 
            && (H2_STREAM_IDX(sp->list, last)->id 
                < H2_STREAM_IDX(sp->list, last-1)->id)) {
            h2_stream_set_sort(sp);
        }
    }
    return APR_SUCCESS;
}

h2_stream *h2_stream_set_remove(h2_stream_set *sp, h2_stream *stream)
{
    int i;
    for (i = 0; i < sp->list->nelts; ++i) {
        h2_stream *s = H2_STREAM_IDX(sp->list, i);
        if (s == stream) {
            int n;
            --sp->list->nelts;
            n = sp->list->nelts - i;
            if (n > 0) {
                /* Close the hole in the array by moving the upper
                 * parts down one step.
                 */
                h2_stream **selts = (h2_stream**)sp->list->elts;
                memmove(selts+i, selts+i+1, n * sizeof(h2_stream*));
            }
            return s;
        }
    }
    return NULL;
}

void h2_stream_set_remove_all(h2_stream_set *sp)
{
    sp->list->nelts = 0;
}

int h2_stream_set_is_empty(h2_stream_set *sp)
{
    AP_DEBUG_ASSERT(sp);
    return sp->list->nelts == 0;
}

h2_stream *h2_stream_set_find(h2_stream_set *sp,
                              h2_stream_set_match_fn *match, void *ctx)
{
    h2_stream *s = NULL;
    int i;
    for (i = 0; !s && i < sp->list->nelts; ++i) {
        s = match(ctx, H2_STREAM_IDX(sp->list, i));
    }
    return s;
}

void h2_stream_set_iter(h2_stream_set *sp,
                        h2_stream_set_iter_fn *iter, void *ctx)
{
    int i;
    for (i = 0; i < sp->list->nelts; ++i) {
        h2_stream *s = H2_STREAM_IDX(sp->list, i);
        if (!iter(ctx, s)) {
            break;
        }
    }
}

apr_size_t h2_stream_set_size(h2_stream_set *sp)
{
    return sp->list->nelts;
}

