/********************************************************************//**
 * @file	s_get_dyn.c
 * @brief	Read a c-string of unknown length from stdin.
 * @author 	migf1 <mig_f1@hotmail.com>
 * @bug		No known bugs
 ************************************************************************
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

/*********************************************************//**
 * @par Prototype
 * 	char *s_get_dyn( int len );
 *
 * @brief		Read a c-string of unknown length form stdin.
 * @param[in] len	(int) Initial length to be allocated for the c-string,
 * 			in characters, excluding the nil-terminator (see notes).
 * @return		The read c-string, or NULL on error. The c-string
 *			returned on success MUST be explicitly freed before
 * 			program termination.
 * @note		The length of the c-string is dynamically doubled every
 *			time it is exhausted. Those reallocations automatically
 *			make room for one extra character: the nil-terminator.
 *			At the end, and only if it is needed, the length of the
 *			created c-string is truncated to the exact number of
 * 			characters read, plus 1 for the nil-terminator.
 *************************************************************
 */
char *s_get_dyn( int len )
{
	int i = 0;					/* counter of stdin chars     */
	char *s = NULL, *try = NULL;			/* try: for testing realloc() */

	/* demand a valid initial length, along with its successful memory allocation */
	if ( len < 1 || NULL == (s = calloc( len+1, sizeof(char)) ) )
		goto ret_failure;

	/* keep reading chars from stdin until either '\n' or EOF is typed */
	for (i=0; EOF != (s[i] = getchar()) && s[i] != '\n'; i++)
	{
		if ( i != 0 && i % len == 0 )      	/* reallocation is needed     */
		{
			len += len;			/* ... increase len by itself */
			try = realloc(s, (len+1) * sizeof(char) );
			if ( !try )			/* ... reallocation failed    */
				goto ret_failure;	/* ... ... cleanup & return   */
			s = try;			/* ... realloc OK, accept it  */
		}
	}
	s[i] = '\0';					/* nil-termnate s             */

	/* if needed, truncate s to its exact length, plus the nil-terminator */
	if ( i == len )
		return s;
	if ( NULL == (try = realloc(s, (i+1) * sizeof(char) )) )
		goto ret_failure;

	return try;

ret_failure:
	/* cleanup & return failure */
	if ( s )
		free(s);
	return NULL;
}

/*********************************************************//**
 * @par Prototype
 * 	int debug_info( const char *fmttext, ... );
 *
 * @brief 		A vfpintf() wrapper for outputing debugging info on stderr.
 * @param[in] fmttext	(const char *) A c-string holding the formatting template.
 * @param ...		A list of arguments, as specified in the formatting template.
 * @return		0 (FALSE) on error, 1 (TRUE) otherwise.
 *************************************************************
 */
int debug_info( const char *fmttext, ... )
{
	va_list	args;

	if ( !fmttext )
		return 0;	/* FALSE */

	va_start( args, fmttext );
	if ( vfprintf( stderr, fmttext, args ) < 0 ) {
		fputs("*** vfprintf() failed!\n", stderr);
		va_end( args );
		return 0;	/* FALSE */
	}
	va_end( args );

	return 1;		/* TRUE */
}

/*********************************************************//**
 * @par Prototype
 * 	char *s_get_dyn_debug( const int len );
 *
 * @brief 	Same as s_get_dyn() but it ouputs debugging information on stderr.
 *************************************************************
 */
char *s_get_dyn_debug( int len )
{
	int i = 0;					/* char counter               */
	char *s = NULL, *try = NULL;			/* try: for testing realloc() */

	debug_info( "--- DBG: memory chunk = %d\n", len );

	/* demand valid len and a successful allocation of memory */
	if ( len < 1 || NULL == (s = calloc( len+1, sizeof(char)) ) )
		return NULL;

	debug_info( "--- DBG: s allocated successfully to %d chars\n", len+1 );

	for (i=0; EOF != (s[i] = getchar()) && s[i] != '\n'; i++)
	{
		if ( i != 0 && i % len == 0 )      /* needs realloc     */
		{
			len += len;		/* increase len */
			try = realloc(s, (len+1) * sizeof(char) );
			if ( !try ) {            	/* realloc failed    */
				free(s);
				return NULL;
			}
			debug_info(
				"--- DBG: s reallocated successfully to %d chars\n",
				len+1
			);
			s = try;			/* accept realloc   */
		}
	}
	s[i] = '\0';

	if ( i == len ) {
		debug_info( "%s\n", "--- DBG: no truncation is needed!" );
		return s;
	}

	debug_info(
		"--- DBG: before truncation: strlen = %u chars, maxlen = %d (including '\\0')\n",
		(unsigned)strlen(s), len+1
	);
	if ( NULL == (try = realloc(s, (i+1) * sizeof(char) )) ) {
		debug_info( "%s\n", "--- DBG: realloc() error, no truncation!\n" );
		free(s);
		return NULL;
	}

	debug_info(
		"--- DBG: after  truncation: strlen = %u, maxlen = %d (including '\\0')\n",
		(unsigned)strlen(s), i+1
	);

	return try;
}

/*********************************************************//**
 *
 *************************************************************
 */
int main( void )
{
	char *s = NULL;

	printf("Type a string of any length: ");
	if ( NULL == (s = s_get_dyn_debug(32)) ) {
		fputs("*** possibly out of memory, aborting program...\n", stderr);
		goto exit_failure;
	}

	printf( "s: %s\n", s );
	free( s );

	exit( EXIT_SUCCESS );

exit_failure:
	if ( s )
		free(s);
	exit( EXIT_FAILURE );
}

