/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "config.h"

#include <stddef.h>
#include <string.h>

/**
 * Returns the space remaining in a buffer assuming that the given number of
 * bytes have already been written. If the number of bytes exceeds the size
 * of the buffer, zero is returned.
 *
 * @param n
 *     The size of the buffer in bytes.
 *
 * @param length
 *     The number of bytes which have been written to the buffer so far. If
 *     the routine writing the bytes will automatically truncate its writes,
 *     this value may exceed the size of the buffer.
 *
 * @return
 *     The number of bytes remaining in the buffer. This value will always
 *     be non-negative. If the number of bytes written already exceeds the
 *     size of the buffer, zero will be returned.
 */
#define REMAINING(n, length) (((n) < (length)) ? 0 : ((n) - (length)))

size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n) {

#ifdef HAVE_STRLCPY
    return strlcpy(dest, src, n);
#else
    /* Calculate actual length of desired string */
    size_t length = strlen(src);

    /* Copy nothing if there is no space */
    if (n == 0)
        return length;

    /* Calculate length of the string which will be copied */
    size_t copy_length = length;
    if (copy_length >= n)
        copy_length = n - 1;

    /* Copy only as much of string as possible, manually adding a null
     * terminator */
    memcpy(dest, src, copy_length);
    dest[copy_length] = '\0';

    /* Return the overall length of the desired string */
    return length;
#endif

}

size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) {

#ifdef HAVE_STRLCPY
    return strlcat(dest, src, n);
#else
    size_t length = strnlen(dest, n);
    return length + guac_strlcpy(dest + length, src, REMAINING(n, length));
#endif

}

char* guac_strnstr(const char *haystack, const char *needle, size_t len) {

#ifdef HAVE_STRNSTR
    return strnstr(haystack, needle, len);
#else
    char* chr;
    size_t nlen = strlen(needle), off = 0;

    /* Follow documented API: return haystack if needle is the empty string. */
    if (nlen == 0)
        return (char *)haystack;

    /* Use memchr to find candidates. It might be optimized in asm. */
    while (off < len && NULL != (chr = memchr(haystack + off, needle[0], len - off))) {
        /* chr is guaranteed to be in bounds of and >= haystack. */
        off = chr - haystack;
        /* If needle would go beyond provided len, it doesn't exist in haystack. */
        if (off + nlen > len)
            return NULL;
        /* Now that we know we have at least nlen bytes, compare them. */
        if (!memcmp(chr, needle, nlen))
            return chr;
        /* Make sure we make progress. */
        off += 1;
    }

    /* memchr ran out of candidates, needle wasn't found. */
    return NULL;
#endif

}

char* guac_strdup(const char* str) {

    /* Return NULL if no string provided */
    if (str == NULL)
        return NULL;

    /* Otherwise just invoke strdup() */
    return strdup(str);

}

size_t guac_strljoin(char* restrict dest, const char* restrict const* elements,
        int nmemb, const char* restrict delim, size_t n) {

    size_t length = 0;
    const char* restrict const* current = elements;

    /* If no elements are provided, nothing to do but ensure the destination
     * buffer is null terminated */
    if (nmemb <= 0)
        return guac_strlcpy(dest, "", n);

    /* Initialize destination buffer with first element */
    length += guac_strlcpy(dest, *current, n);

    /* Copy all remaining elements, separated by delimiter */
    for (current++; nmemb > 1; current++, nmemb--) {
        length += guac_strlcat(dest + length, delim, REMAINING(n, length));
        length += guac_strlcat(dest + length, *current, REMAINING(n, length));
    }

    return length;

}

