//-----------------------------------------------------------------------------
// 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_ull_text
//
// purpose	Append to the first string (given with maximum available space)
//		a string of text representing the unsigned long 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 long) the number to convert
//		4 (const char *) language id string, default "en_US"
//
// 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_ull_text (
    char *		arg_target
    ,
    size_t		arg_length
    ,
    unsigned long long	arg_value
    ,
    const char *	arg_lang
    )
__PROTO_END__
{
    static const char *			nums_en_US	[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_en_US	[8] = {
        "twenty",
        "thirty",
        "forty",
        "fifty",
        "sixty",
        "seventy",
        "eighty",
        "ninety"
    };

    static const unsigned long long	power_en_US	[] = {
        1000000000000000000ULL,
        1000000000000000ULL,
        1000000000000ULL,
        1000000000ULL,
        1000000ULL,
        1000ULL,
        100ULL,
    };

    static const char * const		names_en_US	[] = {
        " quintillion",
        " quadrillion",
        " trillion",
        " billion",
        " million",
        " thousand",
        " hundred",
        NULL
    };

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

    //-------------------------------
    // Check language ID code string.
    //-------------------------------
    if ( arg_lang &&
	 ( ( arg_lang[0] != 'e' && arg_lang[0] != 'E' ) ||
	   ( arg_lang[1] != 'n' && arg_lang[1] != 'N' ) ||
	   ( arg_lang[2] != '_' && arg_lang[2] != '-' && arg_lang[2] != '.' ) ||
	   ( arg_lang[3] != 'U' && arg_lang[3] != 'u' ) ||
	   ( arg_lang[4] != 'S' && arg_lang[4] != 's' ) ||
	   ( arg_lang[5] != 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 long *	power_ptr	;
	const char * const *		names_ptr	;

	power_ptr = power_en_US;
	names_ptr = names_en_US;
	while ( * names_ptr ) {
	    if ( value >= * power_ptr ) {
		len = str_app_ull_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_en_US[ 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_en_US[ 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;
}

