/*
 * Gophernicus 
 *
 * Copyright (c) 2009-2018 Kim Holviala <kimholviala@fastmail.com>
 * Copyright (c) 2019 Gophernicus Developers <gophernicus@gophernicus.org>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include "gophernicus.h"


/*
 * Repeat a character num times and zero-terminate
 */
void strrepeat(char *dest, char c, size_t num)
{
    memset(dest, c, num);
    dest[num] = '\0';
}


/*
 * Replace characters in-place
 */
void strreplace(char *str, char from, char to)
{
    while (*str) {
        if (*str == from) *str = to;
        str++;
    }
}


/*
 * Cut string to width, return resulting width (UTF-8 aware)
 */
size_t strcut(char *str, size_t width)
{
    unsigned char c = '\0';
    int w = 0;
    int i;

    while (width-- && (c = *str++)) {
        if (c >= 0x80 && (*str & 0xc0) == 0x80) {
            i = 0;

            if ((c & 0xf8) == 0xf0) i = 3;
            else if ((c & 0xf0) == 0xe0) i = 2;
            else if ((c & 0xe0) == 0xc0) i = 1;

            while (i--) if (!*str++) break;
        }

        w++;
    }

    if (c) *str = '\0';
    return w;
}


/*
 * Match key and return value (key: value)
 */
char *strkey(char *header, char *key)
{
    char *c;
    size_t len;

    if ((len = strlen(key)) == 0) return NULL;

    if (strncasecmp(header, key, len) == MATCH) {
        c = header + len;
        do { c++; } while (*c == ' ' || *c == '\t');

        if (*c != ':') return NULL;

        do { c++; } while (*c == ' ' || *c == '\t');
        return c;
    }

    return NULL;
}


/*
 * Return last character of a string
 */
char strlast(char *str)
{
    int len;

    if ((len = (int)strlen(str) - 1) >= 0) return str[len];
    else return 0;
}


/*
 * Remove CRLF from a string
 */
void chomp(char *str)
{
    char *c;

    if ((c = strrchr(str, '\n'))) *c = '\0';
    if ((c = strrchr(str, '\r'))) *c = '\0';
}


/*
 * Return charset name
 */
char *strcharset(int charset)
{
    if (charset == AUTO) return "auto";
    if (charset == US_ASCII) return "US-ASCII";
    if (charset == ISO_8859_1) return "ISO-8859-1";
    if (charset == UTF_8) return "UTF-8";

    return "(unknown)";
}


/*
 * Convert a string between UTF-8, ISO-8859-1 and US-ASCII
 */
void strniconv(int charset, char *out, char *in, size_t outsize)
{
    char ascii[] = ASCII;
    unsigned long c;
    size_t len;
    int i;

    /* Loop through the input string */
    len = strlen(in);
    while (--outsize && len > 0) {

        /* Get one input char */
        c = (unsigned char) *in++;
        len--;

        /* 7-bit chars are the same in all three charsets */
        if (c < 0x80) {
            *out++ = (unsigned char) c;
            continue;
        }

        /* Assume ISO-8859-1 which requires 0 extra bytes */
        i = 0;

        /* UTF-8? (We'll actually check the next char here, not current) */
        if ((*in & 0xc0) == 0x80) {

            /* Four-byte UTF-8? */
            if ((c & 0xf8) == 0xf0 && len >= 3) { c &= 0x07; i = 3; }

            /* Three-byte UTF-8? */
            else if ((c & 0xf0) == 0xe0 && len >= 2) { c &= 0x0f; i = 2; }

            /* Two-byte UTF-8? */
            else if ((c & 0xe0) == 0xc0 && len >= 1) { c &= 0x1f; i = 1; }

            /* Parse rest of the UTF-8 bytes */
            while (i--) {
                c <<= 6;
                c |= *in++ & 0x3f;
                len--;
            }
        }

        /*
         * At this point we've got one 32bit UTF character in c and
         * we're ready to convert it to the specified output charset
         */

        /* Handle UTF-8 */
        if (charset == UTF_8) {
            i = 0;

            /* Two-byte encoding? */
            if (c < 0x800 && outsize > 2) { *out++ = (c >> 6) | 0xc0; i = 1; }

            /* Three-byte encoding? */
            else if (c < 0x10000 && outsize > 3) { *out++ = (c >> 12) | 0xe0; i = 2; }

            /* Four-byte encoding? */
            else if (c < 0x110000 && outsize > 4) { *out++ = (c >> 18) | 0xf0; i = 3; }

            /* Encode rest of the UTF-8 bytes */
            while (i--) {
                *out++ = ((c >> (i * 6)) & 0x3f) | 0x80;
                outsize--;
            }
            continue;
        }

        /* Handle ISO-8859-1 */
        if (charset == ISO_8859_1) {

            if (c >= 0xa0 && c <= 0xff)
                *out++ = (unsigned char) c;
            else
                *out++ = UNKNOWN;
            continue;
        }

        /* Handle all other charsets as 7-bit US-ASCII */
        if (c >= 0x80 && c <= 0xff)
            *out++ = ascii[c - 0x80];
        else
            *out++ = UNKNOWN;
    }

    /* Zero-terminate output */
    *out = '\0';
}


/*
 * Encode string with #OCT encoding
 */
void strnencode(char *out, const char *in, size_t outsize)
{
    unsigned char c;

    /* Loop through the input string */
    while (--outsize) {

        /* End of source? */
        if (!(c = *in++)) break;

        /* Need to encode the char? */
        if (c < '+' || c > '~') {

            /* Can we fit the encoded version into outbuffer? */
            if (outsize < 5) break;

            /* Output encoded char */
            snprintf(out, outsize, "#%.3o", c);
            out += 4;
        }

        /* Copy regular chars */
        else *out++ = c;
    }

    /* Zero-terminate output */
    *out = '\0';
}


/*
 * Decode both %HEX and #OCT encodings
 */
void strndecode(char *out, char *in, size_t outsize)
{
    unsigned char c;
    unsigned int i;

    /* Loop through the input string */
    while (--outsize) {

        /* End of source? */
        if (!(c = *in++)) break;

        /* Parse %hex encoding */
        if (c == '%' && strlen(in) >= 2) {
            sscanf(in, "%2x", &i);
            *out++ = i;
            in += 2;
            continue;
        }

        /* Parse #octal encoding */
        if (c == '#' && strlen(in) >= 3) {
            sscanf(in, "%3o", &i);
            *out++ = i;
            in += 3;
            continue;
        }

        /* Copy non-encoded chars */
        *out++ = c;
    }

    /* Zero-terminate output */
    *out = '\0';
}


/*
 * Format number to human-readable filesize with unit
 */
void strfsize(char *out, off_t size, size_t outsize)
{
    static char *unit[] = { UNITS };
    int u;
    float s;

    /* Start with kilobytes */
    s = ((float) size) / 1024;
    u = 0;

    /* Loop through the units until the size is small enough */
    while (s >= 1000 && unit[(u + 1)]) {
        s = s / 1024;
        u++;
    }

    /* Format size */
    snprintf(out, outsize, "%7.1f %s", s, unit[u]);
}


#ifndef HAVE_STRLCPY
/*
 * 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.
 */

#include <sys/types.h>
#include <string.h>

/*
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 */
size_t strlcpy(char *dst, const char *src, size_t siz)
{
    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 */
}

/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
size_t 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 + strlen(s));
    while (*s != '\0') {
        if (n != 1) {
            *d++ = *s;
            n--;
        }
        s++;
    }
    *d = '\0';

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

#endif