/* ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you 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 "serf.h"
#include "serf_bucket_util.h"
#include "serf_private.h"

#ifdef SERF_HAVE_BROTLI

#include <brotli/decode.h>

int serf_bucket_is_brotli_supported(void)
{
    return TRUE;
}

typedef struct brotli_decompress_context_t {
    BrotliDecoderState *state;
    serf_bucket_t *input;
    serf_bucket_t *output;
    const char *pending_data;
    apr_size_t pending_len;
    /* Did we see an APR_EOF for the input stream? */
    int hit_eof;
    /* Did the decoder report the end of the compressed data? */
    int done;
} brotli_decompress_context_t;

static void *alloc_func(void *opaque, size_t size)
{
    serf_bucket_alloc_t *alloc = opaque;

    return serf_bucket_mem_alloc(alloc, size);
}

static void free_func(void *opaque, void *block)
{
    serf_bucket_alloc_t *alloc = opaque;

    if (block)
        serf_bucket_mem_free(alloc, block);
}

/* Implements serf_bucket_aggregate_eof_t */
static apr_status_t refill_output(void *baton, serf_bucket_t *aggregate_bkt)
{
    brotli_decompress_context_t *ctx = baton;

    while (1) {
        if (ctx->pending_len == 0 && !ctx->hit_eof) {
            apr_status_t status;

            status = serf_bucket_read(ctx->input, SERF_READ_ALL_AVAIL,
                                      &ctx->pending_data, &ctx->pending_len);
            if (APR_STATUS_IS_EOF(status))
                ctx->hit_eof = TRUE;
            else if (status)
                return status;
        }

        if (ctx->done && ctx->hit_eof && ctx->pending_len == 0) {
            return APR_EOF;
        }
        else if (ctx->done) {
            /* Finished with some input still there in the bucket, that's
             * an error. */
            return SERF_ERROR_DECOMPRESSION_FAILED;
        }
        else {
            BrotliDecoderResult result;
            apr_size_t avail_out = 0;

            result = BrotliDecoderDecompressStream(
                         ctx->state, &ctx->pending_len,
                         (const uint8_t **)&ctx->pending_data, &avail_out,
                         NULL, NULL);

            if (result == BROTLI_DECODER_RESULT_ERROR) {
                return SERF_ERROR_DECOMPRESSION_FAILED;
            }
            else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
                     && ctx->hit_eof) {
                /* The decoder says it requires more data, but we don't have
                 * it.  This could happen either if the input is truncated or
                 * corrupted, but as we don't know for sure, return a generic
                 * error. */
                return SERF_ERROR_DECOMPRESSION_FAILED;
            }
            else if (result == BROTLI_DECODER_RESULT_SUCCESS
                     && !BrotliDecoderHasMoreOutput(ctx->state)) {
                ctx->done = TRUE;
            }

            if (BrotliDecoderHasMoreOutput(ctx->state)) {
                serf_bucket_t *output_bkt;
                const uint8_t *output;
                apr_size_t output_len = 0;

                /* There is some output for us.  Place it into the aggregate
                 * bucket, and avoid making a copy by wrapping a pointer to
                 * the internal output buffer.  This data is valid until the
                 * next call to BrotliDecoderDecompressStream(), which won't
                 * happen until this bucket is read. */
                output = BrotliDecoderTakeOutput(ctx->state, &output_len);
                output_bkt = serf_bucket_simple_create((const char *)output,
                                                       output_len, NULL, NULL,
                                                       aggregate_bkt->allocator);
                serf_bucket_aggregate_append(aggregate_bkt, output_bkt);

                return APR_SUCCESS;
            }
        }
    }
}

serf_bucket_t *
serf_bucket_brotli_decompress_create(serf_bucket_t *stream,
                                     serf_bucket_alloc_t *alloc)
{
    brotli_decompress_context_t *ctx =
        serf_bucket_mem_calloc(alloc, sizeof(*ctx));

    ctx->state = BrotliDecoderCreateInstance(alloc_func, free_func, alloc);
    ctx->input = stream;
    ctx->output = serf_bucket_aggregate_create(alloc);
    ctx->pending_data = NULL;
    ctx->pending_len = 0;
    ctx->hit_eof = FALSE;
    ctx->done = FALSE;

    serf_bucket_aggregate_hold_open(ctx->output, refill_output, ctx);

    return serf_bucket_create(&serf_bucket_type_brotli_decompress, alloc, ctx);
}

static apr_status_t serf_brotli_decompress_read(serf_bucket_t *bucket,
                                                apr_size_t requested,
                                                const char **data,
                                                apr_size_t *len)
{
    brotli_decompress_context_t *ctx = bucket->data;

    return serf_bucket_read(ctx->output, requested, data, len);
}

static apr_status_t serf_brotli_decompress_readline(serf_bucket_t *bucket,
                                                    int acceptable, int *found,
                                                    const char **data,
                                                    apr_size_t *len)
{
    brotli_decompress_context_t *ctx = bucket->data;

    return serf_bucket_readline(ctx->output, acceptable, found, data, len);
}

static apr_status_t serf_brotli_decompress_peek(serf_bucket_t *bucket,
                                                const char **data,
                                                apr_size_t *len)
{
    brotli_decompress_context_t *ctx = bucket->data;

    return serf_bucket_peek(ctx->output, data, len);
}

static void serf_brotli_decompress_destroy_and_data(serf_bucket_t *bucket)
{
    brotli_decompress_context_t *ctx = bucket->data;

    BrotliDecoderDestroyInstance(ctx->state);
    serf_bucket_destroy(ctx->input);
    serf_bucket_destroy(ctx->output);
    serf_default_destroy_and_data(bucket);
}

static apr_status_t serf_brotli_decompress_set_config(serf_bucket_t *bucket,
                                                      serf_config_t *config)
{
    brotli_decompress_context_t *ctx = bucket->data;
    apr_status_t status;

    status = serf_bucket_set_config(ctx->input, config);
    if (status)
        return status;

    return serf_bucket_set_config(ctx->output, config);
}

const serf_bucket_type_t serf_bucket_type_brotli_decompress = {
    "BROTLI-DECOMPRESS",
    serf_brotli_decompress_read,
    serf_brotli_decompress_readline,
    serf_default_read_iovec,
    serf_default_read_for_sendfile,
    serf_buckets_are_v2,
    serf_brotli_decompress_peek,
    serf_brotli_decompress_destroy_and_data,
    serf_default_read_bucket,
    serf_default_get_remaining,
    serf_brotli_decompress_set_config,
};

#else /* SERF_HAVE_BROTLI */

int serf_bucket_is_brotli_supported(void)
{
    return FALSE;
}

serf_bucket_t *
serf_bucket_brotli_decompress_create(serf_bucket_t *stream,
                                     serf_bucket_alloc_t *alloc)
{
    return NULL;
}

const serf_bucket_type_t serf_bucket_type_brotli_decompress = { 0 };

#endif /* SERF_HAVE_BROTLI */
