/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** 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.
*/

#include "android/utils/stralloc.h"
#include "android/utils/debug.h"
#include "android/utils/misc.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

extern void
stralloc_tabular( stralloc_t*  out,
                  const char** strings, int  count,
                  const char*  prefix,  int  width )
{
    int  nrows, ncols, r, c, n, maxw = 0;

    for (n = 0; n < count; n++) {
        int  len = strlen(strings[n]);
        if (len > maxw)
            maxw = len;
    }
    maxw += 2;
    ncols = width/maxw;
    nrows = (count + ncols-1)/ncols;

    for (r = 0; r < nrows; r++) {
        stralloc_add_str( out, prefix );
        for (c = 0; c < ncols; c++) {
            int  index = c*nrows + r;
            if (index >= count) {
                break;
            }
            stralloc_add_format( out, "%-*s", maxw, strings[index] );
        }
        stralloc_add_str( out, "\n" );
    }
}

/** DYNAMIC STRINGS
 **/

extern void
stralloc_reset( stralloc_t*  s )
{
    free(s->s);
    s->s = NULL;
    s->n = 0;
    s->a = 0;
}

extern void
stralloc_ready( stralloc_t*  s, unsigned int  len )
{
    unsigned  old_max = s->a;
    unsigned  new_max = old_max;

    while (new_max < len) {
        unsigned  new_max2 = new_max + (new_max >> 1) + 16;
        if (new_max2 < new_max)
            new_max2 = UINT_MAX;
        new_max = new_max2;
    }

    s->s = realloc( s->s, new_max );
    if (s->s == NULL) {
        derror( "%s: not enough memory to reallocate %ld bytes",
                __FUNCTION__, new_max );
        exit(1);
    }
    s->a = new_max;
}

extern void
stralloc_readyplus( stralloc_t*  s, unsigned int  len )
{
    unsigned  len2 = s->n + len;

    if (len2 < s->n) { /* overflow ? */
        derror("%s: trying to grow by too many bytes: %ld",
               __FUNCTION__, len);
        exit(1);
    }
    stralloc_ready( s, len2 );
}

extern void
stralloc_copy( stralloc_t*  s, stralloc_t*  from )
{
    stralloc_ready(s, from->n);
    memcpy( s->s, from->s, from->n );
    s->n = from->n;
}

extern void
stralloc_append( stralloc_t*  s, stralloc_t*  from )
{
    stralloc_readyplus( s, from->n );
    memcpy( s->s + s->n, from->s, from->n );
    s->n += from->n;
}

extern void
stralloc_add_c( stralloc_t*  s, int  c )
{
    stralloc_add_bytes( s, (char*)&c, 1 );
}

extern void
stralloc_add_str( stralloc_t*  s, const char*  str )
{
    stralloc_add_bytes( s, str, strlen(str) );
}

extern void
stralloc_add_bytes( stralloc_t*  s, const void*  from, unsigned len )
{
    stralloc_readyplus( s, len );
    memcpy( s->s + s->n, from, len );
    s->n += len;
}

extern char*
stralloc_cstr( stralloc_t*  s )
{
    stralloc_readyplus( s, 1 );
    s->s[s->n] = 0;
    return s->s;
}

void
stralloc_lstrip( stralloc_t*  s )
{
    int  count;

    for (count = 0; count < s->n; count++) {
        if (s->s[count] != ' ' && s->s[count] != '\t')
            break;
    }

    if (count > 0) {
        memmove(s->s, s->s + count, s->n - count);
        s->n -= count;
    }
}

void
stralloc_rstrip( stralloc_t*  s )
{
    int  count = s->n;

    while (count > 0 && (s->s[count-1] == ' ' || s->s[count-1] == '\t'))
        count--;

    s->n = count;
}

void
stralloc_strip( stralloc_t* s )
{
    stralloc_rstrip(s);
    stralloc_lstrip(s);
}

extern char*
stralloc_to_tempstr( stralloc_t*  s )
{
    char*  q = tempstr_get( s->n );

    memcpy( q, s->s, s->n );
    q[s->n] = 0;
    return q;
}

extern void
stralloc_formatv( stralloc_t*  s, const char*  fmt, va_list  args )
{
    stralloc_reset(s);
    stralloc_ready(s,10);

    while (1) {
        int      n;
        va_list  args2;

        va_copy(args2, args);
        n = vsnprintf( s->s, s->a, fmt, args2 );
        va_end(args2);

        /* funky old C libraries returns -1 when truncation occurs */
        if (n > -1 && n < s->a) {
            s->n = n;
            break;
        }
        if (n > -1) {  /* we now precisely what we need */
            stralloc_ready( s, n+1 );
        } else {
            stralloc_ready( s, s->a*2 );
        }
    }
}


extern void
stralloc_format( stralloc_t*  s, const char*  fmt, ... )
{
    va_list  args;
    va_start(args, fmt);
    stralloc_formatv(s, fmt, args);
    va_end(args);
}

extern void
stralloc_add_formatv( stralloc_t*  s, const char*  fmt, va_list  args )
{
    STRALLOC_DEFINE(s2);
    stralloc_formatv(s2, fmt, args);
    stralloc_append( s, s2 );
    stralloc_reset( s2 );
}

extern void
stralloc_add_format( stralloc_t*  s, const char*  fmt, ... )
{
    va_list  args;
    va_start(args, fmt);
    stralloc_add_formatv( s, fmt, args );
    va_end(args);
}

extern void
stralloc_add_quote_c( stralloc_t*  s, int  c )
{
    stralloc_add_quote_bytes( s, (char*)&c, 1 );
}

extern void
stralloc_add_quote_str( stralloc_t*  s, const char*  str )
{
    stralloc_add_quote_bytes( s, str, strlen(str) );
}

extern void
stralloc_add_quote_bytes( stralloc_t*  s, const void*  from, unsigned  len )
{
    uint8_t*   p   = (uint8_t*) from;
    uint8_t*   end = p + len;

    for ( ; p < end; p++ ) {
        int  c = p[0];

        if (c == '\\') {
            stralloc_add_str( s, "\\\\" );
        } else if (c >= ' ' && c < 128) {
            stralloc_add_c( s, c );
        } else if (c == '\n') {
            stralloc_add_str( s, "\\n" );
        } else if (c == '\t') {
            stralloc_add_str( s, "\\t" );
        } else if (c == '\r') {
            stralloc_add_str( s, "\\r" );
        } else {
            stralloc_add_format( s, "\\x%02x", c );
        }
    }
}

extern void
stralloc_add_hex( stralloc_t*  s, unsigned  value, int  num_digits )
{
    const char   hexdigits[16] = "0123456789abcdef";
    int          nn;

    if (num_digits <= 0)
        return;

    stralloc_readyplus(s, num_digits);
    for (nn = num_digits-1; nn >= 0; nn--) {
        s->s[s->n+nn] = hexdigits[value & 15];
        value >>= 4;
    }
    s->n += num_digits;
}

extern void
stralloc_add_hexdump( stralloc_t*  s, void*  base, int  size, const char*  prefix )
{
    uint8_t*   p          = (uint8_t*)base;
    const int  max_count  = 16;
    int        prefix_len = strlen(prefix);

    while (size > 0) {
        int          count = size > max_count ? max_count : size;
        int          count2;
        int          n;

        stralloc_add_bytes( s, prefix, prefix_len );
        stralloc_add_hex( s, p[0], 2 );

        for (n = 1; n < count; n++) {
            stralloc_add_c( s, ' ' );
            stralloc_add_hex( s, p[n], 2 );
        }

        count2 = 4 + 3*(max_count - count);
        stralloc_readyplus( s, count2 );
        memset( s->s + s->n, ' ', count2 );
        s->n += count2;

        stralloc_readyplus(s, count+1);
        for (n = 0; n < count; n++) {
            int  c = p[n];

            if (c < 32 || c > 127)
                c = '.';

            s->s[s->n++] = c;
        }
        s->s[s->n++] = '\n';

        size -= count;
        p    += count;
    }
}