/* Leave the OpenBSD version below so we can track upstream fixes */
/*      $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $        */

/*
 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>

#ifdef HAVE_BSD_STRING_H
# include <bsd/string.h>
#endif

#ifdef HAVE_ICONV
# include <errno.h>
# include <iconv.h>
#endif

#include "eina_private.h"
#include "eina_str.h"
#include "eina_cpu.h"

/*============================================================================*
*                                  Local                                     *
*============================================================================*/

/**
 * @cond LOCAL
 */

/*
 * Internal helper function used by eina_str_has_suffix() and
 * eina_str_has_extension()
 */
static inline Eina_Bool
eina_str_has_suffix_helper(const char *str,
                           const char *suffix,
                           int (*cmp)(const char *, const char *))
{
   size_t str_len;
   size_t suffix_len;

   if ((!str) || (!suffix)) return EINA_FALSE;
   str_len = strlen(str);
   suffix_len = eina_strlen_bounded(suffix, str_len);
   if (suffix_len == (size_t)-1)
      return EINA_FALSE;

   return cmp(str + str_len - suffix_len, suffix) == 0;
}

static inline char **
eina_str_split_full_helper(const char *str,
                           const char *delim,
                           int max_tokens,
                           unsigned int *elements)
{
   char *s, *pos, **str_array;
   const char *src;
   size_t len, dlen;
   unsigned int tokens = 0, x;
   const char *idx[256] = {NULL};

   if ((!str) || (!delim))
     {
        if (elements)
          *elements = 0;

        return NULL;
     }
   if (max_tokens < 0) max_tokens = 0;
   if (max_tokens == 1)
     {
        str_array = malloc(sizeof(char *) * 2);
        if (!str_array)
          {
             if (elements)
                *elements = 0;

             return NULL;
          }

        s = strdup(str);
        if (!s)
          {
             free(str_array);
             if (elements)
                *elements = 0;

             return NULL;
          }
        if (elements)
          *elements = 1;
        str_array[0] = s;
        str_array[1] = NULL;
        return str_array;
     }
   dlen = strlen(delim);
   if (dlen == 0)
     {
        if (elements)
           *elements = 0;

        return NULL;
     }

   src = str;
   /* count tokens and check strlen(str) */
   while (*src != '\0')
     {
        const char *d = delim, *d_end = d + dlen;
        const char *tmp = src;
        for (; (d < d_end) && (*tmp != '\0'); d++, tmp++)
          {
             if (EINA_LIKELY(*d != *tmp))
                break;
          }
        if (EINA_UNLIKELY(d == d_end))
          {
             src = tmp;
             if (tokens < (sizeof(idx) / sizeof(idx[0])))
               {
                  idx[tokens] = tmp;
                  //printf("token %d='%s'\n", tokens + 1, idx[tokens]);
               }
             tokens++;
             if (tokens && (tokens == (unsigned int)max_tokens)) break;
          }
        else
           src++;
     }
   len = src - str + strlen(src);

   str_array = malloc(sizeof(char *) * (tokens + 2));
   if (!str_array)
     {
        if (elements)
           *elements = 0;

        return NULL;
     }

   if (!tokens)
     {
        s = strdup(str);
        if (!s)
          {
             free(str_array);
             if (elements)
                *elements = 0;

             return NULL;
          }
        str_array[0] = s;
        str_array[1] = NULL;
        if (elements)
          *elements = 1;
        return str_array;
     }

   s = malloc(len + 1);
   if (!s)
     {
        free(str_array);
        if (elements)
           *elements = 0;

        return NULL;
     }

   str_array[0] = s;

   if (len == tokens * dlen)
     {
        /* someone's having a laugh somewhere */
        memset(s, 0, len + 1);
        for (x = 1; x < tokens + 1; x++)
          str_array[x] = s + x;
        str_array[x] = NULL;
        if (elements)
          *elements = x;
        return str_array;
     }
   /* copy tokens and string */
   if (idx[0] - str - dlen > len)
     {
        /* FIXME: don't think this can happen but putting this here just in case */
        abort();
     }
   pos = s;
   for (x = 0; x < MIN(tokens, (sizeof(idx) / sizeof(idx[0]))); x++)
     {
        if (x + 1 < (sizeof(idx) / sizeof(idx[0])))
          {
             /* first one is special */
             if (!x)
               {
                  eina_strlcpy(pos, str, idx[x] - str - dlen + 1);
                  str_array[x] = pos;
                  //printf("str_array[%d] = '%s'\n", x, str_array[x]);
                  pos += idx[x] - str - dlen + 1;
                  if ((tokens == 1) && (idx[0]))
                    {
                       eina_strlcpy(pos, idx[x], len + 1 - (pos - s));
                       x++, tokens++;
                       str_array[x] = pos;
                    }
               }
             /* more tokens */
             else if (idx[x + 1])
               {
                  eina_strlcpy(pos, idx[x - 1], idx[x] - idx[x - 1] - dlen + 1);
                  str_array[x] = pos;
                  //printf("str_array[%d] = '%s'\n", x, str_array[x]);
                  pos += idx[x] - idx[x - 1] - dlen + 1;
               }
             /* last token */
             else
               {
                  if (max_tokens && ((unsigned int)max_tokens < tokens + 1))
                    eina_strlcpy(pos, idx[x - 1], len + 1 - (pos - s));
                  else
                    {
                       //printf("diff: %d\n", len + 1 - (pos - s));
                       eina_strlcpy(pos, idx[x - 1], idx[x] - idx[x - 1] - dlen + 1);
                       str_array[x] = pos;
                       //printf("str_array[%d] = '%s'\n", x, str_array[x]);
                       pos += idx[x] - idx[x - 1] - dlen + 1;
                       x++, tokens++;
                       eina_strlcpy(pos, idx[x - 1], len + 1 - (pos - s));
                    }
                  str_array[x] = pos;
                  //printf("str_array[%d] = '%s'\n", x, str_array[x]);
               }
          }
        /* no more tokens saved after this one */
        else
          {
             eina_strlcpy(pos, idx[x - 1], idx[x] - idx[x - 1] - dlen + 1);
             str_array[x] = pos;
             //printf("str_array[%d] = '%s'\n", x, str_array[x]);
             pos += idx[x] - idx[x - 1] - dlen + 1;
             src = idx[x];
             x++, tokens++;
             str_array[x] = s = pos;
             break;
          }
     }
   if ((x != tokens) && ((!max_tokens) || (x < tokens)))
     {
        while (*src != '\0')
          {
             const char *d = delim, *d_end = d + dlen;
             const char *tmp = src;
             for (; (d < d_end) && (*tmp != '\0'); d++, tmp++)
               {
                  if (EINA_LIKELY(*d != *tmp))
                     break;
               }
             if (((!max_tokens) || (((tokens == (unsigned int)max_tokens) || x < tokens - 2))) && (EINA_UNLIKELY(d == d_end)))
               {
                  src = tmp;
                  *s = '\0';
                  s++, x++;
                  //printf("str_array[%d] = '%s'\n", x, str_array[x - 1]);
                  str_array[x] = s;
               }
             else
               {
                  *s = *src;
                  s++, src++;
               }
          }
        *s = 0;
     }
   str_array[tokens] = NULL;
   if (elements)
     *elements = tokens;

   return str_array;
}

/**
 * @endcond
 */

/*============================================================================*
*                                 Global                                     *
*============================================================================*/

/*============================================================================*
*                                   API                                      *
*============================================================================*/

EAPI size_t
eina_strlcpy(char *dst, const char *src, size_t siz)
{
#ifdef HAVE_STRLCPY
   return strlcpy(dst, src, siz);
#else
   char *d = dst;
   const char *s = src;
   size_t n = siz;

   /* Copy as many bytes as will fit */
   if (n != 0)
      while (--n != 0)
        {
           if ((*d++ = *s++) == '\0')
              break;
        }

   /* Not enough room in dst, add NUL and traverse rest of src */
   if (n == 0)
     {
        if (siz != 0)
           *d = '\0';  /* NUL-terminate dst */

        while (*s++)
           ;
     }

   return(s - src - 1); /* count does not include NUL */
#endif
}

EAPI size_t
eina_strlcat(char *dst, const char *src, size_t siz)
{
   char *d = dst;
   const char *s = src;
   size_t n = siz;
   size_t dlen;

   /* Find the end of dst and adjust bytes left but don't go past end */
   while (n-- != 0 && *d != '\0')
      d++;
   dlen = d - dst;
   n = siz - dlen;

   if (n == 0)
     return(dlen + (s ? strlen(s) : 0));

   if (s != NULL)
     {
        while (*s != '\0') {
           if (n != 1)
             {
                *d++ = *s;
                n--;
             }

           s++;
        }
     }
   *d = '\0';

   return(dlen + (s - src)); /* count does not include NUL */
}

EAPI char *
eina_strftime(const char *format, const struct tm *tm)
{
   const size_t flen = strlen(format);
   size_t buflen = 16; // An arbitrary starting size
   char *buf = NULL;

   do {
      char *tmp;
      size_t len;

      tmp = realloc(buf, buflen * sizeof(char));
      if (!tmp) goto on_error;
      buf = tmp;

      len = strftime(buf, buflen, format, tm);
      // Check if we have the expected result and return it.
      if ((len > 0 && len < buflen) || (len == 0 && flen == 0))
        {
           tmp = realloc(buf, ((len + 1) * sizeof(char)));
           buf = tmp;
           return buf;
        }

      /* Possibly buf overflowed - try again with a bigger buffer */
      buflen <<= 1; // multiply buffer size by 2
   } while (buflen < 128 * flen);

 on_error:
   free(buf);
   return NULL;
}

EAPI Eina_Bool
eina_str_has_prefix(const char *str, const char *prefix)
{
   size_t str_len;
   size_t prefix_len;

   str_len = strlen(str);
   prefix_len = eina_strlen_bounded(prefix, str_len);
   if (prefix_len == (size_t)-1)
     return EINA_FALSE;

   return (strncmp(str, prefix, prefix_len) == 0);
}

EAPI Eina_Bool
eina_str_has_suffix(const char *str, const char *suffix)
{
   return eina_str_has_suffix_helper(str, suffix, strcmp);
}

EAPI Eina_Bool
eina_str_has_extension(const char *str, const char *ext)
{
   return eina_str_has_suffix_helper(str, ext, strcasecmp);
}

EAPI char **
eina_str_split_full(const char *str,
                    const char *delim,
                    int max_tokens,
                    unsigned int *elements)
{
   return eina_str_split_full_helper(str, delim, max_tokens, elements);
}


EAPI char **
eina_str_split(const char *str, const char *delim, int max_tokens)
{
   return eina_str_split_full_helper(str, delim, max_tokens, NULL);
}

EAPI size_t
eina_str_join_len(char *dst,
                  size_t size,
                  char sep,
                  const char *a,
                  size_t a_len,
                  const char *b,
                  size_t b_len)
{
   size_t ret = a_len + b_len + 1;
   size_t off;

   if (size < 1)
     return ret;

   if (size <= a_len)
     {
        memcpy(dst, a, size - 1);
        dst[size - 1] = '\0';
        return ret;
     }

   memcpy(dst, a, a_len);
   off = a_len;

   if (size <= off + 1)
     {
        dst[size - 1] = '\0';
        return ret;
     }

   dst[off] = sep;
   off++;

   if (size <= off + b_len + 1)
     {
        memcpy(dst + off, b, size - off - 1);
        dst[size - 1] = '\0';
        return ret;
     }

   memcpy(dst + off, b, b_len);
   dst[off + b_len] = '\0';
   return ret;
}

#ifdef HAVE_ICONV
EAPI char *
eina_str_convert(const char *enc_from, const char *enc_to, const char *text)
{
   iconv_t ic;
   char *new_txt, *outp;
   const char *inp;
   size_t inb, outb, outlen, tob, outalloc;

   if (!text)
      return NULL;

   ic = iconv_open(enc_to, enc_from);
   if (ic == (iconv_t)(-1))
      return NULL;

   new_txt = malloc(64);
   inb = strlen(text);
   outb = 64;
   inp = text;
   outp = new_txt;
   outalloc = 64;
   outlen = 0;

   for (;; )
     {
        size_t count;

        tob = outb;
        count = iconv(ic, (char **)&inp, &inb, &outp, &outb);
        outlen += tob - outb;
        if (count == (size_t)(-1))
          {
             if (errno == E2BIG)
               {
                  new_txt = realloc(new_txt, outalloc + 64);
                  outp = new_txt + outlen;
                  outalloc += 64;
                  outb += 64;
               }
             else
               {
                  if (new_txt)
                     free(new_txt);

                  new_txt = NULL;
                  break;
               }
          }

        if (inb == 0)
          {
             if (outalloc == outlen)
                new_txt = realloc(new_txt, outalloc + 1);

             new_txt[outlen] = 0;
             break;
          }
     }
   iconv_close(ic);
   return new_txt;
}
#else
EAPI char *
eina_str_convert(const char *enc_from EINA_UNUSED,
                 const char *enc_to EINA_UNUSED,
                 const char *text EINA_UNUSED)
{
   return NULL;
}
#endif

#ifdef HAVE_ICONV
EAPI char *
eina_str_convert_len(const char *enc_from, const char *enc_to, const char *text, size_t len, size_t *retlen)
{
   iconv_t ic;
   char *new_txt, *outp;
   const char *inp;
   size_t inb, outb, outlen, tob, outalloc;

   if (retlen) *retlen = 0;
   if (!text) return NULL;

   ic = iconv_open(enc_to, enc_from);
   if (ic == (iconv_t)(-1))
      return NULL;

   new_txt = malloc(64);
   inb = len;
   outb = 64;
   inp = text;
   outp = new_txt;
   outalloc = 64;
   outlen = 0;

   for (;; )
     {
        size_t count;

        tob = outb;
        count = iconv(ic, (char **)&inp, &inb, &outp, &outb);
        outlen += tob - outb;
        if (count == (size_t)(-1))
          {
             if (errno == E2BIG)
               {
                  new_txt = realloc(new_txt, outalloc + 64);
                  outp = new_txt + outlen;
                  outalloc += 64;
                  outb += 64;
               }
             else
               {
                  if (new_txt)
                     free(new_txt);

                  new_txt = NULL;
                  break;
               }
          }

        if (inb == 0)
          {
             if (outalloc == outlen)
                new_txt = realloc(new_txt, outalloc + 1);

             new_txt[outlen] = 0;
             break;
          }
     }
   iconv_close(ic);
   if (retlen) *retlen = outlen;
   return new_txt;
}
#else
EAPI char *
eina_str_convert_len(const char *enc_from EINA_UNUSED, const char *enc_to EINA_UNUSED, const char *text EINA_UNUSED, size_t len EINA_UNUSED, size_t *retlen)
{
   if (retlen) *retlen = 0;
   return NULL;
}
#endif

EAPI char *
eina_str_escape(const char *str)
{
   char *s2, *d;
   const char *s;

   if (!str)
      return NULL;

   s2 = malloc((strlen(str) * 2) + 1);
   if (!s2)
      return NULL;

   for (s = str, d = s2; *s != 0; s++, d++)
     {
        switch (*s)
        {
         case ' ':
         case '\\':
         case '\'':
         case '\"':
           {
             *d = '\\';
             d++;
             *d = *s;
             break;
           }
         case '\n':
           {
             *d = '\\'; d++;
             *d = 'n';
             break;
           }
         case '\t':
           {
             *d = '\\'; d++;
             *d = 't';
             break;
           }
         default:
           {
             *d = *s;
             break;
           }
        }
     }
   *d = 0;
   return s2;
}

EAPI void
eina_str_tolower(char **str)
{
   char *p;
   if ((!str) || (!(*str)))
      return;

   for (p = *str; (*p); p++)
      *p = tolower((unsigned char )(*p));
}

EAPI void
eina_str_toupper(char **str)
{
   char *p;
   if ((!str) || (!(*str)))
      return;

   for (p = *str; (*p); p++)
      *p = toupper((unsigned char)(*p));
}

EAPI unsigned char *
eina_memdup(unsigned char *mem, size_t size, Eina_Bool terminate)
{
   unsigned char *ret;

   if (!mem) return NULL;

   terminate = !!terminate;
   ret = malloc(size + terminate);
   if (!ret) return NULL;

   memcpy(ret, mem, size);
   if (terminate)
     ret[size] = 0;
   return ret;
}
