/*
 * International Chemical Identifier (InChI)
 * Version 1
 * Software version 1.05
 * January 27, 2017
 *
 * The InChI library and programs are free software developed under the
 * auspices of the International Union of Pure and Applied Chemistry (IUPAC).
 * Originally developed at NIST.
 * Modifications and additions by IUPAC and the InChI Trust.
 * Some portions of code were developed/changed by external contributors
 * (either contractor or volunteer) which are listed in the file
 * 'External-contributors' included in this distribution.
 *
 * IUPAC/InChI-Trust Licence No.1.0 for the
 * International Chemical Identifier (InChI)
 * Copyright (C) IUPAC and InChI Trust Limited
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the IUPAC/InChI Trust InChI Licence No.1.0,
 * or any later version.
 *
 * Please note that this library is distributed WITHOUT ANY WARRANTIES
 * whatsoever, whether expressed or implied.
 * See the IUPAC/InChI-Trust InChI Licence No.1.0 for more details.
 *
 * You should have received a copy of the IUPAC/InChI Trust InChI
 * Licence No. 1.0 with this library; if not, please write to:
 *
 * The InChI Trust
 * 8 Cavendish Avenue
 * Cambridge CB1 7US
 * UK
 *
 * or e-mail to alan@inchi-trust.org
 *
 */


#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <limits.h>

#include "mode.h"
#include "mol_fmt.h"

#include "ichierr.h"
#include "util.h"
#include "ichi_io.h"



/*
    Molfile V3000 related procedures

*/


static int get_actual_atom_number( int index, int n, int *orig, int *fin );


/*
    Init V3000 reader
*/
int MolfileV3000Init( MOL_FMT_CTAB* ctab,
                      char *pStrErr )
{
    int ret=0;

    /* STAR ATOMS */
    ctab->v3000->n_star_atoms = 0;
    ctab->v3000->n_non_star_atoms = 0;

    if ( ctab->n_atoms )
    {
        ctab->v3000->atom_index_orig = (int *) inchi_calloc( ctab->n_atoms, sizeof(int) );
        ctab->v3000->atom_index_fin =  (int *) inchi_calloc( ctab->n_atoms,sizeof(int) );
        memset( ctab->v3000->atom_index_orig, -1, ctab->n_atoms );
        memset( ctab->v3000->atom_index_fin, -1, ctab->n_atoms );
    }
    else
    {
        ctab->v3000->atom_index_orig =
        ctab->v3000->atom_index_fin  = NULL;
    }

    /* HAPTIC BONDS */
    ctab->v3000->n_haptic_bonds = 0;
    ctab->v3000->haptic_bonds = (NUM_LISTS *) inchi_calloc(1, sizeof( NUM_LISTS ) );
    if ( !ctab->v3000->haptic_bonds )
    {
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }
    ret = NumLists_Alloc( ctab->v3000->haptic_bonds, 8 );
    if ( ret<0 )
    {
        ctab->v3000->haptic_bonds = NULL;
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }


    /* STEABS */
    ctab->v3000->n_steabs = 0;
    ctab->v3000->steabs = (NUM_LISTS *) inchi_calloc(1, sizeof( NUM_LISTS ) );
    if ( !ctab->v3000->steabs )
    {
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }
    ret = NumLists_Alloc( ctab->v3000->steabs, 1 );
    if ( ret<0 )
    {
        ctab->v3000->steabs = NULL;
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }
    /* STEREL */
    ctab->v3000->n_sterel = 0;
    ctab->v3000->sterel = (NUM_LISTS *) inchi_calloc(1, sizeof( NUM_LISTS ) );
    if ( !ctab->v3000->sterel )
    {
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }
    ret = NumLists_Alloc( ctab->v3000->sterel, 4 );
    if ( ret<0 )
    {
        ctab->v3000->sterel = NULL;
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }
    /* STERAC */
    ctab->v3000->n_sterac = 0;
    ctab->v3000->sterac = (NUM_LISTS *) inchi_calloc(1, sizeof( NUM_LISTS ) );
    if ( !ctab->v3000->sterac )
    {
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }
    ret = NumLists_Alloc( ctab->v3000->sterac, 4 );
    if ( ret<0 )
    {
        ctab->v3000->sterac = NULL;
        AddErrorMessage(pStrErr, "Out of RAM");
        return -1;
    }


    return ret;
}


/*
    Delete V3000 reader data
*/
int DeleteMolfileV3000Info( MOL_FMT_v3000* v3000 )
{
    if ( v3000 )
    {

        if ( v3000->atom_index_orig )
            inchi_free ( v3000->atom_index_orig );
        if ( v3000->atom_index_fin )
            inchi_free ( v3000->atom_index_fin );

        if ( v3000->haptic_bonds )
        {
            NumLists_Free( v3000->haptic_bonds );
            free ( v3000->haptic_bonds );
        }

        if ( v3000->steabs )
        {
            NumLists_Free( v3000->steabs );
            free ( v3000->steabs );
        }

        if ( v3000->sterel )
        {
            NumLists_Free( v3000->sterel );
            free ( v3000->sterel );
        }

        if ( v3000->sterac )
        {
            NumLists_Free( v3000->sterac );
            free ( v3000->sterac );
        }

        inchi_free ( v3000 );
        v3000 = NULL;
    }

    return 0;
}


/*
    Extended version of inchi_fgetsLf which is able of reading
    concatenated lines (ending with '-') of V3000 Molfile.
    Also removes "M  V30 " prefix" and normalizes the rest of string
*/
char* inchi_fgetsLf_V3000( char* line, INCHI_IOSTREAM* inp_stream )
{
char *p=NULL;
int len=0;

    p = inchi_fgetsLf( line, MOL_FMT_V3000_INPLINELEN, inp_stream );
    if ( !p )
        return NULL;

    len = (int) strlen(p);
    if ( len<7 )
        return NULL;

    if ( strncmp( p, "M  V30 ", 7) )
        return NULL;

    p+= 7;
    len = normalize_string( p );

    return  p;
}




/*
    Read V3000 field.

    It is MolfileReadField updated for V3000.
    It considers right whitespace as stop sign,
    no predefined len is used.

    Returns -1 on error otherwise number of bytes read.

    NB: ASSUMES THAT STRING HAS BEEN NORMALIZED with normalize_string()

    TODO: strings with spaces in double quotes
*/
int MolfileV3000ReadField( void* data,
                           int data_type,
                           char** line_ptr)
{
int  nread = 0;
char field[MOL_FMT_V3000_MAXFIELDLEN];
const int max_field_len=sizeof(field);
long ldata = 0L;
double ddata = 0.0;
char *p_end;

    memset( field, 0, max_field_len );

    nread =  read_upto_delim( line_ptr, field, max_field_len, " \t\n\v\f\r" );

    switch( data_type )
    {
        case MOL_FMT_STRING_DATA:
            {
                if ( nread )
                    mystrncpy ( (char *) data, field, nread + 1 );
                else
                    ( (char *)data )[0] = '\0';
            }
            break;

        case MOL_FMT_CHAR_INT_DATA:
        case MOL_FMT_SHORT_INT_DATA:
        case MOL_FMT_LONG_INT_DATA:
        case MOL_FMT_INT_DATA:
            {
            /* assume that field ends at first non-digit */
            ldata = strtol( field, &p_end, 10 );

            if ( p_end==field )
                nread = 0;

            if ( data_type == MOL_FMT_LONG_INT_DATA )
            {
                if ( LONG_MIN < ldata && ldata < LONG_MAX )
                    *(long*) data = (long) ldata;
                else
                {
                    *(long*) data = 0L;
                    nread = -1;
                }
            }
            else if ( data_type == MOL_FMT_INT_DATA )
            {
                if ( INT_MIN <= ldata  && ldata <= INT_MAX )
                    *(int*) data = (int)ldata;
                else
                {
                    *(int*) data = (int)0;
                    nread = -1;
                }
            }

            else if ( data_type == MOL_FMT_CHAR_INT_DATA )
            {
                if ( SCHAR_MIN <= ldata  && ldata <= SCHAR_MAX )
                    *(S_CHAR*) data = (S_CHAR)ldata;
                else
                {
                    *(S_CHAR*) data = (S_CHAR)0;
                    nread = -1;
                }
            }

            else if ( data_type == MOL_FMT_SHORT_INT_DATA )
            {
                if ( SHRT_MIN <= ldata && ldata <= SHRT_MAX )
                    *(S_SHORT*) data = (S_SHORT)ldata;
                else
                {
                    *(S_SHORT*) data = (S_SHORT)0;
                    nread = -1;
                }
            }
            else
                nread =-1;
            }
            break;    /* INT's */


        case MOL_FMT_DOUBLE_DATA:
        case MOL_FMT_FLOAT_DATA:
            {
                /* assume that field ends at first non-digit */
                ddata = strtod( field, &p_end );

                if ( p_end == field )
                    nread = 0;

                if ( data_type == MOL_FMT_DOUBLE_DATA )
                {
                    if ( ddata != HUGE_VAL && /*ldata*/ ddata != -HUGE_VAL )
                        *(double*)data = ddata;
                    else
                    {
                        *(double*)data = 0.0;
                        nread = -1;
                    }
                }
                else if ( data_type == MOL_FMT_FLOAT_DATA )
                {
                    if ( fabs(ddata) <= (double)FLT_MIN )
                        *(float*)data = 0.0;
                    else if ( fabs(ddata) >= (double)FLT_MAX )
                    {
                        *(float*)data = 0.0;
                        nread = -1;
                    }
                }
                else
                {
                    *(float*)data = (float)ddata;
                }
            }
            break; /* REAL's */

        default:
            nread = -1;
    }

    return nread;
}


/*
    Read V3000 keyword.
    TODO: strings with spaces in double quotes
*/
int MolfileV3000ReadKeyword( char* key,
                             char** line_ptr)
{
int  nread = 0;
char field[MOL_FMT_V3000_MAXFIELDLEN];
const int max_field_len=sizeof(field);


    memset( field, 0, max_field_len );

    nread =  read_upto_delim( line_ptr, field, max_field_len, "= \t\n\v\f\r" );

    if ( nread )
    {
        mystrncpy ( key, field, nread + 1 );
        /* consume '=' sign if present */
        if ( *line_ptr )
        {
            if ( *line_ptr[0] == '=' )
            {
                *line_ptr = *line_ptr + 1;
            }
        }
    }
    else
        key[0] = '\0';

    return nread;
}


/*
    Read V3000 head of CTab
*/
int MolfileV3000ReadCTABBeginAndCountsLine( MOL_FMT_CTAB* ctab,
                                            INCHI_IOSTREAM *inp_file,
                                            char *pStrErr )
{
char field[MOL_FMT_V3000_MAXFIELDLEN];
int   err = 0, len;
int failed = 0;

int nc;
char *p=NULL, *line = NULL;
INCHI_IOSTREAM tmpin;
INCHI_IOSTREAM_STRING *pin= &tmpin.s;
inchi_ios_init( &tmpin, INCHI_IOSTREAM_TYPE_STRING, NULL );


    /* Check for proper start */

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    inchi_strbuf_reset( pin );
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    if (  !p  || strcmp(p, "BEGIN CTAB") )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 CTab start marker");
    }
    remove_one_lf( line );


    /* Reset all previosly read data from quasi-counts line            */
    /* (which contains only single meaningful value, 'V3000' marker    */

    ctab->n_atoms = -1;
    ctab->n_bonds = -1;
    ctab->chiral_flag = -1;
    ctab->n_stext_entries = -1;
    /* Relax stricthness of V3000 conformance: */
    /* Do not check if '999' supplied, just use this. */
    ctab->n_property_lines = 999;


    /* Read counts line */

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    inchi_strbuf_reset( pin );
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;

    if ( !p )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Cannot read V3000 counts line");
    }

    remove_one_lf( line );


    /* Parse counts line */

    len = MolfileV3000ReadField(field, MOL_FMT_STRING_DATA, &p);
    if ( strcmp( field, "COUNTS" ) )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Cannot read V3000 counts line");
    }

    failed = 0;
    if (0 > MolfileV3000ReadField(&ctab->n_atoms,MOL_FMT_INT_DATA, &p) )
        failed = 2;
    else if ( 0 > MolfileV3000ReadField(&ctab->n_bonds,MOL_FMT_INT_DATA, &p) )
        failed = 1;
    else if ( 0 > MolfileV3000ReadField(&ctab->v3000->n_sgroups,MOL_FMT_INT_DATA, &p) )
        failed = 1;
    else if ( 0 > MolfileV3000ReadField(&ctab->v3000->n_3d_constraints,MOL_FMT_INT_DATA, &p) )
        failed = 1;
    else if ( 0 > MolfileV3000ReadField(&ctab->chiral_flag, MOL_FMT_CHAR_INT_DATA, &p) )
        failed = 1;

    if ( failed )
    {
        err = 3;
        if ( failed==2 )
        {
            TREAT_ERR( err, 3, "Number of atoms too large. V3000 counts line:");
        }
        else
        {
            /* too long input file line or other value min-max range mismatch */
            TREAT_ERR( err, 3, "Cannot interpret V3000 counts line:");
        }
        dotify_non_printable_chars( line );
        AddErrorMessage(pStrErr, line);
        goto err_fin;
    }

err_fin:
    return err;
}


/*
    Read V3000 SGroup
*/
int MolfileV3000ReadSGroup( MOL_FMT_CTAB* ctab,
                            INCHI_IOSTREAM *inp_file,
                            int err, char *pStrErr )
{
int nc;
char *p=NULL, *line = NULL;
INCHI_IOSTREAM tmpin;
INCHI_IOSTREAM_STRING *pin= &tmpin.s;
inchi_ios_init( &tmpin, INCHI_IOSTREAM_TYPE_STRING, NULL );

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/

    while (1 )
    {
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        remove_one_lf( line );
        if ( p && !strcmp(p, "END SGROUP") )
            return 0;
    }

    /* if (  !p  || strcmp(p, "END SGROUP") ) */
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 SGroup end marker");
    }

err_fin:
    return err;
}


/*
    Read V3000 3DBlock
*/
int MolfileV3000Read3DBlock( MOL_FMT_CTAB* ctab,
                             INCHI_IOSTREAM *inp_file,
                             int err, char *pStrErr )
{
int nc;
char *p=NULL, *line = NULL;
INCHI_IOSTREAM tmpin;
INCHI_IOSTREAM_STRING *pin= &tmpin.s;
inchi_ios_init( &tmpin, INCHI_IOSTREAM_TYPE_STRING, NULL );

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    remove_one_lf( line );

    if (  !p  || strcmp(p, "END OBJ3D") )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 3DBlock end marker");
    }
goto err_fin;

err_fin:
    return err;
}


/*
    Read V3000 collections
*/
int MolfileV3000ReadCollections( MOL_FMT_CTAB* ctab,
                                INCHI_IOSTREAM *inp_file,
                                int err, char *pStrErr )
{
char field[MOL_FMT_V3000_MAXFIELDLEN];
const int max_field_len=sizeof(field);
int  nread, len, n_coll;
int failed = 0;
int nc;
char *p=NULL, *line = NULL;
INCHI_IOSTREAM tmpin;
INCHI_IOSTREAM_STRING *pin= &tmpin.s;
inchi_ios_init( &tmpin, INCHI_IOSTREAM_TYPE_STRING, NULL );

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    remove_one_lf( line );

    while ( p && strcmp(p, "END COLLECTION") )
    {
        int stereo_kind = MOL_FMT_V3000_STENON;
        /* stereo collection of interest */
        NUM_LISTS *ste_coll=NULL;

        nread =  read_upto_delim( &p, field, max_field_len, "/" );
        if ( nread < 6 )
        {
            failed = 1;
            break;
        }
        if ( strcmp(field, "MDLV30") )
        {
            failed = 1;
            break;
        }

        nread =  read_upto_delim( &p, field, max_field_len, "1234567890 \t\n\v\f\r" );
        if ( !strcmp(field, "/STEABS") )
        {
            n_coll = 1;
            stereo_kind = MOL_FMT_V3000_STEABS;
            ste_coll = ctab->v3000->steabs;
        }
        else if ( !strcmp(field, "/STEREL") )
        {
            /* get number of collection */
            if ( 0 > MolfileV3000ReadField( &n_coll, MOL_FMT_CHAR_INT_DATA, &p))
                {  failed = 1; break; }
            stereo_kind = MOL_FMT_V3000_STEREL;
            ste_coll = ctab->v3000->sterel;
        }
        else if ( !strcmp(field, "/STERAC") )
        {
            /* get number of collection */
            if ( 0 > MolfileV3000ReadField( &n_coll, MOL_FMT_CHAR_INT_DATA, &p))
                {  failed = 1; break; }
            stereo_kind = MOL_FMT_V3000_STERAC;
            ste_coll = ctab->v3000->sterac;
        }
        else
            ;

        if ( stereo_kind != MOL_FMT_V3000_STENON )
            /* currently skip non-stereo collections */
        {
            /* consume atoms= */
            if ( (len=MolfileV3000ReadKeyword(field, &p) > 0 ) )
            {
                if ( !strcmp(field, "ATOMS") )
                {
                    int res, *num_list = NULL;

                    if ( 0 > MolfileV3000ReadStereoCollection( ctab, &p, &num_list, pStrErr) )
                        failed = 1;

                    else if ( !num_list )
                        failed = 1;

                    else
                    {
                        int k, nnum;
                        num_list[0] = n_coll;
                        nnum = num_list[1];
                        for (k=2; k<nnum; k++)
                        {
                            num_list[k] =
                                get_actual_atom_number( num_list[k],
                                                        ctab->v3000->n_non_star_atoms+ctab->v3000->n_star_atoms,
                                                        ctab->v3000->atom_index_orig,
                                                        ctab->v3000->atom_index_fin );
                        }
                        res = NumLists_Append( ste_coll, num_list );
                        if ( res < 0 )
                        {
                            failed = 1;
                        }
                        else
                        {
                            if ( stereo_kind == MOL_FMT_V3000_STEABS )
                                ctab->v3000->n_steabs++;
                            else if ( stereo_kind == MOL_FMT_V3000_STEREL )
                                ctab->v3000->n_sterel++;
                            else if ( stereo_kind == MOL_FMT_V3000_STERAC )
                                ctab->v3000->n_sterac++;
                        }
                    }
                }
            }
            else
                {  failed = 1; break; }
        }

/*next_line:*/
        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        remove_one_lf( line );
    }

    if ( failed )
    {
        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        remove_one_lf( line );
    }

    if (  !p  )
        failed = 1;

    if ( failed )
    {
        err = 7;
        TREAT_ERR( err, 7, "Cannot interpret V3000 collection line(s)");
        dotify_non_printable_chars( line );
        AddErrorMessage(pStrErr, line);
        goto err_fin;
    }

    /* Error: No V3000 Collection end marker */
    if ( ctab->v3000->n_steabs ||
         ctab->v3000->n_sterel ||
         ctab->v3000->n_sterac  )
    {
        AddErrorMessage(pStrErr, "V3000 enhanced stereo read/stored but ignored");
    }

err_fin:
    return err;
}


/*
    Read V3000 atoms
*/
int MolfileV3000ReadAtomsBlock( MOL_FMT_CTAB* ctab,
                                INCHI_IOSTREAM *inp_file,
                                int err, char *pStrErr )
{
int i;
static const S_SHORT charge_val[] = {0, 3, 2, 1, 'R', -1, -2, -3};
char field[MOL_FMT_V3000_MAXFIELDLEN];
int nc;
char *p=NULL, *line = NULL;
INCHI_IOSTREAM tmpin;
INCHI_IOSTREAM_STRING *pin= &tmpin.s;
inchi_ios_init( &tmpin, INCHI_IOSTREAM_TYPE_STRING, NULL );

    /* Check for proper start */

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    if (  !p  || strcmp(p, "BEGIN ATOM") )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 Atom block start marker");
    }
    remove_one_lf( line );

    ctab->v3000->n_non_star_atoms = 0;
    for ( i = 0; i < ctab->n_atoms; i++ )
    {
        int ii = -1;

        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        if ( !p )
        {
            if ( !err )
            {
                TREAT_ERR( err, 2, "Cannot read V3000 atom block line");
            }
            break;
        }
        remove_one_lf( line );

        if ( err )
        {
            if ( !strcmp( line, SD_FMT_END_OF_DATA ) )
            {
                err = -abs(err);
                break;
            }
            continue; /* bypass the rest of the Atom block */
        }

        if ( ctab->atoms )
        {
            int index, aamap;    /* not used actually, just read them */
            int len;
            char symbol[6]; /* TODO: treat possibly long V3000 atom names */
            double fx, fy, fz;


            /* Read positional parameters */
            int failed = 0;
            if        ( 0 > MolfileV3000ReadField( &index,MOL_FMT_INT_DATA, &p ) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &symbol, MOL_FMT_STRING_DATA, &p) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &fx, MOL_FMT_DOUBLE_DATA, &p ) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &fy, MOL_FMT_DOUBLE_DATA, &p ) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &fz, MOL_FMT_DOUBLE_DATA, &p ) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &aamap ,MOL_FMT_INT_DATA, &p )  )
                failed = 1;

            if ( failed )
            {

                err = 4;
                TREAT_ERR( err, 4, "Cannot interpret V3000 atom block line:");
                dotify_non_printable_chars( line );
                AddErrorMessage(pStrErr, line);

                if ( !strcmp( line, SD_FMT_END_OF_DATA ) )
                {
                    err = -abs(err);
                    break;
                }
                continue; /* can't interpret a first half of atom block line */
            }


            if ( !strcmp(symbol, "*" ) )
            {
                /* ignore star atoms but save index info */
                ctab->v3000->atom_index_orig[i] = index;
                ctab->v3000->atom_index_fin[i] = -1;
                ctab->v3000->n_star_atoms++;
                continue;
            }

            ctab->v3000->n_non_star_atoms++;
            ctab->v3000->atom_index_orig[i] = index;
            ctab->v3000->atom_index_fin[i] = ctab->v3000->n_non_star_atoms;
            ii = ctab->v3000->n_non_star_atoms - 1;

            mystrncpy( ctab->atoms[ii].symbol, symbol, sizeof(ctab->atoms[ii].symbol) );
            if ( 2 == strlen(ctab->atoms[ii].symbol) && isupper(UCINT ctab->atoms[ii].symbol[1]))
            {
                ctab->atoms[ii].symbol[1] = (char)tolower(UCINT ctab->atoms[ii].symbol[1]); /* 5-4-99 DCh*/
            }
            ctab->atoms[ii].fx = fx;
            ctab->atoms[ii].fy = fy;
            ctab->atoms[ii].fz = fz;


            /* Read key-val pairs if any */
            while ( p && (len=MolfileV3000ReadKeyword(field, &p)) > 0 )
            {

                int itmp;
                char ctmp;
                char stmp[MOL_FMT_V3000_MAXFIELDLEN];

                int failed = 0;

                if ( !strcmp(field, "CHG") )
                {
                    if ( 0 > MolfileV3000ReadField( &ctab->atoms[ii].charge, MOL_FMT_CHAR_INT_DATA, &p))
                        failed = 1;
                }
                else if ( !strcmp(field, "RAD") )
                {
                     if ( 0 > MolfileV3000ReadField( &ctab->atoms[ii].radical, MOL_FMT_CHAR_INT_DATA, &p))
                        failed = 1;
                }
                else if ( !strcmp(field, "CFG") )
                {
                     if ( 0 > MolfileV3000ReadField( &ctab->atoms[ii].stereo_parity, MOL_FMT_CHAR_INT_DATA, &p))
                        failed = 1;
                }

                else if ( !strcmp(field, "MASS") )
                {
                    /*
                        Default = natural abundance
                        A specified value indicates the absolute
                        atomic weight of the designated atom.
                    */
                    S_SHORT iso_mass;
                    if ( 0 > MolfileV3000ReadField( &iso_mass, MOL_FMT_SHORT_INT_DATA, &p))
                    {
                        failed = 1;
                        TREAT_ERR( err, 0, "Isotopic data not recognized:");
                        AddErrorMessage(pStrErr, line);
                            /* ignore isotopic error for now */
                    }
                    else
                    {
                        /*    What we read is an absolute isotopic mass, by V3000 spec.
                            Adjust this to old convention for further processing:
                            set 'ctab->atoms[ii].mass_difference' to
                                127            if isotopic mass is the same as element mass
                                            in Periodic Table (rounded avg by all isotopes), 'atw'
                                delta        otherwise, the value of difference 'delta' = ( isotopic mass - 'atw')
                        */
                        int  atw, delta;
                        atw = get_atomic_mass( ctab->atoms[ii].symbol );
                        delta = (int) iso_mass - atw ;
                        ctab->atoms[ii].mass_difference = (char) ( delta ? delta : ZERO_ATW_DIFF);
                    }
                }

                else if ( !strcmp(field, "VAL") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        failed = 1;
                     else
                    {
                        /* adjust to old convention: was 15 for zero, now -1 for zero */
                        if ( itmp == -1 )
                            ctmp = 15;
                        else
                            ctmp = ( char ) itmp;
                        ctab->atoms[ii].valence = ctmp;
                     }
                }
                else if ( !strcmp(field, "HCOUNT") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;    /* skip query-related stuff */
                }
                else if ( !strcmp(field, "STBOX") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;    /* skip for now */
                }
                else if ( !strcmp(field, "INVRET") || !strcmp(field, "EXACHG") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;    /* skip reaction-related stuff */
                }
                else if ( !strcmp(field, "SUBST") || !strcmp(field, "UNSAT") || !strcmp(field, "RBCNT") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;    ;    /* skip query-related stuff */
                }
                else if ( !strcmp(field, "ATTCHPT") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;
                }
                else if ( !strcmp(field, "RGROUPS") )
                {
                     if ( 0 > MolfileV3000ReadField( &stmp, MOL_FMT_STRING_DATA, &p))
                        ;
                }
                else if ( !strcmp(field, "ATTCHORD") )
                {
                     if ( 0 > MolfileV3000ReadField( &stmp, MOL_FMT_STRING_DATA, &p))
                        ;
                }
                else if ( !strcmp(field, "CLASS") )
                {
                     if ( 0 > MolfileV3000ReadField( &stmp, MOL_FMT_STRING_DATA, &p))
                        ;
                }
                else if ( !strcmp(field, "SEQID") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;
                }


                if ( failed )
                {
                    err = 4;
                    TREAT_ERR( err, 4, "Cannot interpret V3000 atom block key-value pair");
                    dotify_non_printable_chars( line );
                    AddErrorMessage(pStrErr, line);

                    if ( !strcmp( line, SD_FMT_END_OF_DATA ) )
                    {
                        err = -abs(err);
                        break;
                    }
                    continue;
                }
            }
        } /* if ( NULL != ctab->atoms )  */
    }    /* for ( i = 0; i < ctab->n_atoms; i++ )  */

    if ( ctab->v3000->n_star_atoms )
    {
        AddErrorMessage(pStrErr, "V3000 star atoms ignored");
        ctab->n_atoms = ctab->v3000->n_non_star_atoms;
    }

    /* Check for proper finish */

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    inchi_strbuf_reset( pin );
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    if (  !p  || strcmp(p, "END ATOM") )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 Atom block end marker");
    }
    remove_one_lf( line );

err_fin:
    return err;
}


/*
    Read V3000 bonds
*/
int MolfileV3000ReadBondsBlock( MOL_FMT_CTAB* ctab,
                                INCHI_IOSTREAM *inp_file,
                                int err,
                                char *pStrErr )
{
int i;
char field[MOL_FMT_V3000_MAXFIELDLEN];
int nc;
char *p=NULL, *line = NULL;
INCHI_IOSTREAM tmpin;
INCHI_IOSTREAM_STRING *pin= &tmpin.s;
inchi_ios_init( &tmpin, INCHI_IOSTREAM_TYPE_STRING, NULL );

    /* Check for proper start */

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    if (  !p  || strcmp(p, "BEGIN BOND") )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 Bond block start marker");
    }
    remove_one_lf( line );

    ctab->v3000->n_haptic_bonds = 0;
    ctab->v3000->n_non_haptic_bonds = 0;

    for ( i = 0; i < ctab->n_bonds; i++ )
    {
        int is_haptic = 0;

        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        if ( !p )
        {
            if ( !err )
            {
                TREAT_ERR( err, 2, "Cannot read V3000 bond block line");
            }
            break;
        }
        remove_one_lf( line );

        if ( err )
        {
            if ( !strcmp( line, SD_FMT_END_OF_DATA ) )
            {
                err = -abs(err);
                break;
            }
            continue;
        }


        if ( ctab->bonds )
        {
            int index, n_orig_at, len;
            short int atnum1=-1, atnum2=-1;
            char bond_type=0, stereo=0;
            int failed = 0;
            int has_non_existent_atom = 0;

            n_orig_at = ctab->v3000->n_non_star_atoms + ctab->v3000->n_star_atoms;

            /* read positional parameters */

            if (      0 > MolfileV3000ReadField( &index,MOL_FMT_INT_DATA, &p ) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &bond_type, MOL_FMT_CHAR_INT_DATA,  &p ) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &atnum1, MOL_FMT_SHORT_INT_DATA, &p ) )
                failed = 1;
            else if ( 0 > MolfileV3000ReadField( &atnum2, MOL_FMT_SHORT_INT_DATA, &p ) )
                failed = 1;

            atnum1 =
                get_actual_atom_number( atnum1,
                                        n_orig_at,
                                        ctab->v3000->atom_index_orig,
                                        ctab->v3000->atom_index_fin );

            atnum2 =
                get_actual_atom_number( atnum2,
                                        n_orig_at,
                                        ctab->v3000->atom_index_orig,
                                        ctab->v3000->atom_index_fin );

            if ( (atnum1 < 0)  &&  (atnum2 < 0)  )
                failed = 1;
            else if ( atnum1 < 0  ||  atnum2 < 0  )
                has_non_existent_atom = 1;

            if ( failed )
            {

                if ( !err )
                {
                    /* can't interpret bonds block line */
                    TREAT_ERR( err, 4, "Cannot interpret V3000 bond block line:");
                    dotify_non_printable_chars( line );
                    AddErrorMessage(pStrErr, line);
                }
                if ( !strcmp( line, SD_FMT_END_OF_DATA ) )
                {
                    err = -abs(err);
                    break;
                }
            }


            /* TODO: treat new bond types  9 10 */
            /* read key-val pairs if any */
            while ( p && (len=MolfileV3000ReadKeyword(field, &p)) > 0 )
            {

                int itmp;
                char stmp[MOL_FMT_V3000_MAXFIELDLEN];

                int failed = 0;

                if ( !strcmp(field, "CFG") )
                {
                    if ( 0 > MolfileV3000ReadField( &stereo, MOL_FMT_CHAR_INT_DATA,  &p))
                    {
                        failed = 1;
                    }
                    else
                    {
                        /*    adjust stereo to old convention for wedges which was:
                                0 = not stereo, 1 = Up,  4 = Either, 6 = Down
                            now:
                                0 = none (default), 1 = up, 2 = either, 3 = down
                        */
                        if ( stereo == 2 )
                            stereo = 4;
                        else if ( stereo == 3 )
                            stereo = 6;
                     }
                }
                else if ( !strcmp(field, "TOPO") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;    /* skip query-related stuff */
                }
                else if ( !strcmp(field, "RXCTR") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;    /* skip reaction-related stuff */
                }
                else if ( !strcmp(field, "STBOX") )
                {
                     if ( 0 > MolfileV3000ReadField( &itmp, MOL_FMT_INT_DATA, &p))
                        ;    /* skip for now */
                }
                else if ( !strcmp(field, "ENDPTS") )
                {
                    int res, *num_list = NULL;
                    if ( 0 > MolfileV3000ReadHapticBond( ctab, &p, &num_list, pStrErr))
                        failed = 1;
                    else if ( !num_list )
                        failed = 1;
                    else
                    {
                        int existent_atom = atnum1;
                        if ( existent_atom < 0 )
                            existent_atom = atnum2;

                        if ( existent_atom < 0 ) /* should not be here */
                        {
                            failed = 1;
                        }
                        else
                        {
                            int k, nnum;
                            nnum = num_list[2];
                            num_list[1] = existent_atom;
                            for (k=3; k<nnum; k++)
                            {
                                num_list[k] =
                                    get_actual_atom_number( num_list[k],
                                                            n_orig_at,
                                                            ctab->v3000->atom_index_orig,
                                                            ctab->v3000->atom_index_fin );
                            }
                            res = NumLists_Append( ctab->v3000->haptic_bonds, num_list );
                            if ( res < 0 )
                            {
                                failed = 1;
                            }
                            else
                            {
                                is_haptic = 1;
                            }
                        }
                    }
                }
                else if ( !strcmp(field, "DISP") )
                {
                     if ( 0 > MolfileV3000ReadField( &stmp, MOL_FMT_STRING_DATA, &p))
                        ;
                }
                else if ( !strcmp(field, "ATTACH") )
                {
                     if ( 0 > MolfileV3000ReadField( &stmp, MOL_FMT_STRING_DATA, &p))
                        ;
                }


                if ( failed )
                {
                    if ( !err )
                    {
                        /* can't interpret bonds block line */
                        TREAT_ERR( err, 4, "Cannot interpret V3000 bond block line:");
                        dotify_non_printable_chars( line );
                        AddErrorMessage(pStrErr, line);
                    }
                    if ( !strcmp( line, SD_FMT_END_OF_DATA ) )
                    {
                        err = -abs(err);
                        break;
                    }
                }
            } /* while ( p && (len=MolfileV3000ReadKeyword(field, &p)) > 0 ) */


            if ( is_haptic )
            {
                int ii = ctab->v3000->n_haptic_bonds;
                ctab->v3000->haptic_bonds->lists[ii][0] = bond_type;
                ctab->v3000->n_haptic_bonds++;
                continue;
            }
            else
            {
                int ii = ctab->v3000->n_non_haptic_bonds;
                ctab->bonds[ii].atnum1 = atnum1;
                ctab->bonds[ii].atnum2 = atnum2;
                ctab->bonds[ii].bond_type = bond_type;
                ctab->bonds[ii].bond_stereo = stereo;
                ctab->v3000->n_non_haptic_bonds++;
            }
        } /* if ctab->bonds */
    } /* for ( i = 0; i < ctab->n_bonds; i++ )  */


    if ( ctab->v3000->n_haptic_bonds )
    {
        AddErrorMessage(pStrErr, "V3000 haptic bonds read/stored but ignored");
        ctab->n_bonds = ctab->v3000->n_non_haptic_bonds;
    }


    /* Check for proper finish */

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    inchi_strbuf_reset( pin );
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    if (  !p  || strcmp(p, "END BOND") )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 Bond block end marker");
    }

    remove_one_lf( line );

err_fin:
    return err;
}


/*
    Convert atom index to the final consequitive atom number (starting from 1)
    Returns -1 for star atom or not found index
*/
int get_actual_atom_number( int index, int n, int *orig, int *fin )
{
    int i;
    for (i=0; i<n; i++)
    {
        if ( orig[i]==index ) return fin[i];
    }
    return -1;
}


/*
    Read V3000 tail of CTab
*/
int MolfileV3000ReadTailOfCTAB( MOL_FMT_CTAB* ctab,
                                INCHI_IOSTREAM *inp_file,
                                int err,
                                char *pStrErr )
{
int retcode=err;
int nc;
char *p=NULL, *line = NULL;
INCHI_IOSTREAM tmpin;
INCHI_IOSTREAM_STRING *pin= &tmpin.s;
inchi_ios_init( &tmpin, INCHI_IOSTREAM_TYPE_STRING, NULL );

    /*p = inchi_fgetsLf_V3000( line, inp_file );*/
    nc = get_V3000_input_line_to_strbuf(pin, inp_file);
    if ( nc<1 )    p = NULL;
    else        p = line = pin->pStr;
    remove_one_lf( line );

    if (  p  && !strcmp(p, "BEGIN SGROUP") )
    {
        retcode = MolfileV3000ReadSGroup( ctab, inp_file, retcode, pStrErr );
        if ( retcode )
        {
            retcode+= 70;
            TREAT_ERR_AND_FIN (retcode, 1, err_fin, pStrErr );
        }
        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        remove_one_lf( line );
    }

    if (  p  && !strcmp(p, "BEGIN OBJ3D") )
    {
        retcode = MolfileV3000Read3DBlock( ctab, inp_file, retcode, pStrErr );
        if ( retcode )
        {
            retcode+= 70;
            TREAT_ERR_AND_FIN (retcode, 1, err_fin, pStrErr );
        }
        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        remove_one_lf( line );
    }

    while (  p  && !strcmp(p, "LINKNODE") )
    {
        /* skip for now */
        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        remove_one_lf( line );
    }

    /* Collections */
    while (  p  && !strcmp(p, "BEGIN COLLECTION") )
    {
        retcode = MolfileV3000ReadCollections( ctab, inp_file, retcode, pStrErr );
        if ( retcode )
        {
            retcode+= 70;
            TREAT_ERR_AND_FIN (retcode, 1, err_fin, pStrErr );
        }
        /*p = inchi_fgetsLf_V3000( line, inp_file );*/
        inchi_strbuf_reset( pin );
        nc = get_V3000_input_line_to_strbuf(pin, inp_file);
        if ( nc<1 )    p = NULL;
        else        p = line = pin->pStr;
        remove_one_lf( line );
    }

    if (  !p  || strcmp(p, "END CTAB") )
    {
        TREAT_ERR_AND_FIN (err, 1, err_fin, "Error: No V3000 CTAB end marker");
    }

    remove_one_lf( line );


err_fin:
    return err;
}


/*
    Read haptic bond info
*/
int MolfileV3000ReadHapticBond( MOL_FMT_CTAB* ctab,
                                char** line_ptr,
                                int **num_list,
                                char *pStrErr )
{
int  nread = 0;
char field[MOL_FMT_V3000_MAXFIELDLEN];
const int max_field_len=sizeof(field);
char *p_end;
int i, nnum = 0;

    *num_list = NULL;

    memset( field, 0, max_field_len );

    nread =  read_upto_delim( line_ptr, field, max_field_len, "1234567890 \t\n\v\f\r" );
    if ( strcmp(field,"(") )
        return -1;

    nread =  read_upto_delim( line_ptr, field, max_field_len, " \t\n\v\f\r" );

    nnum = strtol( field, &p_end, 10 );

    if ( p_end==field )
        return -1; /* paranoia */
    if ( nnum < 0 )
        return -1;


    *num_list = (int *) calloc( nnum + 3 , sizeof(int) );


    if ( !*num_list )
    {
        nread = -1;
        goto ret;
    }

    (*num_list)[0] = -1;    /* will be bond type, to be filled by caller */
    (*num_list)[1] = -1;    /* will be atom number, to be filled by caller */
    (*num_list)[2] = nnum;

    for ( i=3; i<nnum+3; i++)
    {
        if ( 0 > MolfileV3000ReadField( &((*num_list)[i]), MOL_FMT_INT_DATA, line_ptr ) )
        {
            nread = -1;
            goto ret;
        }
    }


    /* ')' should have been consumed  by strtol */

    /* check for ATTACH=ALL */

    nread =  read_upto_delim( line_ptr, field, max_field_len, " \t\n\v\f\r" );

    if ( nread > 0 )
        if ( strcmp( field, "ATTACH=ALL" ) )
        {
            nread = -1;
            goto ret;
        }


ret:if ( nread < 0 )
    {
        if ( *num_list )
        {
            inchi_free( *num_list );
            *num_list = NULL;
        }
    }
    return nread;
}


/*
    Read V3000 stereo collection
*/
int MolfileV3000ReadStereoCollection( MOL_FMT_CTAB* ctab,
                                      char** line_ptr,
                                      int **num_list,
                                      char *pStrErr)
{
int  nread = 0;
char field[MOL_FMT_V3000_MAXFIELDLEN];
const int max_field_len=sizeof(field);
char *p_end;
int i, nnum = 0;


    *num_list = NULL;

    memset( field, 0, max_field_len );

    nread =  read_upto_delim( line_ptr, field, max_field_len, "1234567890 \t\n\v\f\r" );
    if ( strcmp(field,"(") )
        return -1;

    nread =  read_upto_delim( line_ptr, field, max_field_len, " \t\n\v\f\r" );

    nnum = strtol( field, &p_end, 10 );

    if ( p_end==field )
        return -1; /* paranoia */
    if ( nnum < 0 )
        return -1;


    *num_list = (int *) calloc( nnum + 3 , sizeof(int) );


    if ( !*num_list )
    {
        nread = -1;
        goto ret;
    }

    (*num_list)[0] = -1;    /* reserved, may be filled by caller */
    (*num_list)[1] = nnum;

    for ( i=2; i<nnum+2; i++)
    {
            if ( 0 > MolfileV3000ReadField( &((*num_list)[i]), MOL_FMT_INT_DATA, line_ptr ) )
        {
            nread = -1;
            goto ret;
        }
    }


    /* ')' should have been consumed  by strtol */


ret:if ( nread < 0 )
    {
        if ( *num_list )
        {
            inchi_free( *num_list );
            *num_list = NULL;
        }
    }
    return nread;
}


/*
    Returns
        -1 @ error
*/
int get_V3000_input_line_to_strbuf( INCHI_IOSTREAM_STRING *buf, INCHI_IOSTREAM* inp_stream )
{
    int old_used, crlf2lf = 1, preserve_lf = 0;

    inchi_strbuf_reset( buf );

    old_used = buf->nUsedLength;
    while ( 1 )
    {
        inchi_strbuf_addline( buf, inp_stream, crlf2lf, preserve_lf );

        if ( buf->nUsedLength - old_used < 8 )
            return -1;
        if ( strncmp( buf->pStr + old_used, "M  V30 ", 7) )
            return -1;

        memmove( (void*) (buf->pStr+old_used), (void*) (buf->pStr+old_used+7), buf->nUsedLength - old_used + 1 );
        buf->nUsedLength-= 7;

        if ( buf->pStr[buf->nUsedLength-1] != '-' )
            break;
        buf->pStr[--buf->nUsedLength] = '\0';

        old_used = buf->nUsedLength;
    }




#if 0
    p = check and normalize ( p )

    pp = empty
    while ( p is proper )
    {

        pp = pp + p

        if pp ends with '-'
            /* expect continuation to next line */
            in pp, replace trail '-' with space
        else
            break
        p = get new p
    }

    if pp is empty
        return NULL
    else
        return pp


    if ( !p )
        return NULL;

    while ( p )
    {
        len = strlen(p);
        if ( len<7 )                        break;
        if ( strncmp( p, "M  V30 ", 7) )    break;
        ITRACE_( "\n\nV3000 line, original   \'%-s\'", p );
        p+= 7;
        len = normalize_string( p );
        ITRACE_( "\nV3000 line, normalized \'%-s\'", p );
        //pp = pp + p;
        break;
    }

#endif

    return buf->nUsedLength;
}
