//-----------------------------------------------------------------------------
// 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 <math.h>

#include "string_lib.h"

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	str_app_d_cet
//
// purpose	Append to the first string (given with maximum available space)
//		a double value in cetal/hexadecimal representation.
//
// arguments	1 (char *) pointer to target string
//		2 (size_t) maximum space in target string
//		3 (double) value to encode
//
// returns	(size_t) new length of string, or ~0 for error
//-----------------------------------------------------------------------------
size_t
str_app_d_cet (
    char *		arg_target
    ,
    size_t		arg_size
    ,
    double		arg_value
    )
__PROTO_END__
{
    static char		digit_set	[] = "0123456789abcdef";
    double		mantissa	;
    char *		target_ptr	;
    char *		target_end	;
    char *		target_undo	;
    char *		tmp_ptr		;
    int			exponent	;
    int			need_period	;
    unsigned int	collect		;
    char		tmp_str		[ 3 * sizeof (int) ];


    //---------------------------------------------------
    // Determine the end of string, check for at least
    // some space, and remember where to undo the append.
    //---------------------------------------------------
    target_ptr = arg_target;
    target_end = target_ptr + arg_size - 1;
    while ( target_ptr < target_end && * target_ptr ) ++ target_ptr;
    target_undo = target_ptr;
    if ( target_end - target_ptr < 6 ) goto error_undo_append;

    //-----------------------------------------------------
    // If the value is exactly zero, take this shortcut,
    // since zero will not work with the rest of the logic.
    //-----------------------------------------------------
    if ( arg_value == 0.0 ) {
	* target_ptr ++ = '0';
	* target_ptr ++ = 'x';
	* target_ptr ++ = '0';
	* target_ptr ++ = '.';
	* target_ptr = 0;
	return target_ptr - arg_target;
    }

    //---------------------------------------------------
    // Append the sign of the value and make it positive.
    //---------------------------------------------------
    if ( arg_value < 0.0 ) {
	* target_ptr ++ = '-';
	arg_value = - arg_value;
    }

    //-------------------------------------------------------------
    // Append "0x" to indicate the expression is cetal/hexadecimal.
    //-------------------------------------------------------------
    * target_ptr ++ = '0';
    * target_ptr ++ = 'x';

    //------------------------------------------------------------------
    // Extract mantissa digits one by one and append them to the string.
    //------------------------------------------------------------------
    need_period = 1;
    mantissa = frexp( arg_value, & exponent );

    //-- Syncronize exponent to multiple of 4.
    do {
	mantissa += mantissa;
	exponent -= 1;
    } while ( exponent & 3 );

    //-- Insert leading period and up to 4 0's to avoid exponent.
    if ( -16 <= exponent && exponent < 0 ) {
        if ( target_ptr == target_end ) goto error_undo_append;
        * target_ptr ++ = '.';
        need_period = 0;
        while ( ( exponent += 4 ) < 0 ) {
            if ( target_ptr == target_end ) goto error_undo_append;
            * target_ptr ++ = '0';
        }
    }

    //-- Encode the mantissa significant digits.
    for (;;) {
        collect = 0;
        if ( mantissa >= 8.0 ) { collect += 1; mantissa -= 8.0; }
        collect += collect;
        if ( mantissa >= 4.0 ) { collect += 1; mantissa -= 4.0; }
        collect += collect;
        if ( mantissa >= 2.0 ) { collect += 1; mantissa -= 2.0; }
        collect += collect;
        if ( mantissa >= 1.0 ) { collect += 1; mantissa -= 1.0; }
        if ( target_ptr == target_end ) goto error_undo_append;
        * target_ptr ++ = digit_set[ collect ];
        if ( mantissa == 0.0 ) break;
        mantissa *= 16.0;
        if ( need_period ) {
            if ( exponent == 0 ) {
                if ( target_ptr == target_end ) goto error_undo_append;
                * target_ptr ++ = '.';
                need_period = 0;
            } else {
                exponent -= 4;
            }
        }
    }

    //-- If no period yet, insert one at the end.
    if ( need_period ) {

        //-- Insert up to 3 trailing 0's before period to avoid exponent.
        if ( exponent <= 12 ) {
            while ( exponent > 0 ) {
                if ( target_ptr == target_end ) goto error_undo_append;
                * target_ptr ++ = '0';
                exponent -= 4;
            }
        }

        if ( target_ptr == target_end ) goto error_undo_append;
        * target_ptr ++ = '.';
    }

    //------------------------------------------------------
    // Append the exponent, marker, sign and decimal digits.
    //------------------------------------------------------
    if ( exponent != 0 ) {
	if ( target_ptr == target_end ) goto error_undo_append;
	* target_ptr ++ = 'p';
	if ( exponent < 0 ) {
	    if ( target_ptr == target_end ) goto error_undo_append;
	    * target_ptr ++ = '-';
	    exponent = - exponent;
	}
	tmp_ptr = tmp_str;
	* tmp_ptr ++ = digit_set[ exponent % 10U ];
	while ( exponent /= 10U ) {
	    * tmp_ptr ++ = digit_set[ exponent % 10U ];
	}
	while ( tmp_ptr > tmp_str ) {
	    if ( target_ptr == target_end ) goto error_undo_append;
	    * target_ptr ++ = * -- tmp_ptr;
	}
    }

    //-----------------------------------------------
    // All done.  Terminate string and return length.
    //-----------------------------------------------
    * target_ptr = 0;
    return target_ptr - arg_target;

    //--------------------------------------------------------------
    // Jump here to undo anything appended and return with an error.
    //--------------------------------------------------------------
 error_undo_append:
    * target_undo = 0;
    return ~ (size_t) 0U;
}

