
/** @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.46 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2006-11-29 21:33:09 $
 *
 *  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 "psAbort.h"



static void stringFree(psString string)
{
    // There is non dynamic allocated item
}

psString psStringAlloc(long nChar)
{
    if (nChar < 1) {
        return NULL;
    }
    psString string = psAlloc(nChar + 1);
    psMemSetDeallocator(string, (psFreeFunc)stringFree);
    return string;
}

bool psMemCheckString(psPtr ptr)
{
    PS_ASSERT_PTR(ptr, false);
    return ( psMemGetDeallocator(ptr) == (psFreeFunc)stringFree );
}

psString psStringCopy(const char *string)
{
    // Pass through NULL values!
    if (!string) {
        return NULL;
    }
    psString output = psStringAlloc(strlen(string) + 1); // Output string
    // Copy input string to memory just allocated
    return strcpy(output, string);
}

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

    // Pass through NULL values
    if (!string) {
        return 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,
                _("Can not copy a negative number of characters (%d)."),
                nChar);
        return NULL;
    }

    // Copy input string to memory allocated up to nChar characters
    psString output = psStringAlloc((size_t) nChar + 1);
    output = strncpy(output, string, (size_t) nChar);

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

    // Return the string pointer
    return output;
}

ssize_t psStringAppend(char **dest,
                       const char *format,
                       ...)
{
    PS_ASSERT_PTR_NON_NULL(dest, 0);
    PS_ASSERT_PTR_NON_NULL(format, 0);

    va_list ap;
    va_start(ap, format);
    ssize_t length = psStringAppendV(dest, format, ap);
    va_end(ap);

    return length;
}

ssize_t psStringAppendV(char **dest,
                        const char *format,
                        va_list ap)
{
    PS_ASSERT_PTR_NON_NULL(dest, 0);
    PS_ASSERT_PTR_NON_NULL(format, 0);

    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) {
        oldLength = strlen(*dest);
    } else {
        oldLength = 0;
    }

    va_list         apCopy;

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

    // 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;
    }

    // realloc string to string + tail + \0
    if (*dest) {
        *dest = psRealloc(*dest, oldLength + tailLength + 1);
    } else {
        *dest = psStringAlloc(oldLength + tailLength + 1);
    }

    // append tail + \0
    // XXX this second call to va_copy() isn't strictly nessicary as the
    // calling function can't assume we won't modify the va_list.  However, we
    // have decided to error on the side of caution.
    va_copy(apCopy, ap);
    vsnprintf(*dest + oldLength, tailLength + 1, format, apCopy);
    va_end(apCopy);

    return length;
}

ssize_t psStringPrepend(char **dest,
                        const char *format,
                        ...)
{
    PS_ASSERT_PTR_NON_NULL(dest, 0);
    PS_ASSERT_PTR_NON_NULL(format, 0);

    va_list ap;
    va_start(ap, format);
    ssize_t length = psStringPrependV(dest, format, ap);
    va_end(ap);

    return length;
}

ssize_t psStringPrependV(char **dest,
                         const char *format,
                         va_list ap)
{
    PS_ASSERT_PTR_NON_NULL(dest, 0);
    PS_ASSERT_PTR_NON_NULL(format, 0);

    size_t          length;             // complete string length (sans \0)
    ssize_t         headLength;         // length of string to prepend
    char            *oldDest;           // copy of original string

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

    va_list apCopy;

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

    // 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
    // XXX this second call to va_copy() isn't strictly nessicary as the
    // calling function can't assume we won't modify the va_list.  However, we
    // have decided to error on the side of caution.
    va_copy(apCopy, ap);
    vsnprintf(*dest, length + 1, format, apCopy);
    va_end(apCopy);

    // 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
psString psStringSubstitute(psString input,
                            const char *replace,
                            const char *key)
{
    // Empty input gives empty output
    if (!input || strlen(input) == 0) {
        return input;
    }
    // No key gives the same as the input
    if (!key || strlen(key) == 0) {
        return input;
    }
    if (!psMemCheckString(input)) {
        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "This function requires a psString as input.\n");
        return NULL;
    }


    // replace == NULL is valid: it just means that we strip out the key
    // without putting anything else in its place
    size_t replaceLength;               // Size of "replace" string
    if (replace) {
        replaceLength = strlen(replace);
    } else {
        replaceLength = 0;
    }

    char *search = input;               // Search this string
    while (true) {
        char *found = strstr(search, key); // Found occurence of the key
        if (!found) {
            return input;
        }

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

        // this is safe since we will subtract strlen(key) elements from input
        psString output = psStringAlloc(strlen(input) + replaceLength + 1); // Output string
        int numChar = found - input;    // Number of characters to copy

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

        // copy the key replacement to the start of the key
        if (replace && replaceLength > 0) {
            strcpy(output + numChar, replace);
            numChar += replaceLength;
        }

        // copy the remainder to the end of the replacement
        strcpy(output + numChar, found + strlen(key));

        psFree(input);
        input = output;
        search = output + numChar;
    }

    psAbort(__func__, "Should never get here.\n");
    return NULL;
}

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;
}


psString psStringStripCVS(const char *string, const char *tagName)
{
    PS_ASSERT_STRING_NON_EMPTY(string, NULL);
    PS_ASSERT_STRING_NON_EMPTY(tagName, NULL);

    psString tagString = NULL;          // Tag string, e.g., "$Name:" from "Name"
    psStringAppend(&tagString, "$%s:", tagName);
    char *p = strstr(string, tagString); // The start of the real tag
    psFree(tagString);
    if (!p) {
        return psStringCopy("UNKNOWN");
    }

    psString tag = psStringCopy(string + 6); // The tag, without the leading tagString
    p = strrchr(tag, '$');              // The closing dollar sign
    if (p) {
        *p = 0;
    }
    psStringStrip(tag);
    if (*tag == 0) {
        psFree(tag);
        return psStringCopy("UNKNOWN");
    }

    return tag;
}

