//
// tardy - a tar post-processor
// Copyright (C) 1991-1999, 2001, 2002, 2008, 2009, 2011 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
//
//
// A literal pool is maintained.  Each string has a reference count.  The
// string stays in the literal pool for as long as it has a positive
// reference count.  To determine if a string is already in the literal pool,
// linear dynamic hashing is used to guarantee an O(1) search.  Making all equal
// strings the same item in the literal pool means that string equality is
// a pointer test, and thus very fast.
//

#include <libtardy/ac/assert.h>
#include <libtardy/ac/ctype.h>
#include <libtardy/ac/stddef.h>
#include <libtardy/ac/stdio.h>
#include <libtardy/ac/stdlib.h>
#include <libtardy/ac/string.h>
#include <libtardy/ac/stdarg.h>
#include <libexplain/malloc.h>
#include <libexplain/output.h>

#include <libtardy/rcstring.h>


//
// maximum conversion width for numbers
//
#define MAX_WIDTH 509

static  string_ty       **hash_table;
static  str_hash_ty     hash_modulus;
static  str_hash_ty     hash_cutover;
static  str_hash_ty     hash_cutover_mask;
static  str_hash_ty     hash_cutover_split_mask;
static  str_hash_ty     hash_split;
static  str_hash_ty     hash_load;
static  int         changed;

#define MAX_HASH_LEN 20


//
// NAME
//      hash_generate - hash string to number
//
// SYNOPSIS
//      str_hash_ty hash_generate(char *s, size_t n);
//
// DESCRIPTION
//      The hash_generate function is used to make a number from a string.
//
// RETURNS
//      str_hash_ty - the magic number
//
// CAVEAT
//      Only the last MAX_HASH_LEN characters are used.
//      It is important that str_hash_ty be unsigned (int or long).
//

static str_hash_ty
hash_generate(const char *s, size_t n)
{
    if (n > MAX_HASH_LEN)
    {
        s += n - MAX_HASH_LEN;
        n = MAX_HASH_LEN;
    }

    str_hash_ty retval = 0;
    while (n > 0)
    {
        unsigned char c = *s++;
        retval = (retval + (retval << 1)) ^ c;
        --n;
    }
    return retval;
}


//
// NAME
//      str_initialize - start up string table
//
// SYNOPSIS
//      void str_initialize(void);
//
// DESCRIPTION
//      The str_initialize function is used to create the hash table and
//      initialize it to empty.
//
// RETURNS
//      void
//
// CAVEAT
//      This function must be called before any other defined in this file.
//

void
str_initialize(void)
{
    hash_modulus = 1<<8; // MUST be a power of 2
    hash_cutover = hash_modulus;
    hash_split = hash_modulus - hash_cutover;
    hash_cutover_mask = hash_cutover - 1;
    hash_cutover_split_mask = (hash_cutover * 2) - 1;
    hash_load = 0;
    hash_table = new string_ty * [hash_modulus];
    for (str_hash_ty j = 0; j < hash_modulus; ++j)
        hash_table[j] = 0;
}


//
// NAME
//      split - reduce table loading
//
// SYNOPSIS
//      void split(void);
//
// DESCRIPTION
//      The split function is used to reduce the load factor on the hash table.
//
// RETURNS
//      void
//
// CAVEAT
//      A load factor of about 80% is suggested.
//

static void
split(void)
{
    //
    // get the list to be split across buckets
    //
    string_ty *p = hash_table[hash_split];
    hash_table[hash_split] = 0;

    //
    // increase the modulus by one order of 2-magnitude
    // sigma(2**-n) == 1
    //
    {
        size_t new_hash_modulus = hash_modulus * 2;
        string_ty **new_hash_table = new string_ty * [new_hash_modulus];
        for (size_t j = 0; j < hash_modulus; ++j)
            new_hash_table[j] = hash_table[j];
        for (size_t j = hash_modulus; j < new_hash_modulus; ++j)
            new_hash_table[j] = 0;
        delete [] hash_table;
        hash_table = new_hash_table;
        hash_modulus = new_hash_modulus;
    }
    hash_split = hash_modulus - hash_cutover;
    if (hash_split >= hash_cutover)
    {
        hash_cutover = hash_modulus;
        hash_split = 0;
        hash_cutover_mask = hash_cutover - 1;
        hash_cutover_split_mask = (hash_cutover * 2) - 1;
    }

    //
    // now redistribute the list elements
    //
    while (p)
    {
        string_ty *p2 = p;
        p = p->str_next;

        str_hash_ty idx = p2->str_hash & hash_cutover_mask;
        if (idx < hash_split)
            idx = p2->str_hash & hash_cutover_split_mask;
        p2->str_next = hash_table[idx];
        hash_table[idx] = p2;
    }
}


//
// NAME
//      str_from_c - make string from C string
//
// SYNOPSIS
//      string_ty *str_from_c(char*);
//
// DESCRIPTION
//      The str_from_c function is used to make a string from a null terminated
//      C string.
//
// RETURNS
//      string_ty* - a pointer to a string in dynamic memory.  Use str_free when
//      finished with.
//
// CAVEAT
//      The contents of the structure pointed to MUST NOT be altered.
//

string_ty *
str_from_c(const char *s)
{
    return str_n_from_c(s, strlen(s));
}


//
// NAME
//      str_n_from_c - make string
//
// SYNOPSIS
//      string_ty *str_n_from_c(char *s, size_t n);
//
// DESCRIPTION
//      The str_n_from_c function is used to make a string from an array of
//      characters.  No null terminator is assumed.
//
// RETURNS
//      string_ty* - a pointer to a string in dynamic memory.  Use str_free when
//      finished with.
//
// CAVEAT
//      The contents of the structure pointed to MUST NOT be altered.
//

string_ty *
str_n_from_c(const char *s, size_t length)
{
    if (!hash_table)
        str_initialize();
    str_hash_ty hash = hash_generate(s, length);

    str_hash_ty idx = hash & hash_cutover_mask;
    if (idx < hash_split)
        idx = hash & hash_cutover_split_mask;

    for (string_ty *p = hash_table[idx]; p; p = p->str_next)
    {
        if
        (
            p->str_hash == hash
        &&
            p->str_length == length
        &&
            !memcmp(p->str_text, s, length)
        )
        {
            p->str_references++;
            return p;
        }
    }

    string_ty *p =
        (string_ty *)explain_malloc_or_die(sizeof(string_ty) + length);
    p->str_hash = hash;
    p->str_length = length;
    p->str_references = 1;
    p->str_next = hash_table[idx];
    hash_table[idx] = p;
    memcpy(p->str_text, s, length);
    p->str_text[length] = 0;

    hash_load++;
    while (hash_load * 10 > hash_modulus * 8)
        split();
    ++changed;
    return p;
}


//
// NAME
//      str_copy - make a copy of a string
//
// SYNOPSIS
//      string_ty *str_copy(string_ty *s);
//
// DESCRIPTION
//      The str_copy function is used to make a copy of a string.
//
// RETURNS
//      string_ty* - a pointer to a string in dynamic memory.  Use str_free when
//      finished with.
//
// CAVEAT
//      The contents of the structure pointed to MUST NOT be altered.
//

string_ty *
str_copy(string_ty *s)
{
    assert(s->str_references > 0);
    s->str_references++;
    return s;
}


//
// NAME
//      str_free - release a string
//
// SYNOPSIS
//      void str_free(string_ty *s);
//
// DESCRIPTION
//      The str_free function is used to indicate that a string hash been
//      finished with.
//
// RETURNS
//      void
//
// CAVEAT
//      This is the only way to release strings DO NOT use the free function.
//

void
str_free(string_ty *s)
{
    if (!s)
        return;
    assert(s->str_references > 0);
    if (s->str_references > 1)
    {
        s->str_references--;
        return;
    }
    ++changed;

    //
    // find the hash bucket it was in,
    // and remove it
    //
    str_hash_ty idx = s->str_hash & hash_cutover_mask;
    if (idx < hash_split)
        idx = s->str_hash & hash_cutover_split_mask;
    for (string_ty **spp = &hash_table[idx]; *spp; spp = &(*spp)->str_next)
    {
        if (*spp == s)
        {
            *spp = s->str_next;
            free(s);
            --hash_load;
            return;
        }
    }

    //
    // should never reach here!
    //
    explain_output_error_and_die("attempted to free non-existent string (bug)");
}


//
// NAME
//      str_catenate - join two strings
//
// SYNOPSIS
//      string_ty *str_catenate(string_ty *, string_ty *);
//
// DESCRIPTION
//      The str_catenate function is used to concatenate two strings to form a
//      new string.
//
// RETURNS
//      string_ty* - a pointer to a string in dynamic memory.  Use str_free when
//      finished with.
//
// CAVEAT
//      The contents of the structure pointed to MUST NOT be altered.
//

string_ty *
str_catenate( string_ty *s1, string_ty *s2)
{
    static char     *tmp;
    static size_t   tmplen;
    size_t          length;

    length = s1->str_length + s2->str_length;
    if (tmplen < length)
    {
        delete [] tmp;
        tmplen = 16;
        while (tmplen < length)
            tmplen <<= 1;
        tmp = new char [tmplen];
    }

    memcpy(tmp, s1->str_text, s1->str_length);
    memcpy(tmp + s1->str_length, s2->str_text, s2->str_length);
    return str_n_from_c(tmp, length);
}


//
// NAME
//      str_cat_three - join three strings
//
// SYNOPSIS
//      string_ty *str_cat_three(string_ty *, string_ty *, string_ty *);
//
// DESCRIPTION
//      The str_cat_three function is used to concatenate three strings to form
//      a new string.
//
// RETURNS
//      string_ty* - a pointer to a string in dynamic memory.  Use str_free when
//      finished with.
//
// CAVEAT
//      The contents of the structure pointed to MUST NOT be altered.
//

string_ty *
str_cat_three(string_ty *s1, string_ty *s2, string_ty *s3)
{
    static char     *tmp;
    static size_t   tmplen;
    size_t          length;

    length = s1->str_length + s2->str_length + s3->str_length;
    if (tmplen < length)
    {
        delete [] tmp;
        tmplen = 16;
        while (tmplen < length)
            tmplen <<= 1;
        tmp = new char [tmplen];
    }

    memcpy(tmp, s1->str_text, s1->str_length);
    memcpy(tmp + s1->str_length, s2->str_text, s2->str_length);
    memcpy
    (
        tmp + s1->str_length + s2->str_length,
        s3->str_text,
        s3->str_length
    );
    return str_n_from_c(tmp, length);
}


//
// NAME
//      str_equal - test equality of strings
//
// SYNOPSIS
//      int str_equal(string_ty *, string_ty *);
//
// DESCRIPTION
//      The str_equal function is used to test if two strings are equal.
//
// RETURNS
//      int; zero if the strings are not equal, nonzero if the strings are
//      equal.
//
// CAVEAT
//      This function is implemented as a macro in strings.h
//

#if 0

// this one is inlined in common/str.h

int
str_equal(string_ty *s1, string_ty *s2)
{
    return (s1 == s2);
}

#endif


//
// NAME
//      str_upcase - upcase a string
//
// SYNOPSIS
//      string_ty *str_upcase(string_ty *);
//
// DESCRIPTION
//      The str_upcase function is used to form a string which is an upper case
//      form of the supplied string.
//
// RETURNS
//      string_ty* - a pointer to a string in dynamic memory.  Use str_free when
//      finished with.
//
// CAVEAT
//      The contents of the structure pointed to MUST NOT be altered.
//

string_ty *
str_upcase(string_ty *s)
{
    static char *tmp;
    static size_t tmplen;
    if (tmplen < s->str_length)
    {
        delete [] tmp;
        tmplen = 16;
        while (tmplen < s->str_length)
            tmplen <<= 1;
        tmp = new char [tmplen];
    }

    const char *cp1 = s->str_text;
    char *cp2 = tmp;
    for (;;)
    {
        unsigned char c = *cp1++;
        if (!c)
            break;
        if (c >= 'a' && c <= 'z')
            c += 'A' - 'a';
        *cp2++ = c;
    }
    return str_n_from_c(tmp, s->str_length);
}


//
// NAME
//      str_bool - get boolean value
//
// SYNOPSIS
//      int str_bool(string_ty *s);
//
// DESCRIPTION
//      The str_bool function is used to determine the boolean value of the
//      given string.  A "false" result is if the string is empty or
//      0 or blank, and "true" otherwise.
//
// RETURNS
//      int: zero to indicate a "false" result, nonzero to indicate a "true"
//      result.
//

int
str_bool(string_ty *s)
{
    const char *cp = s->str_text;
    while (*cp)
    {
        if (!isspace(*cp) && *cp != '0')
            return 1;
        ++cp;
    }
    return 0;
}


//
// NAME
//      str_field - extract a field from a string
//
// SYNOPSIS
//      string_ty *str_field(string_ty *, char separator, int field_number);
//
// DESCRIPTION
//      The str_field functipon is used to erxtract a field from a string.
//      Fields of the string are separated by ``separator'' characters.
//      Fields are numbered from 0.
//
// RETURNS
//      Asking for a field off the end of the string will result in a null
//      pointer return.  The null string is considered to have one empty field.
//

string_ty *
str_field(string_ty *s, int sep, int fldnum)
{
    const char *cp = s->str_text;
    while (fldnum > 0)
    {
        const char *ep = strchr(cp, sep);
        if (!ep)
            return 0;
        cp = ep + 1;
        --fldnum;
    }
    const char *ep = strchr(cp, sep);
    if (ep)
        return str_n_from_c(cp, ep - cp);
    return str_from_c(cp);
}


bool
operator < (const rcstring &lhs, const rcstring &rhs)
{
    return (strcmp(lhs.c_str(), rhs.c_str()) < 0);
}


bool
operator <= (const rcstring &lhs, const rcstring &rhs)
{
    return (strcmp(lhs.c_str(), rhs.c_str()) <= 0);
}


bool
operator > (const rcstring &lhs, const rcstring &rhs)
{
    return (strcmp(lhs.c_str(), rhs.c_str()) > 0);
}


bool
operator >= (const rcstring &lhs, const rcstring &rhs)
{
    return (strcmp(lhs.c_str(), rhs.c_str()) >= 0);
}
