/* sml3_gummi.c: Gummistrings */

/* Copyright 2012-2017 Kurt Nienhaus
 *
 * This file is part of libsammel3.
 * libsammel3 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * libsammel3 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with libsammel3.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include "config.h"
#include "sml3_fehler.h"
#include "sml3_util.h"
#include "sml3_gummi.h"

void SML3_guminit(struct SML3_gummi *, size_t);
void SML3_gumdest(struct SML3_gummi *);
char * SML3_gumgetval(struct SML3_gummi *);
char * SML3_gumalloc(struct SML3_gummi *, size_t);
size_t SML3_gumsize(struct SML3_gummi *);
void SML3_gumswapgum(struct SML3_gummi *, struct SML3_gummi *);
char * SML3_gumswapstr(struct SML3_gummi *, char *, size_t);
size_t SML3_gumcpy(struct SML3_gummi *, size_t, const char *);
char * SML3_gumncpy(struct SML3_gummi *, size_t, const char *, size_t);
char * SML3_gumset(struct SML3_gummi *, size_t, int, size_t);
char * SML3_gummove(struct SML3_gummi *, size_t, const char *, size_t);
int SML3_gumprintf(struct SML3_gummi *, size_t, const char *, ...);
int SML3_gumvprintf(struct SML3_gummi *, size_t, const char *, va_list);
ssize_t SML3_gumfget(struct SML3_gummi *, size_t, FILE *);
ssize_t SML3_gumsget(struct SML3_gummi *, size_t, char **);

static void gum_concat(struct SML3_gummi *, size_t, const char *, size_t, int);


/* gum_concat [thread-sicher]:
 * kopiert an Gummistring 1.Arg ab Position 2.Arg
 * den String 3.Arg mit Laenge 4.Arg
 * 1.Arg: Gummistring
 * 2.Arg: Startposition im 1.Arg
 * 3.Arg: zu kopierender String (oder NULL)
 * 4.Arg: Stringlaenge vom 3.Arg
 * 5.Arg: 1=strncpy oder 2=memmove verwenden
 */
static void
gum_concat(struct SML3_gummi *gm, size_t pos, const char *string, size_t slen, int str0mem)
{
  size_t innen, nsize;

  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }

  if (gm->allo > 0 && gm->val != NULL
      && string != NULL && string >= gm->val && string <= gm->val + gm->allo) {
    innen = string - gm->val;
  } else {
    innen = (size_t)-1;
  }

  if (pos + slen >= gm->allo) {  /* Gummistring vergroessern */
    nsize = pos + slen + 1;
    if (gm->allo == 0 || gm->val == NULL) {
      gm->val = SML3_malloc(sizeof(char) * nsize);
      *gm->val = '\0';
    } else {
      gm->val = SML3_realloc(gm->val, sizeof(char) * nsize);
      if (innen != (size_t)-1) { string = gm->val + innen;}  /* string war innerhalb vom Gummistring-Wert */
    }
    gm->allo = nsize;
    gm->val[gm->allo - 1] = '\0';
  }

  if (string != NULL && slen > 0) {
    if (str0mem == 1) {  /* strncpy statt memmove */
      if (innen != (size_t)-1) {  /* strncpy ist nicht ueberlapp-sicher */
        char * ptr;
        if ((ptr = memchr(string, 0, slen)) != NULL) {
          nsize = ptr - string;
        } else {
          nsize = slen;
        }
        if (nsize > 0) { memmove(gm->val + pos, string, nsize); }
        if (nsize < slen) { memset(gm->val + pos + nsize, 0, slen - nsize); }
      } else {
        strncpy(gm->val + pos, string, slen);
      }
    } else {  /* memmove statt strncpy */
      memmove(gm->val + pos, string, slen);
    }
  }
} /* Ende gum_concat */


/* SML3_guminit [thread-sicher]:
 * initialisiert Gummistring;
 * muss einmalig vor Verwendung des Gummistrings aufgerufen werden,
 * alternativ kann SML3_GUM_INITIALIZER verwendet werden
 * 1.Arg: Adresse eines Gummistrings
 * 2.Arg: Initial-Allokationsgroesse
 */
void
SML3_guminit(struct SML3_gummi *gm, size_t allo)
{
  if (gm == NULL) { return; }
  if (allo < 1) { allo = 1; }
  if ((gm->val = malloc(sizeof(char) * allo)) == NULL) {
    allo = 1;
    if ((gm->val = malloc(sizeof(char) * allo)) == NULL) { gm->allo = 0; return; }
  }
  *gm->val = '\0';
  gm->allo = allo;
} /* Ende SML3_guminit */


/* SML3_gumdest [thread-sicher]:
 * gibt Gummistring-Speicher frei;
 * dennoch ist SML3_guminit() fuer eine weitere Verwendung nicht noetig
 * 1.Arg: Adresse des Gummistrings
 */
void
SML3_gumdest(struct SML3_gummi *gm)
{
  if (gm == NULL || gm->allo == 0) { return; }
  if (gm->val != NULL) { free(gm->val); }
  gm->val = NULL;
  gm->allo = 0;
} /* Ende SML3_gumdest */


/* SML3_gumgetval [thread-sicher]:
 * gibt den Pointer auf den Gummistring-Wert Inhalt zurueck
 * (er kann ungueltig werden, wenn der Gummistring veraendert wird)
 * 1.Arg: Adresse des Gummistrings
 * Rueckgabe: Pointer auf Gummistring-Wert
 */
char *
SML3_gumgetval(struct SML3_gummi *gm)
{
  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (gm->allo == 0 || gm->val == NULL) { gum_concat(gm, 0, NULL, 1, 2); }
  return gm->val;
} /* Ende SML3_gumgetval */


/* SML3_gumalloc [thread-sicher]:
 * alloziert Gummistring-Wert auf Laenge 2.Arg
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval())
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: zu allozierende Groesse
 * Rueckgabe: Pointer auf Gummistring-Wert
 */
char *
SML3_gumalloc(struct SML3_gummi *gm, size_t len)
{
  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  gum_concat(gm, 0, NULL, len, 2);
  return gm->val;
} /* Ende SML3_gumalloc */


/* SML3_gumsize [reentrant]:
 * gibt Allokationsgroesse des Gummistring-Wertes zurueck
 * 1.Arg: Adresse des Gummistrings
 * Rueckgabe: Allokationsgroesse des Gummistring-Wertes
 */
size_t
SML3_gumsize(struct SML3_gummi *gm)
{
  if (gm == NULL || gm->val == NULL) { return 0; }
  return gm->allo;
} /* Ende SML3_gumsize */


/* SML3_gumswapgum [thread-sicher]:
 * vertauscht die Werte zweier Gummistrings
 * 1.+2.Arg: zu tauschende Gummistrings
 */
void
SML3_gumswapgum(struct SML3_gummi *g1, struct SML3_gummi *g2)
{
  struct SML3_gummi gtmp;
  if (g1 == NULL || g2 == NULL) { return; }
  memmove(&gtmp, g1, sizeof(struct SML3_gummi));
  memmove(g1, g2, sizeof(struct SML3_gummi));
  memmove(g2, &gtmp, sizeof(struct SML3_gummi));
} /* Ende SML3_gumswapgum */


/* SML3_gumswapstr [thread-sicher]:
 * vertauscht den Gummistring-Wert 1.Arg mit dem uebergebenen String 2.Arg
 * und gibt Pointer auf alten Gummistring-Wert zurueck;
 * veraendert Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * Der neue Gummistring-Wert ist das 2.Arg, keine Kopie,
 * daher muss das 2.Arg dynamisch alloziert sein.
 * Der Rueckgabewert muss freigegeben werden.
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: neuer zu setzender Wert oder NULL
 * 3.Arg: Allokationsgroesse 2.Arg oder 0 = Stringlaenge + 1
 * Rueckgabe: (freizugebenden) Pointer auf alten Gummistring-Wert
 *
 * Falls der neue Wert (2.Arg) NULL ist, kann danach SML3_gumdest() und/oder
 * SML3_guminit() aufgerufen werden, muss aber nicht
 */
char *
SML3_gumswapstr(struct SML3_gummi *gm, char *vneu, size_t alloneu)
{
  char *valt;
  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (gm->allo == 0 || gm->val == NULL) {
    valt = SML3_calloc(1, sizeof(char));
  } else {
    valt = gm->val;
  }
  if (vneu != NULL) {
    gm->val = vneu;
    gm->allo = (alloneu > 0 ? alloneu : strlen(gm->val) + 1);
  } else {
    gm->val = NULL;
    gm->allo = 0;
  }
  return valt;
} /* Ende SML3_gumswapstr */


/* SML3_gumcpy [thread-sicher]:
 * aehnlich str(l)cpy fuer Gummistrings
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der kopiert werden soll
 * 3.Arg: zu kopierender String
 * Rueckgabe: Anzahl kopierter Zeichen
 */
size_t
SML3_gumcpy(struct SML3_gummi *gm, size_t pos, const char *string)
{
  size_t len;
  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (string == NULL) { return 0; }
  len = strlen(string);
  gum_concat(gm, pos, string, len + 1, 2);
  return len;
} /* Ende SML3_gumcpy */


/* SML3_gumncpy [thread-sicher]:
 * wie strncpy fuer Gummistrings
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der kopiert werden soll
 * 3.Arg: zu kopierender String
 * 4.Arg: Anzahl zu kopierender Zeichen
 * Rueckgabe: Pointer auf Gummistring-Wert
 */
char *
SML3_gumncpy(struct SML3_gummi *gm, size_t pos, const char *string, size_t len)
{
  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (string == NULL) { string = ""; len = 1; }
  gum_concat(gm, pos, string, len, 1);
  return gm->val;
} /* Ende SML3_gumncpy */


/* SML3_gumset [thread-sicher]:
 * wie memset fuer Gummistrings
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der Zeichen gesetzt werden sollen
 * 3.Arg: zu setzendes Zeichen
 * 4.Arg: Anzahl zu setzender Zeichen
 * Rueckgabe: Pointer auf Gummistring-Wert
 */
char *
SML3_gumset(struct SML3_gummi *gm, size_t pos, int zch, size_t len)
{
  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  gum_concat(gm, pos, NULL, len, 2);
  if (len > 0) { memset(gm->val + pos, zch, len); }
  return gm->val;
} /* Ende SML3_gumset */


/* SML3_gummove [thread-sicher]:
 * wie memmove fuer Gummistrings
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der kopiert werden soll
 * 3.Arg: zu kopierender Bereich
 * 4.Arg: Anzahl zu kopierender Zeichen
 * Rueckgabe: Pointer auf Gummistring-Wert
 */
char *
SML3_gummove(struct SML3_gummi *gm, size_t pos, const char *string, size_t len)
{
  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (string == NULL) { string = ""; len = 1; }
  gum_concat(gm, pos, string, len, 2);
  return gm->val;
} /* Ende SML3_gummove */


/* SML3_gumprintf [thread-sicher]:
 * wie sprintf fuer Gummistrings
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der kopiert werden soll
 * 3.Arg: Formatstring
 * weitere Arg: Variablen fuer Formatstring
 * Rueckgabe: Anzahl kopierter Zeichen
 */
int
SML3_gumprintf(struct SML3_gummi *gm, size_t pos, const char *fmt, ...)
{
  int len;
  char *bufp;
  size_t bufsize;

  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (fmt == NULL) {
    gum_concat(gm, pos, "", 1, 2);
    return 0;
  }

  bufsize = 512;
  bufp = SML3_malloc(sizeof(char) * bufsize);

  *bufp = '\0'; len = 0;
  for (;;) {
    va_list ap;
    va_start(ap, fmt);
    len = vsnprintf(bufp, bufsize, fmt, ap);
    va_end(ap);
    if (len < 0) {  /* zu klein: alter Stil */
      bufsize += 512;
    } else if (len >= (int)bufsize) {  /* zu klein: neuer Stil */
      bufsize = len + 1;
    } else {  /* ok */
      break;
    }
    bufp = SML3_realloc(bufp, sizeof(char) * bufsize);
  }
  gum_concat(gm, pos, bufp, len + 1, 2);
  free(bufp);
  return len;
} /* Ende SML3_gumprintf */


/* SML3_gumvprintf [thread-sicher]:
 * wie vsprintf fuer Gummistrings
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der kopiert werden soll
 * 3.Arg: Formatstring
 * 4.Arg: mit va_start() ermittelte Argumentliste
 *        (es wird kein va_end() aufgerufen)
 * Rueckgabe: Anzahl kopierter Zeichen
 */
int
SML3_gumvprintf(struct SML3_gummi *gm, size_t pos, const char *fmt, va_list ap)
{
  int len;
  char *bufp;
  size_t bufsize;

  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (fmt == NULL) {
    gum_concat(gm, pos, "", 1, 2);
    return 0;
  }

  bufsize = 512;
  bufp = SML3_malloc(sizeof(char) * bufsize);

  *bufp = '\0'; len = 0;
  for (;;) {
    va_list aq;
    va_copy(aq, ap);
    len = vsnprintf(bufp, bufsize, fmt, aq);
    va_end(aq);
    if (len < 0) {  /* zu klein: alter Stil */
      bufsize += 512;
    } else if (len >= (int)bufsize) {  /* zu klein: neuer Stil */
      bufsize = len + 1;
    } else {  /* ok */
      break;
    }
    bufp = SML3_realloc(bufp, sizeof(char) * bufsize);
  }
  gum_concat(gm, pos, bufp, len + 1, 2);
  free(bufp);
  return len;
} /* Ende SML3_gumvprintf */


/* SML3_gumfget [thread-sicher]:
 * aehnlich fgets, aber Rueckgabe in Gummistring
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der kopiert werden soll
 * 3.Arg: Filepointer, aus dem gelesen wird
 * Rueckgabe: >0 = Anzahl gelesener Zeichen
 *             0 = Dateiende
 *            -1 = Fehler
 * SML3-errno-Wert: EIO = Fehler Lesen aus Filepointer
 */
ssize_t
SML3_gumfget(struct SML3_gummi *gm, size_t pos, FILE *ffp)
{
  char buf[1024];
  ssize_t anz;
  size_t s0;

  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  anz = 0;
  do {
    clearerr(ffp);
    if (fgets(buf, sizeof(buf), ffp) == NULL) {
      if (ferror(ffp)) {
        SML3_fehlernew(EIO, "fgets: %s", SML3_strerror(EIO));
        return (ssize_t)-1;
      }
      break;
    }
    s0 = SML3_gumcpy(gm, pos + anz, buf);
    anz += s0;
  } while (buf[s0 - 1] != '\n');
  return anz;
} /* Ende SML3_gumfget */


/* SML3_gumsget [thread-sicher]:
 * wie SML3_gumfget, liest aber aus einem String statt einer Datei
 * veraendert evtl. Gummistring-Wert-Adresse (siehe SML3_gumgetval()).
 * 1.Arg: Adresse des Gummistrings
 * 2.Arg: Position, ab der kopiert werden soll
 * 3.Arg: Adresse auf Stringpointer, ab wo gelesen werden soll
 *        Achtung: Aendert sich mit jedem Aufruf.
 * Rueckgabe: >0 = Anzahl gelesener Zeichen
 *             0 = Stringende
 */
ssize_t
SML3_gumsget(struct SML3_gummi *gm, size_t pos, char **pstr)
{
  char *ptr;
  ssize_t anz;

  if (gm == NULL) { SML3_fehlerexit("%s", SML3_strerror(EINVAL)); }
  if (pstr == NULL || *pstr == NULL || **pstr == '\0') { SML3_gumcpy(gm, pos, ""); return 0; }
  if ((ptr = strchr(*pstr, '\n')) == NULL) { ptr = *pstr + strlen(*pstr); } else { ptr++; }
  anz = (ptr - *pstr);
  SML3_gumncpy(gm, pos, *pstr, anz);
  SML3_gumcpy(gm, pos + anz, "");
  *pstr = ptr;
  return anz;
} /* Ende SML3_gumsget */
