/***************************************************************************
 begin       : Thu Apr 03 2003
 copyright   : (C) 2021 by Martin Preuss
 email       : martin@libchipcard.de

 ***************************************************************************
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Lesser General Public            *
 *   License as published by the Free Software Foundation; either          *
 *   version 2.1 of the License, or (at your option) any later version.    *
 *                                                                         *
 *   This library 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     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
 *   MA  02111-1307  USA                                                   *
 *                                                                         *
 ***************************************************************************/


#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#define DISABLE_DEBUGLOG


#include <gwenhywfar/gwenhywfarapi.h>
#include <gwenhywfar/misc.h>
#include <gwenhywfar/text.h>

#include "stringlist_p.h"
#include "debug.h"

#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif



GWEN_STRINGLIST *GWEN_StringList_new(void)
{
  GWEN_STRINGLIST *sl;

  GWEN_NEW_OBJECT(GWEN_STRINGLIST, sl);
  assert(sl);
  sl->ignoreRefCount=1;
  return sl;
}



void GWEN_StringList_free(GWEN_STRINGLIST *sl)
{
  GWEN_STRINGLISTENTRY *curr, *next;

  if (sl) {
    curr=sl->first;
    while (curr) {
      next=curr->next;
      GWEN_StringListEntry_free(curr);
      curr=next;
    } /* while */
    GWEN_FREE_OBJECT(sl);
  }
}



void GWEN_StringList_SetSenseCase(GWEN_STRINGLIST *sl, int i)
{
  assert(sl);
  sl->senseCase=i;
}



void GWEN_StringList_SetIgnoreRefCount(GWEN_STRINGLIST *sl, int i)
{
  assert(sl);
  sl->ignoreRefCount=i;
}



GWEN_STRINGLISTENTRY *GWEN_StringListEntry_new(const char *s, int take)
{
  GWEN_STRINGLISTENTRY *sl;

  GWEN_NEW_OBJECT(GWEN_STRINGLISTENTRY, sl);
  assert(sl);
  sl->refCount=1;
  if (s) {
    if (take)
      sl->data=s;
    else
      sl->data=strdup(s);
  }
  return sl;
}



void GWEN_StringListEntry_ReplaceString(GWEN_STRINGLISTENTRY *e,
                                        const char *s,
                                        int take)
{
  assert(e);
  if (e->data)
    free((void *)(e->data));
  if (take)
    e->data=s;
  else
    e->data=strdup(s);
}



void GWEN_StringListEntry_free(GWEN_STRINGLISTENTRY *sl)
{
  if (sl) {
    if (sl->data)
      free((void *)(sl->data));
    GWEN_FREE_OBJECT(sl);
  }
}



void GWEN_StringList_AppendEntry(GWEN_STRINGLIST *sl,
                                 GWEN_STRINGLISTENTRY *se)
{
  GWEN_STRINGLISTENTRY *curr;

  assert(sl);
  assert(se);

  curr=sl->first;
  if (!curr) {
    sl->first=se;
  }
  else {
    while (curr->next) {
      curr=curr->next;
    }
    curr->next=se;
  }
  sl->count++;
}



GWEN_STRINGLIST *GWEN_StringList_fromTabString(const char *s, int checkDup)
{
  GWEN_STRINGLIST *sl;

  sl=GWEN_StringList_new();
  if (s && *s) {
    while (*s) {
      const char *t;
      char *tmpStr;

      t=strchr(s, '\t');
      if (t) {
        int len;

        len=(t-s);
        tmpStr=(char *) malloc(len+1);
        assert(tmpStr);
        memmove(tmpStr, s, len);
        tmpStr[len]=0;
        /* add partial string, take it over */
        GWEN_StringList_AppendString(sl, tmpStr, 1, checkDup);
        s=t+1;
      }
      else {
        /* just add the remaining string (don't take over, copy!) */
        GWEN_StringList_AppendString(sl, s, 0, checkDup);
        break;
      }
    }
  }

  return sl;
}



void GWEN_StringList_RemoveEntry(GWEN_STRINGLIST *sl,
                                 GWEN_STRINGLISTENTRY *se)
{
  GWEN_STRINGLISTENTRY *curr;

  assert(sl);
  assert(se);

  curr=sl->first;
  if (curr) {
    if (curr==se) {
      sl->first=curr->next;
      if (sl->count)
        sl->count--;
    }
    else {
      while (curr->next!=se) {
        curr=curr->next;
      }
      if (curr) {
        curr->next=se->next;
        if (sl->count)
          sl->count--;
      }
    }
  }
}



void GWEN_StringList_Clear(GWEN_STRINGLIST *sl)
{
  GWEN_STRINGLISTENTRY *se, *next;

  assert(sl);
  se=sl->first;
  sl->first=0;
  sl->count=0;
  while (se) {
    next=se->next;
    GWEN_StringListEntry_free(se);
    se=next;
  } /* while */
}



int GWEN_StringList_AppendString(GWEN_STRINGLIST *sl,
                                 const char *s,
                                 int take,
                                 int checkDouble)
{
  GWEN_STRINGLISTENTRY *se;

  if (checkDouble) {
    se=sl->first;
    if (sl->senseCase) {
      while (se) {
        if (strcmp(se->data, s)==0) {
          if (take)
            free((char *)s);
          se->refCount++;
          return 0;
        }
        se=se->next;
      } /* while */
    }
    else {
      while (se) {
        if (strcasecmp(se->data, s)==0) {
          if (take)
            free((char *)s);
          se->refCount++;
          return 0;
        }
        se=se->next;
      } /* while */
    }
  } /* if checkdouble */

  se=GWEN_StringListEntry_new(s, take);
  GWEN_StringList_AppendEntry(sl, se);
  return 1;
}



int GWEN_StringList_InsertString(GWEN_STRINGLIST *sl,
                                 const char *s,
                                 int take,
                                 int checkDouble)
{
  GWEN_STRINGLISTENTRY *se;

  if (checkDouble) {
    se=sl->first;
    if (sl->senseCase) {
      while (se) {
        if (strcmp(se->data, s)==0) {
          if (take)
            free((char *)s);
          se->refCount++;
          return 0;
        }
        se=se->next;
      } /* while */
    }
    else {
      while (se) {
        if (strcasecmp(se->data, s)==0) {
          if (take)
            free((char *)s);
          se->refCount++;
          return 0;
        }
        se=se->next;
      } /* while */
    }
  } /* if checkdouble */
  se=GWEN_StringListEntry_new(s, take);
  se->next=sl->first;
  sl->first=se;
  sl->count++;
  return 1;
}



int GWEN_StringList_RemoveString(GWEN_STRINGLIST *sl, const char *s)
{
  GWEN_STRINGLISTENTRY *se;

  se=sl->first;
  if (sl->senseCase) {
    while (se) {
      if (strcmp(se->data, s)==0) {
        assert(se->refCount);
        se->refCount--;
        if (sl->ignoreRefCount)
          GWEN_StringList_RemoveEntry(sl, se);
        else {
          if (se->refCount==0)
            GWEN_StringList_RemoveEntry(sl, se);
        }
        return 1;
      }
      se=se->next;
    } /* while */
    return 0;
  }
  else {
    while (se) {
      if (strcasecmp(se->data, s)==0) {
        assert(se->refCount);
        se->refCount--;
        if (sl->ignoreRefCount)
          GWEN_StringList_RemoveEntry(sl, se);
        else {
          if (se->refCount==0)
            GWEN_StringList_RemoveEntry(sl, se);
        }
        return 1;
      }
      se=se->next;
    } /* while */
    return 0;
  }
}



void GWEN_StringList_RemoveFirstString(GWEN_STRINGLIST *sl)
{
  GWEN_STRINGLISTENTRY *se;

  assert(sl);
  se=sl->first;
  if (se) {
    assert(se->refCount);

    se->refCount--;
    if (sl->ignoreRefCount)
      GWEN_StringList_RemoveEntry(sl, se);
    else {
      if (se->refCount==0)
        GWEN_StringList_RemoveEntry(sl, se);
    }
  }
}



GWEN_STRINGLISTENTRY *GWEN_StringList_FirstEntry(const GWEN_STRINGLIST *sl)
{
  assert(sl);
  return sl->first;
}



GWEN_STRINGLISTENTRY *GWEN_StringListEntry_Next(const GWEN_STRINGLISTENTRY *se)
{
  assert(se);
  return se->next;
}



const char *GWEN_StringListEntry_Data(const GWEN_STRINGLISTENTRY *se)
{
  assert(se);
  return se->data;
}



void GWEN_StringListEntry_SetData(GWEN_STRINGLISTENTRY *se, const char *s)
{
  assert(se);
  if (se->data)
    free((void *)(se->data));
  if (s)
    se->data=strdup(s);
  else
    se->data=NULL;
}



unsigned int GWEN_StringList_Count(const GWEN_STRINGLIST *sl)
{
  assert(sl);
  return sl->count;
}



int GWEN_StringList_HasString(const GWEN_STRINGLIST *sl,
                              const char *s)
{
  GWEN_STRINGLISTENTRY *se;

  assert(sl);
  se=sl->first;
  if (sl->senseCase) {
    while (se) {
      if (strcmp(se->data, s)==0) {
        return 1;
      }
      se=se->next;
    } /* while */
    return 0;
  }
  else {
    while (se) {
      if (strcasecmp(se->data, s)==0) {
        return 1;
      }
      se=se->next;
    } /* while */
    return 0;
  }
}



int GWEN_StringList_GetStringPos(const GWEN_STRINGLIST *sl, const char *s)
{
  GWEN_STRINGLISTENTRY *se;
  int i;

  assert(sl);
  se=sl->first;
  if (sl->senseCase) {
    i=0;
    while (se) {
      if (strcmp(se->data, s)==0) {
        return i;
      }
      i++;
      se=se->next;
    } /* while */
    return -1;
  }
  else {
    i=0;
    while (se) {
      if (strcasecmp(se->data, s)==0) {
        return i;
      }
      i++;
      se=se->next;
    } /* while */
    return -1;
  }
}



GWEN_STRINGLISTENTRY *GWEN_StringList_FindStringEntry(const GWEN_STRINGLIST *sl, const char *s)
{
  GWEN_STRINGLISTENTRY *se;
  int i;

  assert(sl);
  se=sl->first;
  if (sl->senseCase) {
    i=0;
    while (se) {
      if (strcmp(se->data, s)==0) {
        return se;
      }
      i++;
      se=se->next;
    } /* while */
    return NULL;
  }
  else {
    i=0;
    while (se) {
      if (strcasecmp(se->data, s)==0) {
        return se;
      }
      i++;
      se=se->next;
    } /* while */
    return NULL;
  }
}



GWEN_STRINGLIST *GWEN_StringList_dup(const GWEN_STRINGLIST *sl)
{
  GWEN_STRINGLISTENTRY *se;
  GWEN_STRINGLIST *newsl;

  assert(sl);
  newsl=GWEN_StringList_new();

  se=sl->first;
  while (se) {
    GWEN_STRINGLISTENTRY *newse;

    newse=GWEN_StringListEntry_new(se->data, 0);
    GWEN_StringList_AppendEntry(newsl, newse);
    se=se->next;
  } /* while */

  return newsl;
}


void *GWEN_StringList_ForEach(const GWEN_STRINGLIST *l,
                              void *(*func)(const char *s, void *u),
                              void *user_data)
{
  GWEN_STRINGLISTENTRY *it;
  const char *el;
  void *result = 0;
  assert(l);

  it = GWEN_StringList_FirstEntry(l);
  if (!it)
    return 0;
  while (it) {
    el = GWEN_StringListEntry_Data(it);
    result = func(el, user_data);
    if (result) {
      return result;
    }
    it = GWEN_StringListEntry_Next(it);
  }
  return 0;
}



const char *GWEN_StringList_FirstString(const GWEN_STRINGLIST *l)
{
  assert(l);
  if (l->first==0)
    return 0;
  return l->first->data;
}



static int GWEN_StringList__compar_asc_nocase(const void *a, const void *b)
{
  const GWEN_STRINGLISTENTRY *const *pse1 = a, * const * pse2 = b;
  const GWEN_STRINGLISTENTRY *se1 = *pse1, *se2 = *pse2;
  if (se1 && se2 && se1->data && se2->data)
    return strcmp(se1->data, se2->data);
  else
    return 0;
}
static int GWEN_StringList__compar_desc_nocase(const void *a, const void *b)
{
  const GWEN_STRINGLISTENTRY *const *pse1 = a, * const * pse2 = b;
  const GWEN_STRINGLISTENTRY *se1 = *pse1, *se2 = *pse2;
  if (se1 && se2 && se1->data && se2->data)
    return strcmp(se2->data, se1->data);
  else
    return 0;
}
static int GWEN_StringList__compar_asc_case(const void *a, const void *b)
{
  const GWEN_STRINGLISTENTRY *const *pse1 = a, * const * pse2 = b;
  const GWEN_STRINGLISTENTRY *se1 = *pse1, *se2 = *pse2;
  if (se1 && se2 && se1->data && se2->data)
    return strcasecmp(se1->data, se2->data);
  else
    return 0;
}
static int GWEN_StringList__compar_desc_case(const void *a, const void *b)
{
  const GWEN_STRINGLISTENTRY *const *pse1 = a, * const * pse2 = b;
  const GWEN_STRINGLISTENTRY *se1 = *pse1, *se2 = *pse2;
  if (se1 && se2 && se1->data && se2->data)
    return strcasecmp(se2->data, se1->data);
  else
    return 0;
}

static int GWEN_StringList__compar_asc_int(const void *a, const void *b)
{
  const GWEN_STRINGLISTENTRY *const *pse1 = a, * const * pse2 = b;
  const GWEN_STRINGLISTENTRY *se1 = *pse1, *se2 = *pse2;
  if (se1 && se2 && se1->data && se2->data) {
    int i1, i2;

    i1=atoi(se1->data);
    i2=atoi(se2->data);
    return (i1>i2) - (i1<i2);
  }
  else
    return 0;
}

static int GWEN_StringList__compar_desc_int(const void *a, const void *b)
{
  const GWEN_STRINGLISTENTRY *const *pse1 = a, * const * pse2 = b;
  const GWEN_STRINGLISTENTRY *se1 = *pse1, *se2 = *pse2;
  if (se1 && se2 && se1->data && se2->data) {
    int i1, i2;

    i1=atoi(se1->data);
    i2=atoi(se2->data);
    return (i2>i1) - (i2<i1);
  }
  else
    return 0;
}



void GWEN_StringList_Sort(GWEN_STRINGLIST *l,
                          int ascending,
                          GWEN_STRINGLIST_SORT_MODE sortMode)
{
  GWEN_STRINGLISTENTRY **tmpEntries;
  GWEN_STRINGLISTENTRY *sentry;
  GWEN_STRINGLISTENTRY **psentry;

  if (l->count<2)
    return;

  /* sort entries into a linear pointer list */
  tmpEntries=(GWEN_STRINGLISTENTRY **)malloc((l->count+1)*
                                             sizeof(GWEN_STRINGLISTENTRY *));
  assert(tmpEntries);
  sentry=l->first;
  psentry=tmpEntries;
  while (sentry) {
    *(psentry++)=sentry;
    sentry=sentry->next;
  } /* while */
  *psentry=0;

  /* sort */
  switch (sortMode) {
  case GWEN_StringList_SortModeNoCase:
    if (ascending)
      qsort(tmpEntries, l->count, sizeof(GWEN_STRINGLISTENTRY *),
            GWEN_StringList__compar_asc_nocase);
    else
      qsort(tmpEntries, l->count, sizeof(GWEN_STRINGLISTENTRY *),
            GWEN_StringList__compar_desc_nocase);
    break;

  case GWEN_StringList_SortModeCase:
    if (ascending)
      qsort(tmpEntries, l->count, sizeof(GWEN_STRINGLISTENTRY *),
            GWEN_StringList__compar_asc_case);
    else
      qsort(tmpEntries, l->count, sizeof(GWEN_STRINGLISTENTRY *),
            GWEN_StringList__compar_desc_case);
    break;

  case GWEN_StringList_SortModeInt:
    if (ascending)
      qsort(tmpEntries, l->count, sizeof(GWEN_STRINGLISTENTRY *),
            GWEN_StringList__compar_asc_int);
    else
      qsort(tmpEntries, l->count, sizeof(GWEN_STRINGLISTENTRY *),
            GWEN_StringList__compar_desc_int);
    break;

  default:
    DBG_ERROR(GWEN_LOGDOMAIN, "Unknown sortmode %d", sortMode);
  }

  /* sort entries back into GWEN_STRINGLIST */
  psentry=tmpEntries;
  sentry=0;
  while (*psentry) {
    if (sentry)
      sentry->next=*psentry;
    else
      l->first=*psentry;
    sentry=*psentry;
    psentry++;
  } /* while */
  sentry->next=NULL;

  free(tmpEntries);

}



const char *GWEN_StringList_StringAt(const GWEN_STRINGLIST *sl, int idx)
{
  GWEN_STRINGLISTENTRY *se;

  assert(sl);
  se=sl->first;
  while (se) {
    if (idx--==0)
      return se->data;
    se=se->next;
  } /* while */
  return 0;
}



int GWEN_StringList_StringAsIntAt(const GWEN_STRINGLIST *l, int idx, int defaultValue)
{
  const char *s;

  s=GWEN_StringList_StringAt(l, idx);
  if (s&& *s) {
    int i;

    if (1==sscanf(s, "%d", &i))
      return i;
  }

  return defaultValue;
}



GWEN_STRINGLIST *GWEN_StringList_fromString(const char *str, const char *delimiters, int checkDouble)
{
  if (str && *str) {
    GWEN_STRINGLIST *sl;
    const unsigned char *s;

    sl=GWEN_StringList_new();
    s=(const unsigned char *)str;

    while (*s) {
      /* skip blanks */
      while (*s && *s<33)
        s++;

      if (*s) {
        const unsigned char *pStart;
        int len;

        /* read word */
        pStart=s;
        //s++;
        while (*s && strchr(delimiters, *s)==NULL)
          s++;
        len=s-pStart;

        if (len) {
          char *toAdd;

          toAdd=(char *) malloc(len+1);
          assert(toAdd);

          memmove(toAdd, pStart, len);
          toAdd[len]=0;

          GWEN_StringList_AppendString(sl, toAdd, 1, checkDouble);
        }
      }

      if (*s==0)
        break;
      s++;
    }

    if (GWEN_StringList_Count(sl)==0) {
      GWEN_StringList_free(sl);
      return NULL;
    }
    return sl;
  }
  else
    return NULL;
}



GWEN_STRINGLIST *GWEN_StringList_fromString2(const char *str, const char *delimiters, int checkDouble, uint32_t flags)
{
  if (str && *str) {
    GWEN_STRINGLIST *sl;
    const char *s;
    GWEN_BUFFER *wbuf;

    sl=GWEN_StringList_new();
    s=(const char *)str;

    wbuf=GWEN_Buffer_new(0, 256, 0, 1);
    while (*s) {
      char *copyOfWord;

      while (*s && isspace((int)*s))
        s++;
      if (!(*s))
        break;

      DBG_VERBOUS(GWEN_LOGDOMAIN, "Reading word from here: [%s] (delimiters: [%s])", s, delimiters);
      if (GWEN_Text_GetWordToBuffer(s, delimiters, wbuf, flags, &s))
        break;

      copyOfWord=strdup(GWEN_Buffer_GetStart(wbuf));
      GWEN_StringList_AppendString(sl, copyOfWord, 1, checkDouble);
      GWEN_Buffer_Reset(wbuf);
      if (*s)
        s++;
    } /* while */
    GWEN_Buffer_free(wbuf);

    if (GWEN_StringList_Count(sl)==0) {
      GWEN_StringList_free(sl);
      return NULL;
    }
    return sl;
  }

  return NULL;
}



int GWEN_StringList_toBuffer(const GWEN_STRINGLIST *sl, const char *delimiter, GWEN_BUFFER *outBuffer)
{
  int entriesAdded=0;

  if (sl) {
    GWEN_STRINGLISTENTRY *se;

    se=sl->first;
    while (se) {
      if (se->data && *(se->data)) {
        if (entriesAdded && delimiter && *delimiter)
          GWEN_Buffer_AppendString(outBuffer, delimiter);
        GWEN_Buffer_AppendString(outBuffer, se->data);
        entriesAdded++;
      }
      se=se->next;
    } /* while */
  }
  return entriesAdded;
}



void GWEN_StringList_RemoveCommonFirstEntries(GWEN_STRINGLIST *sl1, GWEN_STRINGLIST *sl2)
{
  GWEN_STRINGLISTENTRY *se1;
  GWEN_STRINGLISTENTRY *se2;

  se1=GWEN_StringList_FirstEntry(sl1);
  se2=GWEN_StringList_FirstEntry(sl2);

  while(se1 && se2) {
    GWEN_STRINGLISTENTRY *se1Next;
    GWEN_STRINGLISTENTRY *se2Next;
    const char *s1;
    const char *s2;

    se1Next=GWEN_StringListEntry_Next(se1);
    se2Next=GWEN_StringListEntry_Next(se2);

    s1=GWEN_StringListEntry_Data(se1);
    s2=GWEN_StringListEntry_Data(se2);
    if (!(s1 && *s1 && s2 && *s2 && strcasecmp(s1, s2)==0))
      break;
    GWEN_StringList_RemoveEntry(sl1, se1);
    GWEN_StringList_RemoveEntry(sl2, se2);

    se1=se1Next;
    se2=se2Next;
  }
}



void GWEN_StringList_AppendStringList(GWEN_STRINGLIST *slDest, const GWEN_STRINGLIST *slSource, int checkDouble)
{
  if (slSource) {
    GWEN_STRINGLISTENTRY *se;

    se=slSource->first;
    while (se) {
      if (se->data && *(se->data))
        GWEN_StringList_AppendString(slDest, se->data, 0, checkDouble);
      se=se->next;
    } /* while */
  }
}



