/* dynstr.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Port                                                                   */
/*                                                                           */
/* Handling of strings with dynamic allocation                               */
/*                                                                           */
/*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include "dynstr.h"

static void check_dynamic(const as_dynstr_t *p_str)
{
  if (!p_str->dynamic)
  {
    fprintf(stderr, "attemt to resize non-dynamic string\n");
    abort();
  }
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_ini(as_dynstr_t *p_str, size_t AllocLen)
 * \brief  initialize empty string with given capacity
 * \param  p_str string to initialize
 * \param  AllocLen initial alloc length
 * ------------------------------------------------------------------------ */

void as_dynstr_ini(as_dynstr_t *p_str, size_t ini_capacity)
{
  p_str->p_str = ini_capacity ? (char*)malloc(ini_capacity) : NULL;
  p_str->capacity = p_str->p_str ? ini_capacity : 0;
  p_str->dynamic = !!p_str->p_str;
  memset(p_str->p_str, 0, p_str->capacity);
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_ini_clone(as_dynstr_t *p_str, const as_dynstr_t *p_src)
 * \brief  initialize empty string from other string
 * \param  p_str string to initialize
 * \param  p_src string to clone
 * ------------------------------------------------------------------------ */

void as_dynstr_ini_clone(as_dynstr_t *p_str, const as_dynstr_t *p_src)
{
  p_str->p_str = (char*)malloc(p_src->capacity);
  if (p_str->p_str)
  {
    strcpy(p_str->p_str, p_src->p_str);
    p_str->capacity = p_src->capacity;
    p_str->dynamic = 1;
  }
  else
  {
    p_str->capacity = 0;
    p_str->dynamic = 0;
  }
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_ini_c_str(as_dynstr_t *p_str, const char *p_src)
 * \brief  initialize string from C string
 * \param  p_str string to initialize
 * \param  p_src init source
 * ------------------------------------------------------------------------ */

void as_dynstr_ini_c_str(as_dynstr_t *p_str, const char *p_src)
{
  size_t capacity = as_dynstr_roundup_len(strlen(p_src));

  p_str->p_str = (char*)malloc(capacity);
  if (p_str->p_str)
  {
    strcpy(p_str->p_str, p_src);
    p_str->capacity = capacity;
    p_str->dynamic = 1;
  }
  else
  {
    p_str->capacity = 0;
    p_str->dynamic = 0;
  }
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_realloc(as_dynstr_t *p_str, size_t new_capacity)
 * \brief  shrink/grow string's capacity
 * \param  p_str string to adapt
 * \param  new_capacity new capacity
 * \return 0 if success or error code
 * ------------------------------------------------------------------------ */

int as_dynstr_realloc(as_dynstr_t *p_str, size_t new_capacity)
{
  char *p_new;
  size_t old_capacity;

  check_dynamic(p_str);
  p_new = (char*)realloc(p_str->p_str, new_capacity);
  old_capacity = p_str->capacity;
  if (p_new)
  {
    p_str->p_str = p_new;
    p_str->capacity = new_capacity;
    if (new_capacity > old_capacity)
      memset(p_str->p_str + old_capacity, 0, new_capacity - old_capacity);
    else if (new_capacity > 0)
      p_str->p_str[new_capacity - 1] = '\0';
    return 0;
  }
  else
    return ENOMEM;
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_free(as_dynstr_t *p_str)
 * \brief  free/destroy string
 * \param  p_str string to handle
 * ------------------------------------------------------------------------ */

void as_dynstr_free(as_dynstr_t *p_str)
{
  if (p_str->dynamic && p_str->p_str)
    free(p_str->p_str);
  p_str->p_str = NULL;
  p_str->capacity = 0;
  p_str->dynamic = 0;
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_copy(as_dynstr_t *p_dest, const as_dynstr_t *p_src)
 * \brief  set string from other string
 * \param  p_str string to set
 * \param  p_src init source
 * \return actual # of characters copied
 * ------------------------------------------------------------------------ */

size_t as_dynstr_copy(as_dynstr_t *p_dest, const as_dynstr_t *p_src)
{
  return as_dynstr_copy_c_str(p_dest, p_src->p_str);
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_copy_c_str(as_dynstr_t *p_dest, const char *p_src)
 * \brief  set string from C string
 * \param  p_str string to set
 * \param  p_src init source
 * \return actual # of characters copied
 * ------------------------------------------------------------------------ */

size_t as_dynstr_copy_c_str(as_dynstr_t *p_dest, const char *p_src)
{
  return as_dynstr_copy_raw_str(p_dest, p_src, strlen(p_src));
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_copy_raw_str(as_dynstr_t *p_dest, const char *p_src, size_t len)
 * \brief  set string from raw string
 * \param  p_str string to set
 * \param  p_src init source
 * \param  len source length
 * \return actual # of characters copied
 * ------------------------------------------------------------------------ */

size_t as_dynstr_copy_raw_str(as_dynstr_t *p_dest, const char *p_src, size_t len)
{
  if ((len >= p_dest->capacity) && p_dest->dynamic)
    as_dynstr_realloc(p_dest, as_dynstr_roundup_len(len));

  if (len >= p_dest->capacity)
    len = p_dest->capacity - 1;
  memcpy(p_dest->p_str, p_src, len);
  p_dest->p_str[len] = '\0';
  return len;
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_append(as_dynstr_t *p_dest, const char *p_src, size_t src_len)
 * \brief  extend string
 * \param  p_dest string to extend
 * \param  p_src what to append
 * \param  src_len how much to append
 * \return actual # of bytes transferred
 * ------------------------------------------------------------------------ */

size_t as_dynstr_append(as_dynstr_t *p_dest, const char *p_src, size_t src_len)
{
  size_t dest_len = strlen(p_dest->p_str);

  if ((dest_len + src_len + 1 > p_dest->capacity) && p_dest->dynamic)
    as_dynstr_realloc(p_dest, as_dynstr_roundup_len(dest_len + src_len));
  if (src_len >= p_dest->capacity - dest_len)
    src_len = p_dest->capacity - dest_len - 1;
  memcpy(p_dest->p_str + dest_len, p_src, src_len);
  p_dest->p_str[dest_len + src_len] = '\0';
  return src_len;
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_append_c_str(as_dynstr_t *p_dest, const char *p_src)
 * \brief  extend string
 * \param  p_dest string to extend
 * \param  p_src what to append
 * \return actual # of bytes transferred
 * ------------------------------------------------------------------------ */

size_t as_dynstr_append_c_str(as_dynstr_t *p_dest, const char *p_src)
{
  return as_dynstr_append(p_dest, p_src, strlen(p_src));
}

/*!------------------------------------------------------------------------
 * \fn     as_dynstr_dump_hex(FILE *p_file, const as_dynstr_t *p_str)
 * \brief  debug helper
 * \param  p_file where to dump
 * \param  p_str string to dump
 * ------------------------------------------------------------------------ */

void as_dynstr_dump_hex(FILE *p_file, const as_dynstr_t *p_str)
{
  const char *p_run;

  fprintf(p_file, "[%u]", (unsigned)p_str->capacity);
  for (p_run = p_str->p_str; *p_run; p_run++) fprintf(p_file, " %02x", *p_run & 0xff);
  fprintf(p_file, "\n");
}
