/* nonzstring.h */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Port                                                                   */
/*                                                                           */
/* Handling of non-NUL terminated stringd                                    */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"
#include <string.h>
#include "strutil.h"

#include "nonzstring.h"

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_ini(as_nonz_dynstr_t *p_str, size_t ini_capacity)
 * \brief  create/initialize string
 * \param  p_str string to set up
 * \param  ini_capacity initial capacity
 * ------------------------------------------------------------------------ */

void as_nonz_dynstr_ini(as_nonz_dynstr_t *p_str, size_t ini_capacity)
{
  if (ini_capacity > 0)
  {
    p_str->p_str = (char*)calloc(1, ini_capacity);
    p_str->capacity = p_str->p_str ? ini_capacity : 0;
    p_str->len = 0;
  }
  else
  {
    p_str->p_str = NULL;
    p_str->capacity = p_str->len = 0;
  }
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_ini_c_str(as_nonz_dynstr_t *p_str, const char *p_src)
 * \brief  create/initialize string from C string
 * \param  p_str string to set up
 * \param  p_src data source
 * ------------------------------------------------------------------------ */

void as_nonz_dynstr_ini_c_str(as_nonz_dynstr_t *p_str, const char *p_src)
{
  size_t len = strlen(p_src);

  as_nonz_dynstr_ini(p_str, as_nonz_dynstr_roundup_len(len));
  p_str->len = (p_str->capacity < len) ? p_str->capacity : len;
  memcpy(p_str->p_str, p_src, p_str->len);
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_realloc(as_nonz_dynstr_t *p_str, size_t new_capacity)
 * \brief  resize buffer of string
 * \param  p_str string to resize
 * \param  new_capacity new capacity
 * ------------------------------------------------------------------------ */

void as_nonz_dynstr_realloc(as_nonz_dynstr_t *p_str, size_t new_capacity)
{
  size_t old_capacity = p_str->capacity;
  char *p_new_str = p_str->p_str
                  ? (char*)realloc(p_str->p_str, new_capacity)
                  : (char*)malloc(new_capacity);

  if (p_new_str)
  {
    p_str->p_str = p_new_str;
    p_str->capacity = new_capacity;
    if (new_capacity > old_capacity)
      memset(p_str->p_str + old_capacity, 0, new_capacity - old_capacity);
    else if (p_str->len > new_capacity)
      p_str->len = new_capacity;
  }
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_free(as_nonz_dynstr_t *p_str)
 * \brief  free_destroy string
 * \param  p_str string to free
 * ------------------------------------------------------------------------ */

void as_nonz_dynstr_free(as_nonz_dynstr_t *p_str)
{
  if (p_str->p_str)
    free(p_str->p_str);
  p_str->p_str = NULL;
  p_str->len = p_str->capacity = 0;
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_to_c_str(char *p_dest, const as_nonz_dynstr_t *p_src, size_t dest_len)
 * \brief  convert string to C-style string
 * \param  p_dest destination
 * \param  p_src source
 * \param  dest_len dest capacity
 * \return result length
 * ------------------------------------------------------------------------ */

size_t as_nonz_dynstr_to_c_str(char *p_dest, const as_nonz_dynstr_t *p_src, size_t dest_len)
{
  if (dest_len > 0)
  {
    unsigned TransLen = dest_len - 1;

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

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_copy(as_nonz_dynstr_t *p_dest, const as_nonz_dynstr_t *p_src)
 * \brief  copy string
 * \param  p_dest destination
 * \param  p_src source
 * \return actual dest length after copy
 * ------------------------------------------------------------------------ */

size_t as_nonz_dynstr_copy(as_nonz_dynstr_t *p_dest, const as_nonz_dynstr_t *p_src)
{
  if (p_dest != p_src)
  {
    size_t trans_len;

    if (p_dest->capacity < p_src->capacity)
      as_nonz_dynstr_realloc(p_dest, p_src->capacity);
    trans_len = p_src->len;
    if (trans_len > p_dest->capacity)
      trans_len = p_dest->capacity;
    memcpy(p_dest->p_str, p_src->p_str, p_dest->len = trans_len);
  }
  return p_dest->len;
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_swap(as_nonz_dynstr_t *p_str1, as_nonz_dynstr_t *p_str2)
 * \brief  exchange contents of two strings
 * \param  p_str1, p_str2 strings to swap contents
 * ------------------------------------------------------------------------ */

void as_nonz_dynstr_swap(as_nonz_dynstr_t *p_str1, as_nonz_dynstr_t *p_str2)
{
  if (p_str1 != p_str2)
  {
    size_t len, capacity;
    char *p_str;

    len = p_str1->len;
    capacity = p_str1->capacity;
    p_str = p_str1->p_str;

    p_str1->len = p_str2->len;
    p_str1->capacity = p_str2->capacity;
    p_str1->p_str = p_str2->p_str;

    p_str2->len = len;
    p_str2->capacity = capacity;
    p_str2->p_str = p_str;
  }
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_append_raw(as_nonz_dynstr_t *p_dest, const char *p_src, int src_len)
 * \brief  extend string
 * \param  p_dest string to extend
 * \param  p_src what to append
 * \param  src_len source length (-1 for NUL-terminated source)
 * \return actual # of bytes transferred
 * ------------------------------------------------------------------------ */

size_t as_nonz_dynstr_append_raw(as_nonz_dynstr_t *p_dest, const char *p_src, int src_len)
{
  size_t trans_len;

  if (src_len < 0)
    src_len = strlen(p_src);

  if (p_dest->len + src_len > p_dest->capacity)
    as_nonz_dynstr_realloc(p_dest, as_nonz_dynstr_roundup_len(p_dest->len + src_len));
  trans_len = p_dest->capacity - p_dest->len;
  if ((size_t)src_len < trans_len)
    trans_len = src_len;
  memcpy(p_dest->p_str + p_dest->len, p_src, trans_len);
  p_dest->len += trans_len;
  return trans_len;
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_append_char(as_nonz_dynstr_t *p_dest, char src)
 * \brief  extend string
 * \param  p_dest string to extend
 * \param  src what to append
 * \return actual # of bytes transferred
 * ------------------------------------------------------------------------ */

size_t as_nonz_dynstr_append_char(as_nonz_dynstr_t *p_dest, char src)
{
  return as_nonz_dynstr_append_raw(p_dest, &src, 1);
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_copy_c_str(as_nonz_dynstr_t *p_dest, const char *p_src)
 * \brief  convert C-style string to non-NUL terminated string
 * \param  p_dest string to fill
 * \param  p_src string to fill from
 * \return actual # of characters appended
 * ------------------------------------------------------------------------ */

size_t as_nonz_dynstr_copy_c_str(as_nonz_dynstr_t *p_dest, const char *p_src)
{
  p_dest->len = 0;
  return as_nonz_dynstr_append_raw(p_dest, p_src, -1);
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_append(as_nonz_dynstr_t *p_dest, const as_nonz_dynstr_t *p_src)
 * \brief  extend string by non-NUL terminated string
 * \param  p_dest string to extend
 * \param  p_src string to append
 * \return actual # of characters appended
 * ------------------------------------------------------------------------ */

size_t as_nonz_dynstr_append(as_nonz_dynstr_t *p_dest, const as_nonz_dynstr_t *p_src)
{
  return as_nonz_dynstr_append_raw(p_dest, p_src->p_str, p_src->len);
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_cmp(const as_nonz_dynstr_t *p_str1, const as_nonz_dynstr_t *p_str2)
 * \brief  compare two strings
 * \param  p_str1, p_str2 strings to compare
 * \return -1/0/+1 if Str1 </==/> Str2
 * ------------------------------------------------------------------------ */

int as_nonz_dynstr_cmp(const as_nonz_dynstr_t *p_str1, const as_nonz_dynstr_t *p_str2)
{
  return strlencmp(p_str1->p_str, p_str1->len, p_str2->p_str, p_str2->len);
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_dynstr_find(const as_nonz_dynstr_t *p_haystack, const as_nonz_dynstr_t *p_needle)
 * \brief  find first occurence of substring
 * \param  p_haystack where to search
 * \param  p_needle what to search
 * \return first position or -1 if not found
 * ------------------------------------------------------------------------ */

int as_nonz_dynstr_find(const as_nonz_dynstr_t *p_haystack, const as_nonz_dynstr_t *p_needle)
{
  int pos, maxpos = ((int)p_haystack->len) - ((int)p_needle->len);

  for (pos = 0; pos <= maxpos; pos++)
    if (!memcmp(p_haystack->p_str + pos, p_needle->p_str, p_needle->len))
      return pos;

  return -1;
}

/*!------------------------------------------------------------------------
 * \fn     as_nonz_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_nonz_dynstr_dump_hex(FILE *p_file, const as_nonz_dynstr_t *p_str)
{
  const char *p_run;

  fprintf(p_file, "[%u/%u]", (unsigned)p_str->len, (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");
}
