
/** @file  psString.c
 *
 * -*- mode: C; c-basic-indent: 4; tab-width: 8; indent-tabs-mode: nil -*-
 * vim: set cindent ts=8 sw=4 expandtab:
 *
 *  @brief Contains the definition of string utility functions
 *
 *  String utility functions defined shall provide basic string copying
 *  capabilities while using the preferred memory management utilities.
 *
 *  @author Eric Van Alst, MHPCC
 *  @author David Robbins, MHPCC
 *
 *  @version $Revision: 1.33 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2006-06-30 02:20:06 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "psString.h"
#include "psMemory.h"
#include "psError.h"
#include "psAssert.h"

#include "psErrorText.h"

psString psStringCopy(const char *string)
{
    // Pass through NULL values!

    // Allocate memory using psAlloc function
    // Copy input string to memory just allocated
    // Return the copy
    return string ? strcpy(psAlloc(strlen(string) + 1), string) : NULL;
}

psString psStringNCopy(const char *string,
                       unsigned int nChar)
{
    PS_ASSERT_PTR_NON_NULL(string, NULL);
    char *returnValue = NULL;

    // Check the number of characters to copy is non-negative
    if (nChar == UINT_MAX) {
        // Log error message and return NULL
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psString_NCHAR_NEGATIVE,
                nChar);
        return NULL;
    }
    // Allocate memory using psAlloc function - nChar bytes
    // Copy input string to memory allocated up to nChar characters
    // Return the copy
    // Pass through NULL values
    returnValue =
              string ? strncpy(psAlloc((size_t) nChar + 1), string, (size_t) nChar)
              : NULL;

    // Ensure the last byte is NULL character
    returnValue[nChar] = '\0';

    // Return the string pointer
    return returnValue;
}

ssize_t psStringAppend(char **dest,
                       const char *format,
                       ...)
{
    PS_ASSERT_PTR_NON_NULL(format, 0);
    va_list         args;
    size_t          length;             // complete string length (sans \0)
    size_t          oldLength;          // original string length (sans \0)
    ssize_t         tailLength;         // length of string to append

    if (!dest || !format) {
        return 0;
    }

    if (!*dest) {
        *dest = psStringCopy("");
        oldLength = 0;
    } else {
        // size of existing string
        oldLength = strlen(*dest);
    }

    // find the size of the string to append
    va_start(args, format);
    // C99 guarentees vsnprintf() to work as expected with size = 0
    tailLength = vsnprintf(*dest, 0, format, args);
    va_end(args);

    // if the new tail is zero length, return the length of the old string.  if
    // it's a format error, return the error code.
    if (tailLength < 1) {
        return tailLength == 0 ? oldLength : tailLength;
    }

    // new string length (sans \0)
    length = oldLength + tailLength;

    // realloc string to string + tail + \0
    *dest = psRealloc(*dest, length + 1);

    // append tail + \0
    va_start(args, format);
    vsnprintf(*dest + oldLength, tailLength + 1, format, args);
    va_end(args);

    return length;
}

ssize_t psStringPrepend(char **dest,
                        const char *format,
                        ...)
{
    PS_ASSERT_PTR_NON_NULL(format, 0);
    va_list         args;
    size_t          length;             // complete string length (sans \0)
    ssize_t         headLength;         // length of string to prepend
    char            *oldDest;           // copy of original string

    if (!dest || !format) {
        return 0;
    }

    if (!*dest) {
        // makes the string backup and concatination pointless
        *dest = psStringCopy("");
        length = 0;
    } else {
        // size of existing string
        length = strlen(*dest);
    }

    // find the size of the string to prepend
    va_start(args, format);
    // C99 guarentees vsnprintf() to work as expected with size = 0
    headLength = vsnprintf(*dest, 0, format, args);
    va_end(args);

    // if the new head is zero length, return the length of the old string.  if
    // it's a format error, return the error code.
    if (headLength < 1) {
        return headLength == 0 ? length : headLength;
    }

    // backup original string
    oldDest = psStringCopy(*dest);

    // new string length (sans \0)
    length += headLength;

    // realloc string to head + string + \0
    *dest = psRealloc(*dest, length + 1);

    // copy the new head to the beginning of string
    va_start(args, format);
    vsnprintf(*dest, length + 1, format, args);
    va_end(args);

    // append the original string
    strncat(*dest, oldDest, length + 1);

    psFree(oldDest);

    return length;
}

// split the string by the given splitters
// NULL input string returns empty (not NULL) list
// NULL splitters is an error
psList *psStringSplit(const char *string,
                      const char *splitters,
                      bool multipleAreSignificant)
{
    PS_ASSERT_PTR_NON_NULL(splitters, NULL);

    psList *values = psListAlloc(NULL); // The list of values to return
    // An input NULL string should not generate an error: it is a valid case
    if (string == NULL) {
        return values;
    }

    char *next = NULL;
    char *current = (char *) string;
    while ((next = strpbrk (current, splitters)) != NULL) {

        // are multiple splitters in-a-row significant?
        if ((next == current) && !multipleAreSignificant) {
            current ++;
            continue;
        }

        // Copy the current word
        psString word = psStringNCopy(current, next - current);
        psListAdd(values, PS_LIST_TAIL, word);
        psFree(word);

        current = next + 1;
    }

    if (strlen(current) > 0) {
        // Copy the last word
        psString word = psStringCopy(current);
        (void)psListAdd(values, PS_LIST_TAIL, word);
        psFree(word);
    }

    return values;
}

// given the input string, search for all copies of the key, and replace with the replacement value
// the input string may be freed if not needed
char *psStringSubstitute(char *input, const char *replace, const char *key)
{
    if (key == NULL || strlen(key) == 0) {
        return input;
    }

    while (true) {
        char *p = strstr (input, key);
        if (!p) {
            return input;
        }

        // we have input = xxxkeyxxx
        // we want output = xxxreplacexxx

        // this is safe since we will subtract strlen(key) elements from input
        char *output = psAlloc(strlen(input) + strlen(replace) + 1);
        int Nc = p - input;

        // copy the first segement into 'output'
        strncpy(output, input, Nc);

        // copy the key replacement to the start of the key
        strcpy(&output[Nc], replace);
        Nc += strlen (replace);

        // copy the remainder to the end of the replacement
        strcpy(&output[Nc], p + strlen(key));

        psFree(input);
        input = output;
    }
    return input;
}

psArray *psStringSplitArray(const char *string, const char *splitters, bool multi)
{

    psList *list = psStringSplit(string, splitters, multi);
    psArray *array = psListToArray(list);
    psFree (list);
    return array;
}

#ifndef whitespace
#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
#endif

/* Strip whitespace from the start and end of STRING. */
size_t psStringStrip(char *string)
{
    long i;

    if (!string || strlen(string) == 0) {
        return 0;
    }

    for (i = 0; i < strlen(string) && whitespace(string[i]); i++)
        ; // No action
    if (i) {
        memmove (string, string + i, strlen(string+i)+1);
    }
    for (i = strlen (string) - 1; (i > 0) && whitespace(string[i]); i--)
        ; // No action
    string[++i] = 0;

    return i;
}
/*
 * Used by PS_FILE_LINE
 */
const char *p_psFileLine(const char *file, int line)
{
    static char msg[100];
    sprintf(msg, "%s:%d", file, line);
    return msg;
}
