/*******************************************************************************
 apr_buckets_usp.c

 Copyright (C) 2012-2026 CodeShop B.V.

 For licensing see the LICENSE file
******************************************************************************/

#include <httpd.h>
#include <http_core.h>
#include <http_log.h>
#include <apr_buckets.h>
#include <apr_buckets_usp.h>

#include <mod_streaming_export.h>
#include <output_bucket.h>
#include <buckets_t.h>

////////////////////////////////////////////////////////////////////////////////

typedef struct
{
  apr_bucket_refcount refcount;
  bucket_t* bucket;
  request_rec* request;
} apr_bucket_usp;

static void usp_bucket_log_error(void* context, fmp4_result result,
                                 char const* error)
{
  request_rec* request = context;
  ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, request,
                "usp_bucket_read: %s %s", fmp4_result_to_string(result), error);
}

static apr_status_t usp_bucket_read(apr_bucket *b, const char **str,
                                    apr_size_t *len, apr_read_type_e block)
{
  (void)block; // suppress unused parameter warning

  fmp4_result result = FMP4_OK;

  apr_bucket_usp* usp = b->data;

  bucket_t* bucket = usp->bucket;
  request_rec* request = usp->request;

  int update_size = bucket_size(bucket) == UINT64_MAX;

#if 1 && defined(_DEBUG)
  uint32_t type = get_bucket_type(bucket);

  fprintf(stderr, "usp_bucket_read(%p) length=%d start=%u type=%c%c%c%c size=%u\n",
          b, (int32_t)(b->length), (uint32_t)(b->start),
          (char)(type >> 24),
          (char)(type >> 16),
          (char)(type >> 8),
          (char)(type >> 0),
          (uint32_t)bucket_size(bucket));
#endif

  result = bucket_read(bucket, (const uint8_t**)str, len, usp_bucket_log_error,
                       request);

#if 1 && defined(_DEBUG)
  type = get_bucket_type(bucket);
  fprintf(stderr, "usp_bucket_read(%p) length=%d len=%u type=%c%c%c%c size=%u\n",
          b, (int32_t)(b->length), (uint32_t)(*len),
          (char)(type >> 24),
          (char)(type >> 16),
          (char)(type >> 8),
          (char)(type >> 0),
          (uint32_t)(bucket_size(bucket)));
#endif

  // if it is a dynamic bucket for which we didn't know the size yet, then
  // set it now.
  if(update_size)
  {
    b->length = bucket_size(bucket);
  }

  // The brigade may have been partitioned (e.g. by the byterange_filter), so
  // we need to adjust the returned str and len.

  if(b->start + b->length > *len)
  {
    return APR_EGENERAL;
  }

  *str += b->start;
  *len = b->length;

  bucket_t *next = bucket_next(bucket);
  if(bucket != next)
  {
    do
    {
      bucket_remove(bucket);

      // consumes bucket
      apr_bucket* new_bucket = apr_bucket_usp_create(next, b->list, request);
      APR_BUCKET_INSERT_AFTER(b, new_bucket);
      b = new_bucket;

      bucket = next;
      next = bucket_next(bucket);

    } while(bucket != next);

    //
    {
      apr_bucket* flush_bucket = apr_bucket_flush_create(b->list);
      APR_BUCKET_INSERT_AFTER(b, flush_bucket);
    }
  }

  return result == FMP4_OK ? APR_SUCCESS : APR_EGENERAL;
}

static void usp_bucket_destroy(void *data)
{
  apr_bucket_usp *h = data;

  if(apr_bucket_shared_destroy(h))
  {
    bucket_delete(h->bucket);
    apr_bucket_free(h);
  }
}

apr_bucket* apr_bucket_usp_make(apr_bucket *b, bucket_t* bucket,
                                request_rec* request)
{
  apr_bucket_usp *h = apr_bucket_alloc(sizeof(*h), b->list);
  h->bucket = bucket;
  h->request = request;

  uint64_t size = bucket_size(bucket);
  b = apr_bucket_shared_make(b, h, 0,
    size == UINT64_MAX ? (apr_size_t)(-1) : size);

  b->type = &apr_bucket_type_usp;

  return b;
}

apr_bucket* apr_bucket_usp_create(bucket_t* bucket, apr_bucket_alloc_t *list,
                                  request_rec* request)
{
  apr_bucket* b = apr_bucket_alloc(sizeof(*b), list);

  APR_BUCKET_INIT(b);
  b->free = apr_bucket_free;
  b->list = list;
  return apr_bucket_usp_make(b, bucket, request);
}

const apr_bucket_type_t apr_bucket_type_usp = {
    "USP", 5, APR_BUCKET_DATA,
    usp_bucket_destroy,
    usp_bucket_read,
    apr_bucket_setaside_noop,
    apr_bucket_shared_split,
    apr_bucket_shared_copy
};

////////////////////////////////////////////////////////////////////////////////

// convert the Apache brigade to our bucket structure.
apr_status_t
apache_brigade_to_usp_buckets(apr_bucket_brigade* bb, buckets_t* buckets)
{
  apr_status_t rv = APR_SUCCESS;

  apr_off_t length = -1;
  rv = apr_brigade_length(bb, 0, &length);
  if(rv != APR_SUCCESS)
  {
    return rv;
  }

  ap_assert(length != -1);

  apr_size_t bytes = (apr_size_t)length;
  bucket_t* bucket = bucket_heap_create(NULL, bytes);
  bucket_insert_tail(buckets, bucket);
  char* dst;
  fmp4_result result = bucket_write(bucket, (uint8_t **) &dst, NULL);
  if(result != FMP4_OK)
  {
    return APR_EGENERAL;
  }
  rv = apr_brigade_flatten(bb, dst, &bytes);
  if(rv != APR_SUCCESS)
  {
    return rv;
  }

//apr_brigade_cleanup(bb);

  return APR_SUCCESS;
}

// convert our bucket structure to the Apache brigade.
apr_status_t
usp_buckets_to_apache_brigade(buckets_t* buckets, apr_bucket_brigade* bb,
                              request_rec* request)
{
  bucket_t* head = buckets->bucket_;
  bucket_t* bucket = bucket_next(head);

  while(bucket != head)
  {
    apr_bucket* e;

    bucket_t* next = bucket_next(bucket);
    bucket_remove(bucket);
    e = apr_bucket_usp_create(bucket, bb->bucket_alloc, request);
    APR_BRIGADE_INSERT_TAIL(bb, e);

    bucket = next;
  }

  return APR_SUCCESS;
}

// End Of File

