/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#define __STRINGLIB_INTERNAL__

#include "cpr_types.h"
#include "cpr_stdlib.h"
#include "cpr_stdio.h"
#include "cpr_string.h"
#include "cpr_stddef.h"
#include "cpr_locks.h"
#include "string_lib.h"
#include "phone_debug.h"
#include "debug.h"


#define STRING_SIGNATURE (('S'<<8)|'T')
#define STR_TO_STRUCT(str) ((string_block_t *)((str) - (offsetof(string_block_t,data))))
#define STRUCT_TO_STR(sbt) ((const char *) (sbt)->data)

static string_t empty_str;
static int strlib_is_string(string_t str);

/*
 *  Function: strlib_malloc
 *
 *  PARAMETERS:const char* : string which is to be malloc'ed
               int         : length of string or -1
 *
 *  DESCRIPTION:strlib_malloc : creates a new string and returns a const char*
 *  to the new string. Size of String is equal to length specified or actual
 *  length of the string when length is specified as -1(LEN_UNKNOWN)
 *
 *  RETURNS: Pointer to malloc'ed string
 *
 */
string_t
strlib_malloc (const char *str, int length, const char *fname, int line)
{
    string_block_t *temp;
    int size;

    // if specified length is unknown or invalid... then calculate it
    // Length < 0 is not expected, but since length is an int, it could
    // theoritically be negative.  [ This check accounts for that, and
    // avoids a static analysis warning related to same ]
    if ((length == LEN_UNKNOWN) || (length < 0)) {
        length = strlen(str);
    }

    size = sizeof(string_block_t) + length + 1;
    temp = (string_block_t *) cpr_malloc(size);

    if (!temp) {
        err_msg("Error: Strlib_Malloc() Failed. Requested Size = %d\n", size);
        return (string_t) 0;
    }

    temp->refcount  = 1;
    temp->length    = (uint16_t) length;
    temp->signature = STRING_SIGNATURE;
    temp->fname     = fname;
    temp->line      = line;
    /* There used to be memcpy here which will walk off the end of */
    /* str pointer which is a bad thing to do */
    sstrncpy(temp->data, str, length + 1);
    temp->data[length] = '\0';

    return STRUCT_TO_STR(temp);
}


/*
 *  Function: strlib_copy
 *
 *  PARAMETERS:string_t : string whose ref count is to be incremented
 *
 *  DESCRIPTION:just increments the reference count of the string.
 *  It is not like conventional strcpy as far as implementation
 *  is concerned, but effectivly when user calls strlib_copy he gets
 *  private copy which cannot be modified by any other function.
 *  String should be modified only by calling strlib_open
 *  which checks for the refcount and if its more than 1 allocates new string.
 *
 *  RETURNS: string_t: string whose ref count is  incremented
 *
 */
string_t
strlib_copy (string_t str)
{
    string_block_t *temp;

    if (!strlib_is_string(str)) {
        return (NULL);
    }

    temp = STR_TO_STRUCT(str);

    /*
     * Refcount is limited to USHRT_MAX since refcount is
     * of type uint16_t.
     */
    if ((temp->refcount < 0xffff) && (str != empty_str)) {
        temp->refcount++;
    }

    return STRUCT_TO_STR(temp);
}


/*
 *  Function: strlib_update
 *
 *  PARAMETERS:string_t    : destination string
 *             const char* : source string
 *
 *  DESCRIPTION:like strcpy but returns const char* to a string in pool
 *
 *  RETURNS: string_t:Pointer to a new string
 *
 */
string_t
strlib_update (string_t destination, const char *source,
              const char *calling_fname, int line)
{
    const char *fname = "strlib_udpate";
    string_t ret_str;

    /* Bogus destination */
    if (!destination) {
        /* Should never happen, so report it */
        err_msg("%s: Destination String is invalid: %s:%d", fname,
                calling_fname, line);
        /* bad input, bad output */
        return NULL;
    }

    /* Bogus source */
    if (!source) {
        /* Should never happen, so report it and return something */
        err_msg("%s: Source String is invalid: %s:%d", fname,
                calling_fname, line);
        strlib_free(destination);
        return strlib_empty();
    }

    if (source[0] == '\0') {
        /* Source is NULL string, use string empty */
        strlib_free(destination);
        return strlib_empty();
    }

    ret_str = strlib_malloc(source, LEN_UNKNOWN, calling_fname, line);

    if (!ret_str) {
        /*
         * If a malloc error occurred, give them back what they had.
         * It's not right, but it's better than nothing.
         */
        ret_str = destination;
    } else {
        strlib_free(destination);
    }

    return (ret_str);
}


/*
 *  Function: strlib_append
 *
 *  PARAMETERS: string_t    : oringinal string
 *              const char* : to be appended string
 *
 *  DESCRIPTION:this function will append a string to the original string,
 *  makes a duplicate of the string and returns it.
 *
 *  RETURNS: Appended string (string_t) or NULL if failure
 *
 */
string_t
strlib_append (string_t str, const char *toappend_str,
              const char *fname, int line)
{
    int curlen;
    char *buf;

    /* Should never happen */
    if (!str) {
        return NULL;
    }

    curlen = strlen(str);
    /* not really an open, rather more like modify */
    buf = strlib_open(str, curlen + strlen(toappend_str) + 1, fname, line);
    /*
     * Validate returned value, but buf could be equal to str (buf == str)
     * so there still may be issues.
     */
    if (buf) {
        strcpy(buf + curlen, toappend_str);
        return buf;
    }
    return NULL;
}


/*
 *  Function: strlib_free
 *
 *  PARAMETERS: string_t : string to which reference is to be removed
 *
 *  DESCRIPTION:It will remove the node containing the string from the linked
 *  list if its refcount is 0 else it will decrement the refcount.
 *
 *  RETURNS: none
 *
 */
void
strlib_free (string_t str)
{
    string_block_t *temp;

    if ((!strlib_is_string(str)) || (str == empty_str)) {
        return;
    }

    temp = STR_TO_STRUCT(str);
    temp->refcount--;
    if (temp->refcount == 0) {
        temp->signature = 0;
        cpr_free(temp);
    }

    return;
}


/*
 *  Function: strlib_open
 *
 *  PARAMETERS: string_t : string to be modified
 *              int      : length of string to be modified
 *
 *  DESCRIPTION:User will call open when he wants to modify the string. If
 *  length of string after modification is going to be = < original string
 *  return to him pointer to original string, else a new string is malloced
 *  for user and a pointer to it is send to him.
 *
 *  RETURNS: char* to modifiable string
 *
 */
char *
strlib_open (string_t str, int length, const char *fname, int line)
{
    char *ret_str;
    string_block_t *temp;

    if (!strlib_is_string(str)) {
        return (NULL);
    }

    temp = STR_TO_STRUCT(str);

    if ((temp->refcount == 1) && (length <= temp->length)) {
        ret_str = (char *) str;
    } else {
        ret_str = (char *) strlib_malloc(str, length, fname, line);
        if (!ret_str) {
            /*
             * If a malloc error occurred, give them back what they had.
             * It's not right, but it's better than nothing.
             */
            ret_str = (char *) str;
        } else {
            strlib_free(str);
        }
    }

    return (ret_str);
}


/*
 *  Function: strlib_close
 *
 *  PARAMETERS:char* : string to be made unmodifiable
 *
 *  DESCRIPTION:Just returns const char* for the string, so that user
 *  does not change it by mistake
 *
 *  RETURNS:string_t: pointer to same string
 *
 */
string_t
strlib_close (char *str)
{
    if (!strlib_is_string(str)) {
        return (NULL);
    }
    return (str);
}


/*
 *  Function: strlib_is_string
 *
 *  PARAMETERS: string_t : string which is to be validated
 *
 *  DESCRIPTION:Checks whether the signature for the provided string is valid
 *
 *  RETURNS: static int
 *
 */
int
strlib_is_string (string_t str)
{
    string_block_t *temp;

    if (str == NULL) {
        err_msg("Strlib Error: strlib_is_tring passed invalid string\n");
        return (0);
    }

    temp = STR_TO_STRUCT(str);

    if (temp->signature == STRING_SIGNATURE) {
        return (1);
    } else {
        err_msg("Strlib Error: strlib_is_tring passed invalid string\n");
        return (0);
    }

}

int
strlib_test_memory_is_string (void *mem)
{
    string_block_t *temp;

    if (!mem) {
        return FALSE;
    }

    temp = (string_block_t *) mem;
    if (temp->signature == STRING_SIGNATURE) {
        return TRUE;
    }
    return FALSE;
}


/*
 *  Function: strlib_empty
 *
 *  PARAMETERS: None
 *
 *  DESCRIPTION:strlib_empty will be called by user when he wants
 *  to initialize his string to '/0' or "". It is not same as initializing
 *  it to NULL. if(str) will evaluate to true if str = strlib_empty().
 *  Correct way to check for empty string will be to do if (str[0] == '\0') or
 *  if (str[0] == "")
 *
 *  RETURNS: Pointer to empty_str
 */
string_t
strlib_empty (void)
{
    string_block_t *temp;
    static boolean empty_str_init = FALSE;

    if (empty_str_init == FALSE) {
        empty_str = strlib_malloc("", LEN_UNKNOWN, __FILE__, __LINE__);
        temp = STR_TO_STRUCT(empty_str);
        temp->refcount = 0xffff;
        empty_str_init = TRUE;
    }
    return (empty_str);
}

void
strlib_init (void)
{
  (void) strlib_empty(); // force it to allocate the empty string buffer
}
