/*
 * Copyright © 2009  Red Hat, Inc.
 * Copyright © 2018  Google, Inc.
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * Red Hat Author(s): Behdad Esfahbod
 * Google Author(s): Behdad Esfahbod
 */

#ifndef HB_BLOB_HH
#define HB_BLOB_HH

#include "hb.hh"


/*
 * hb_blob_t
 */

struct hb_blob_t
{
  ~hb_blob_t () { destroy_user_data (); }

  void destroy_user_data ()
  {
    if (destroy)
    {
      destroy (user_data);
      user_data = nullptr;
      destroy = nullptr;
    }
  }

  void replace_buffer (const char       *new_data,
                       unsigned          new_length,
                       hb_memory_mode_t  new_mode,
                       void             *new_user_data,
                       hb_destroy_func_t new_destroy)
  {
    if (new_data != data)
      destroy_user_data ();
    data = new_data;
    length = new_length;
    mode = new_mode;
    user_data = new_user_data;
    destroy = new_destroy;
  }

  HB_INTERNAL bool try_make_writable ();
  HB_INTERNAL bool try_make_writable_inplace ();
  HB_INTERNAL bool try_make_writable_inplace_unix ();

  hb_bytes_t as_bytes () const { return hb_bytes_t (data, length); }
  template <typename Type>
  const Type* as () const { return as_bytes ().as<Type> (); }

  public:
  hb_object_header_t header;

  const char *data = nullptr;
  unsigned int length = 0;
  hb_memory_mode_t mode = (hb_memory_mode_t) 0;

  void *user_data = nullptr;
  hb_destroy_func_t destroy = nullptr;

  /*
   * Blob-recycling helpers.
   *
   * Encoders that produce a sized byte buffer and wrap it as an
   * hb_blob_t can amortize malloc/blob-allocation across repeated
   * renders by handing the output blob back via a recycle slot.  On
   * the next encode these helpers reuse (or realloc) the buffer and
   * the same hb_blob_t handle is returned, skipping malloc/free
   * and blob-handle churn across glyph-by-glyph encoding loops.
   *
   * Blobs managed by this machinery are identified by the address
   * of recycle_data_destroy.
   */

  struct recycle_data_t
  {
    char *buf;
    unsigned capacity;
  };

  static inline void recycle_data_destroy (void *user_data)
  {
    auto *bd = (recycle_data_t *) user_data;
    hb_free (bd->buf);
    hb_free (bd);
  }

  /* Acquire a buffer of at least @needed bytes.  If @recycled is
   * one of our blobs, reuse its buffer (or realloc it).
   * *@out_capacity receives the actual capacity (>= @needed).
   * *@out_replaced_buf is set to the recycled buf when realloc
   * fails and a fresh buffer was allocated instead -- the caller
   * must hb_free() that buf after recycle_finalize() runs.  Returns
   * nullptr on allocation failure. */
  static inline char *
  recycle_acquire (hb_blob_t *recycled,
                   unsigned   needed,
                   unsigned  *out_capacity,
                   char     **out_replaced_buf)
  {
    *out_replaced_buf = nullptr;

    if (recycled && recycled->destroy == recycle_data_destroy)
    {
      auto *bd = (recycle_data_t *) recycled->user_data;
      if (bd->capacity >= needed)
      {
        *out_capacity = bd->capacity;
        return bd->buf;
      }
      /* Grow with a 1.5x ramp to amortize repeated growth. */
      unsigned alloc_bytes = needed;
      if (unlikely (hb_unsigned_add_overflows (needed, needed / 2,
                                               &alloc_bytes)))
        alloc_bytes = needed;
      char *new_buf = (char *) hb_realloc (bd->buf, alloc_bytes);
      if (new_buf)
      {
        bd->buf = new_buf;
        bd->capacity = alloc_bytes;
        *out_capacity = alloc_bytes;
        return new_buf;
      }
      /* Realloc failed.  Fall through to a fresh hb_malloc and stash
       * the old buf for the caller to free after recycle_finalize. */
      *out_replaced_buf = bd->buf;
    }

    char *buf = (char *) hb_malloc (needed);
    if (unlikely (!buf))
      return nullptr;
    *out_capacity = needed;
    return buf;
  }

  /* Wrap @buf (of @capacity, with @length used) into an hb_blob_t.
   * If @recycled is one of our blobs, update and return it (cheap);
   * otherwise create a new blob.  Pass @replaced_recycled_buf from
   * recycle_acquire(). */
  static inline hb_blob_t *
  recycle_finalize (char      *buf,
                    unsigned   capacity,
                    unsigned   length,
                    hb_blob_t *recycled,
                    char      *replaced_recycled_buf)
  {
    if (recycled && recycled->destroy == recycle_data_destroy)
    {
      auto *bd = (recycle_data_t *) recycled->user_data;
      if (replaced_recycled_buf && replaced_recycled_buf != buf)
        hb_free (replaced_recycled_buf);
      bd->buf = buf;
      bd->capacity = capacity;
      recycled->data = (const char *) buf;
      recycled->length = length;
      return recycled;
    }

    /* No recycled blob to update -- create a fresh one with our
     * destroy closure so the next recycle round can reuse it. */
    recycle_data_t *bd = (recycle_data_t *) hb_malloc (sizeof (*bd));
    if (unlikely (!bd))
    {
      hb_free (buf);
      return nullptr;
    }
    bd->buf = buf;
    bd->capacity = capacity;

    return hb_blob_create ((const char *) buf, length,
                           HB_MEMORY_MODE_WRITABLE,
                           bd, recycle_data_destroy);
  }

  /* Discard @buf returned by recycle_acquire without committing to
   * a blob.  Frees @buf if it was a fresh allocation; leaves any
   * recycled buffer untouched. */
  static inline void
  recycle_abort (char *buf, hb_blob_t *recycled)
  {
    if (!buf) return;
    if (recycled && recycled->destroy == recycle_data_destroy)
    {
      auto *bd = (recycle_data_t *) recycled->user_data;
      if (buf == bd->buf) return;  /* owned by the recycled blob */
    }
    hb_free (buf);
  }

  /* Stash @blob in @slot as the recycled output for the next
   * encode.  Destroys any previously stashed blob.  Safe to call
   * with @blob = nullptr or the empty-singleton blob (treated as
   * "drop"). */
  static inline void
  recycle_stash (hb_blob_t **slot, hb_blob_t *blob)
  {
    hb_blob_destroy (*slot);
    *slot = nullptr;
    if (!blob || blob == hb_blob_get_empty ())
      return;
    *slot = blob;
  }
};


/*
 * hb_blob_ptr_t
 */

template <typename P>
struct hb_blob_ptr_t
{
  typedef hb_remove_pointer<P> T;

  hb_blob_ptr_t (hb_blob_t *b_ = nullptr) : b (b_) {}
  hb_blob_t * operator = (hb_blob_t *b_) { return b = b_; }
  const T * operator -> () const { return get (); }
  const T & operator * () const  { return *get (); }
  template <typename C> operator const C * () const { return get (); }
  operator const char * () const { return (const char *) get (); }
  const T * get () const { return b->as<T> (); }
  hb_blob_t * get_blob () const { return b.get_raw (); }
  unsigned int get_length () const { return b.get ()->length; }
  void destroy () { hb_blob_destroy (b.get_raw ()); b = nullptr; }

  private:
  hb_nonnull_ptr_t<hb_blob_t> b;
};


#endif /* HB_BLOB_HH */
