/* str - simple counted-string package
**
** Copyright (C) 2022 by Jef Poskanzer <jef@mail.acme.com>.
** All rights reserved.
**
** You may use this software however you like as long as you keep my
** name on it and don't sue me.
**
** For commentary on this license please see http://acme.com/license.html
*/

#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>

#include "str.h"
#include "leakfinder.h"


#define INITIAL_SIZE 8


/* Private routines. */


/* Check if there's enough room for more bytes, and if not then enlarge. */
static bool check_size( str* s, size_t newlen )
    {
    size_t newsize;
    char* newbytes;

    if ( newlen <= s->size )
	return true;
    /* Got to enlarge the byte array. */
    newsize = newlen * 2;
    newbytes = (char*) REALLOC( (void*) s->bytes, newsize );
    if ( newbytes == (char*) 0 )
	return false;
    s->bytes = newbytes;
    s->size = newsize;
    return true;
    }


/* Public routines. */


str* str_create( void )
    {
    str* s;

    s = (str*) MALLOC( sizeof(str) );
    if ( s == (str*) 0 )
	return (str*) 0;
    s->size = INITIAL_SIZE;
    s->bytes = (char*) MALLOC( s->size );
    if ( s->bytes == (char*) 0 )
	{
	FREE( s );
	return (str*) 0;
	}
    str_clear( s );
    return s;
    }


void str_clear( str* s )
    {
    s->len = 0;
    }


size_t str_len( str* s )
    {
    return s->len;
    }


void str_destroy( str* s )
    {
    FREE( s->bytes );
    FREE( s );
    }


bool str_copy( str* tos, char* frombytes, size_t len )
    {
    str_clear( tos );
    return str_append( tos, frombytes, len );
    }


bool str_append( str* tos, char* frombytes, size_t len )
    {
    if ( ! check_size( tos, tos->len + len ) )
	return false;
    memmove( &tos->bytes[tos->len], frombytes, len );
    tos->len += len;
    return true;
    }


bool str_append_char( str* tos, char fromc )
    {
    if ( ! check_size( tos, tos->len + 1 ) )
	return false;
    tos->bytes[tos->len] = fromc;
    ++tos->len;
    return true;
    }


bool str_prepend( str* tos, char* frombytes, size_t len )
    {
    if ( ! check_size( tos, tos->len + len ) )
	return false;
    memmove( &tos->bytes[len], tos->bytes, tos->len );
    memmove( tos->bytes, frombytes, len );
    tos->len += len;
    return true;
    }


bool str_prepend_char( str* tos, char fromc )
    {
    if ( ! check_size( tos, tos->len + 1 ) )
	return false;
    memmove( &tos->bytes[1], tos->bytes, tos->len );
    tos->bytes[0] = fromc;
    ++tos->len;
    return true;
    }



/* String ring. */

struct str_ring_entry
    {
    str* b;
    TAILQ_ENTRY( str_ring_entry ) entries;
    };


str_ring* str_ring_create( void )
    {
    str_ring* sr = MALLOC( sizeof(str_ring) );
    if ( sr == (str_ring*) 0 )
	return (str_ring*) 0;
    TAILQ_INIT( sr );
    return sr;
    }


bool str_ring_empty( str_ring* sr )
    {
    return TAILQ_EMPTY( sr );
    }


bool str_ring_addfirst( str_ring* sr, str* b )
    {
    struct str_ring_entry* e = MALLOC( sizeof(struct str_ring_entry) );
    if ( e == (struct str_ring_entry*) 0 )
	return false;
    e->b = b;
    TAILQ_INSERT_HEAD( sr, e, entries );
    return true;
    }


bool str_ring_addlast( str_ring* sr, str* b )
    {
    struct str_ring_entry* e = MALLOC( sizeof(struct str_ring_entry) );
    if ( e == (struct str_ring_entry*) 0 )
	return false;
    e->b = b;
    TAILQ_INSERT_TAIL( sr, e, entries );
    return true;
    }


str* str_ring_removefirst( str_ring* sr )
    {
    struct str_ring_entry* e;
    str* b;

    e = TAILQ_FIRST( sr );
    TAILQ_REMOVE( sr, e, entries );
    b = e->b;
    FREE( (void*) e );
    return b;
    }


str* str_ring_removelast( str_ring* sr )
    {
    struct str_ring_entry* e;
    str* b;

    e = TAILQ_LAST( sr, str_ring_head );
    TAILQ_REMOVE( sr, e, entries );
    b = e->b;
    FREE( (void*) e );
    return b;
    }


str* str_ring_first( str_ring* sr )
    {
    struct str_ring_entry* e = TAILQ_FIRST( sr );

    return e->b;
    }


void str_ring_destroy( str_ring* sr )
    {
    str* b;

    while ( ! str_ring_empty( sr ) )
	{
	b = str_ring_removefirst( sr );
	str_destroy( b );
	}
    FREE( (void*) sr );
    }
