/*
 * Copyright 2023 Red Hat
 * SPDX-License-Identifier: MIT
 *
 * This is a C rewrite of pieces of the d3d12 frontend which is:
 * Copyright © Microsoft Corporation
 */
#ifndef VL_BITSTREAM_H
#define VL_BITSTREAM_H

struct vl_bitstream_encoder {
   uint8_t *bits;
   uint32_t bits_buffer_size;
   uint32_t offset;

   uint32_t enc_buffer;

   int32_t bits_to_go;
   bool prevent_start_code;

   bool internal_buffer;
   bool overflow;
};

#define VL_BITSTREAM_MAX_BUFFER 256

static inline void
vl_bitstream_encoder_clear(struct vl_bitstream_encoder *enc,
                           void *buffer_base,
                           size_t buffer_offset,
                           size_t buffer_limit)
{
   memset(enc, 0, sizeof(*enc));
   enc->bits_to_go = 32;

   if (!buffer_base) {
      enc->bits = malloc(VL_BITSTREAM_MAX_BUFFER);
      enc->bits_buffer_size = VL_BITSTREAM_MAX_BUFFER;
      enc->internal_buffer = true;
   } else {
      enc->bits = (uint8_t *)buffer_base + buffer_offset;
      enc->bits_buffer_size = buffer_limit;
   }
}

static inline void
vl_bitstream_encoder_free(struct vl_bitstream_encoder *enc)
{
   if (enc->internal_buffer)
      free(enc->bits);
}

static inline int
vl_bitstream_get_num_bits_for_byte_align(struct vl_bitstream_encoder *enc)
{
   return enc->bits_to_go & 7;
}

static inline int
vl_bitstream_get_byte_count(struct vl_bitstream_encoder *enc)
{
   return enc->offset + ((32 - enc->bits_to_go) >> 3);
}

static inline bool
vl_bitstream_is_byte_aligned(struct vl_bitstream_encoder *enc)
{
   if (enc->overflow)
      enc->bits_to_go = 32;
   return !(enc->bits_to_go & 7);
}

static inline void
vl_bitstream_write_byte_start_code(struct vl_bitstream_encoder *enc, uint8_t val)
{
   int offset = enc->offset;
   uint8_t *buffer = enc->bits + enc->offset;
   if (enc->prevent_start_code && enc->offset > 1) {
      if (((val & 0xfc) | buffer[-2] | buffer[-1]) == 0) {
         *buffer++ = 3;
         offset++;
      }
   }

   *buffer = val;
   offset++;
   enc->offset = offset;
}

static inline bool
vl_bitstream_verify_buffer(struct vl_bitstream_encoder *enc, uint32_t bytes_to_write)
{
   if (enc->overflow)
      return false;

   if (enc->offset + bytes_to_write > enc->bits_buffer_size) {
      enc->overflow = true;
      return false;
   }
   return true;
}

static inline void
vl_bitstream_flush(struct vl_bitstream_encoder *enc)
{
   ASSERTED bool is_aligned = vl_bitstream_is_byte_aligned(enc);
   assert (is_aligned);

   uint32_t temp = (uint32_t)(32 - enc->bits_to_go);

   if (!vl_bitstream_verify_buffer(enc, temp >> 3)) {
      return;
   }

   while (temp > 0) {
      vl_bitstream_write_byte_start_code(enc, (uint8_t)(enc->enc_buffer >> 24));
      enc->enc_buffer <<= 8;
      temp -= 8;
   }

   enc->bits_to_go = 32;
   enc->enc_buffer = 0;
}

static inline void
vl_bitstream_put_bits(struct vl_bitstream_encoder *enc, int bits_count, uint32_t bits_val)
{
   if (bits_count < enc->bits_to_go) {
      enc->enc_buffer |= (bits_val << (enc->bits_to_go - bits_count));
      enc->bits_to_go -= bits_count;
   } else if (vl_bitstream_verify_buffer(enc, 4)) {
      int left_over_bits = bits_count - enc->bits_to_go;
      enc->enc_buffer |= (bits_val >> left_over_bits);

      {
         uint8_t *temp = (uint8_t *)&enc->enc_buffer;
         vl_bitstream_write_byte_start_code(enc, *(temp + 3));
         vl_bitstream_write_byte_start_code(enc, *(temp + 2));
         vl_bitstream_write_byte_start_code(enc, *(temp + 1));
         vl_bitstream_write_byte_start_code(enc, *temp);
      }

      enc->enc_buffer = 0;
      enc->bits_to_go = 32 - left_over_bits;

      if (left_over_bits > 0)
         enc->enc_buffer = (bits_val << (32 - left_over_bits));
   }
}

static inline void
vl_bitstream_put_uvlc(struct vl_bitstream_encoder *enc, uint64_t val)
{
   uint32_t num_bits = 0;
   uint64_t value_plus1 = (uint64_t)val + 1;
   uint32_t num_leading_zeros = 0;

   while ((uint64_t)1 << num_bits <= value_plus1)
      num_bits++;

   num_leading_zeros = num_bits - 1;
   vl_bitstream_put_bits(enc, num_leading_zeros, 0);
   vl_bitstream_put_bits(enc, 1, 1);
   vl_bitstream_put_bits(enc, num_leading_zeros, (uint32_t)value_plus1);
}

static inline int
vl_bitstream_get_exp_golomb0_code_len(uint32_t val)
{
   int len = 0;
   val++;

   if (val >= 0x10000) {
      val >>= 16;
      len += 16;
   }
   if (val >= 0x100) {
      val >>= 8;
      len += 8;
   }
   assert(val < 256);

   return len + util_logbase2(val);
}

static inline void
vl_bitstream_exp_golomb_ue(struct vl_bitstream_encoder *enc, uint32_t val)
{
   if (val != UINT32_MAX) {
      int len = vl_bitstream_get_exp_golomb0_code_len(val);
      vl_bitstream_put_bits(enc, (len << 1) + 1, val + 1);
   } else {
      vl_bitstream_put_bits(enc, 32, 0);
      vl_bitstream_put_bits(enc, 1, 1);
      vl_bitstream_put_bits(enc, 32, 1);
   }
}

static inline void
vl_bitstream_exp_golomb_se(struct vl_bitstream_encoder *enc, int32_t val)
{
   if (val > 0)
      vl_bitstream_exp_golomb_ue(enc, (val << 1) - 1);
   else
      vl_bitstream_exp_golomb_ue(enc, ((-val) << 1) - (val == INT_MIN));
}

static inline void
vl_bitstream_rbsp_trailing(struct vl_bitstream_encoder *enc)
{
   vl_bitstream_put_bits(enc, 1, 1);
   int left = vl_bitstream_get_num_bits_for_byte_align(enc);

   if (left)
      vl_bitstream_put_bits(enc, left, 0);

   ASSERTED bool is_aligned = vl_bitstream_is_byte_aligned(enc);
   assert(is_aligned);
}

static inline uint8_t*
vl_bitstream_get_byte_offset(struct vl_bitstream_encoder *enc)
{
   ASSERTED bool is_aligned = vl_bitstream_is_byte_aligned(enc);
   assert(is_aligned);
   return enc->bits + (enc->offset + ((32 - enc->bits_to_go) >> 3));
}

#endif
