//-----------------------------------------------------------------------------
// Copyright © 2003 - Philip Howard - All rights reserved
//
// 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 2
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//-----------------------------------------------------------------------------
// package	libh/string
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

#include "string_lib.h"

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	str_app_ul_text
//
// purpose	Append to the first string (given with maximum available space)
//		a string of text representing the unsigned long value.
//
//		Do append only if all the text can be appended.
//
// arguments	1 (char *) pointer to target string
//		2 (size_t) maximum space in target string
//		3 (unsigned long) the number to convert
//		4 (const char *) language id string, default "eng"
//
// returns	(size_t) new length of string, or ~0 for error
//
// note		Contents of available space after the termination character
//		are destroyed if the append fails due to insufficient space.
//		Nothing should be kept there, anyway.
//-----------------------------------------------------------------------------
size_t
str_app_ul_text (
    char *		arg_target
    ,
    size_t		arg_length
    ,
    unsigned long	arg_value
    ,
    const char *	arg_lang
    )
__PROTO_END__
{
    static const char *			nums_eng	[20] = {
        "zero",
        "one",
        "two",
        "three",
        "four",
        "five",
        "six",
        "seven",
        "eight",
        "nine",
        "ten",
        "eleven",
        "twelve",
        "thirteen",
        "fourteen",
        "fifteen",
        "sixteen",
        "seventeen",
        "eighteen",
        "nineteen"
    };

    static const char *			tens_eng	[8] = {
        "twenty",
        "thirty",
        "forty",
        "fifty",
        "sixty",
        "seventy",
        "eighty",
        "ninety"
    };

    static const unsigned long		power_eng	[] = {
#if ULONG_MAX > 4294967295UL
#if ULONG_MAX > 549755813888UL
#if ULONG_MAX > 562949953421312UL
#if ULONG_MAX > 576460752303423488UL
        1000000000000000000UL,
#endif
        1000000000000000UL,
#endif
        1000000000000UL,
#endif
#endif
        1000000000UL,
        1000000UL,
        1000UL,
        100UL,
    };

    static const char * const		names_eng	[] = {
#if ULONG_MAX > 4294967295UL
#if ULONG_MAX > 549755813888UL
#if ULONG_MAX > 562949953421312UL
#if ULONG_MAX > 576460752303423488UL
        " quintillion",
#endif
        " quadrillion",
#endif
        " trillion",
#endif
#endif
        " billion",
        " million",
        " thousand",
        " hundred",
        NULL
    };

    char *		reset_ptr	;
    unsigned long	value		;
    size_t		len		;
    size_t		total_len	;

    //-------------------------------------------------
    // Check language ID code string (SIL or ISO code).
    //-------------------------------------------------
    if ( arg_lang &&
	 ( tolower( arg_lang[0] ) != 'e' ||
	   tolower( arg_lang[1] ) != 'n' ||
	   tolower( arg_lang[2] ) != 'g' ||
	   tolower( arg_lang[3] ) != 'a' ||
	   arg_lang[4] != 0 ) ) {
	return ~0;
    }

    //-----------------------------------------
    // Find and save the current end of string.
    //-----------------------------------------
    reset_ptr = arg_target;
    while ( * reset_ptr ) ++ reset_ptr;
    total_len = reset_ptr - arg_target;

    //--------------------------
    // Get the value to convert.
    //--------------------------
    value = arg_value;

    //---------------------------------------
    // Handle high power numbers recursively.
    //---------------------------------------
    {
	const unsigned long *	power_ptr	;
	const char * const *	names_ptr	;

	power_ptr = power_eng;
	names_ptr = names_eng;
	while ( * names_ptr ) {
	    if ( value >= * power_ptr ) {
		len = str_app_ul_text( arg_target, arg_length, value / ( * power_ptr ), arg_lang );
		if ( len == ~0 ) goto reset_abort;
		total_len += len;

		len = str_app_str( arg_target, arg_length, * names_ptr );
		if ( len == ~0 ) goto reset_abort;
		total_len += len;

		value %= * power_ptr;
		if ( value == 0 ) return total_len;

		str_app_ch( arg_target, arg_length, ' ' );
	    }
	    ++ power_ptr;
	    ++ names_ptr;
	}
    }

    //-----------------------
    // Handle tens, 20 to 90.
    //-----------------------
    if ( value >= 20UL ) {
	len = str_app_str( arg_target, arg_length, tens_eng[ value / 10UL - 2 ] );
	if ( len == ~0 ) goto reset_abort;
	total_len += len;

	value %= 10UL;
	if ( value == 0 ) return total_len;

	str_app_ch( arg_target, arg_length, ' ' );
    }

    //-------------------------------
    // Handle final numbers, 0 to 19.
    //-------------------------------
    len = str_app_str( arg_target, arg_length, nums_eng[ value ] );
    if ( len == ~0 ) goto reset_abort;
    total_len += len;

    //------------------------------
    // Return the new string length.
    //------------------------------
    return total_len;

    //-----------------------------------------------------------------
    // The number text does not fit, so restore the original and abort.
    //-----------------------------------------------------------------
 reset_abort:
    * reset_ptr = 0;
    return ~0;
}

