/*
 * International Chemical Identifier (InChI)
 * Version 1
 * Software version 1.02
 * January 10, 2009
 * Developed at NIST
 *
 * The InChI library and programs are free software developed under the
 * auspices of the International Union of Pure and Applied Chemistry (IUPAC);
 * you can redistribute this software and/or modify it under the terms of 
 * the GNU Lesser General Public License as published by the Free Software 
 * Foundation:
 * http://www.opensource.org/licenses/lgpl-license.php
 */


#include "mode.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <errno.h>
#include <limits.h>
#include <float.h>
#include <math.h>
#include "inpdef.h"
#include "ichi.h"
#include "strutil.h"
#include "util.h"
#include "ichierr.h"
#include "ichimain.h"
#include "extr_ct.h"
#include "ichi_io.h"

#include "ichicomp.h"
#include "inchi_api.h"



/*^^^ */

static int INCHI_DECL GetINCHI( inchi_Input *inp, inchi_Output *out );
static void INCHI_DECL FreeINCHI ( inchi_Output *out );
static void INCHI_DECL FreeStructFromINCHI( inchi_OutputStruct *out );
static int INCHI_DECL GetStructFromINCHI( inchi_InputINCHI *inpInChI, 
                                         inchi_OutputStruct *outStruct );
static int INCHI_DECL GetINCHIfromINCHI( inchi_InputINCHI *inpInChI, inchi_Output *out );

/*^^^ */





/*************************************************************************
 *
 *   Local prototypes
 *
 *************************************************************************/


int SetAtomProperties( inp_ATOM *at, MOL_COORD *szCoord, inchi_Atom *ati,
                       int a1, int *nDim, char *pStrErr, int *err );
int SetBondProperties( inp_ATOM *at, inchi_Atom *ati, int a1, int j,
                       int nNumAtoms, int *nNumBonds, char *pStrErr, int *err );
int SetAtomAndBondProperties( inp_ATOM *at, inchi_Atom *ati, int a1,
                              int bDoNotAddH, char *pStrErr, int *err );
void SetNumImplicitH(inp_ATOM* at, int num_atoms);
int Extract0DParities( inp_ATOM *at, int nNumAtoms, inchi_Stereo0D *stereo0D,
                       int num_stereo0D, char *pStrErr, int *err );
int parse_options_string ( char *cmd, const char *argv[], int maxargs );

int InpAtom0DToInchiAtom( inp_ATOM *at, int num_atoms, inchi_OutputStruct *outStruct );


int ExtractOneStructure( STRUCT_DATA *sd, INPUT_PARMS *ip, char *szTitle,
         inchi_Input *inp, 
         INCHI_IOSTREAM *log_file, INCHI_IOSTREAM *output_file, INCHI_IOSTREAM *prb_file,
         ORIG_ATOM_DATA *orig_inp_data, long *num_inp, char *pStr, int nStrLen );

/*************************************************************************/

int bInterrupted = 0;

/********************************************************************
 *
 * INCHI API: DEALLOCATE INCHI OUTPUT
 *
 ********************************************************************/

void INCHI_DECL FreeStdINCHI( inchi_Output *out )
{
#ifdef ONLY_STDINCHI_STDKEY
    FreeINCHI( out );
#else
#endif
}


static void INCHI_DECL FreeINCHI( inchi_Output *out )
{
    if ( out->szInChI ) {
        inchi_free( out->szInChI );
    }
    if ( out->szLog ) {
        inchi_free( out->szLog );
    }
    if ( out->szMessage ) {
        inchi_free( out->szMessage );
    }
    memset( out, 0, sizeof(*out) );
}



void INCHI_DECL FreeStructFromStdINCHI( inchi_OutputStruct *out )
{
#ifdef ONLY_STDINCHI_STDKEY
    FreeStructFromINCHI( out );
#else
#endif
}


/*******************************************************************/
static void INCHI_DECL FreeStructFromINCHI( inchi_OutputStruct *out )
{
    if ( out->atom ) {
        inchi_free( out->atom );
    }
    if ( out->stereo0D ) {
        inchi_free( out->stereo0D );
    }
    if ( out->szLog ) {
        inchi_free( out->szLog );
    }
    if ( out->szMessage ) {
        inchi_free( out->szMessage );
    }
    memset( out, 0, sizeof(*out) );
}
/********************************************************************/
#define INCHI_MAX_NUM_ARG 32
/********************************************************************
 *
 *    INCHI API: MAIN ENTRY POINT
 *
 ********************************************************************/

int bLibInchiSemaphore = 0;


int INCHI_DECL GetStdINCHI( inchi_Input *inp, inchi_Output *out )
{
#ifdef ONLY_STDINCHI_STDKEY
    return GetINCHI( inp, out );
#else
    /* to be filled */
    return inchi_Ret_ERROR;
#endif
}




static int INCHI_DECL GetINCHI( inchi_Input *inp, inchi_Output *out )
{
    STRUCT_DATA struct_data;
    STRUCT_DATA *sd = &struct_data;       
    char szTitle[MAX_SDF_HEADER+MAX_SDF_VALUE+256];

    int i;
    long num_inp, num_err;
    char      szSdfDataValue[MAX_SDF_VALUE+1];
    PINChI2     *pINChI[INCHI_NUM];
    PINChI_Aux2 *pINChI_Aux[INCHI_NUM];

    unsigned long  ulDisplTime = 0;    /*  infinite, milliseconds */
    unsigned long  ulTotalProcessingTime = 0;

    INPUT_PARMS inp_parms;
    INPUT_PARMS *ip = &inp_parms;

    ORIG_ATOM_DATA OrigAtData; /* 0=> disconnected, 1=> original */
    ORIG_ATOM_DATA *orig_inp_data = &OrigAtData;
    ORIG_ATOM_DATA PrepAtData[2]; /* 0=> disconnected, 1=> original */
    ORIG_ATOM_DATA *prep_inp_data = PrepAtData;
    int             bReleaseVersion = bRELEASE_VERSION;
    const int nStrLen = 64000;
    char *pStr = NULL;
    int   nRet = 0, nRet1;

    STRUCT_FPTRS *pStructPtrs = NULL;

#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
    int  num_repeat = REPEAT_ALL;
#endif

    const char *argv[INCHI_MAX_NUM_ARG+1];
    int   argc;
    char *szOptions = NULL;

    INCHI_IOSTREAM inchi_file[3], *output_file = inchi_file, *log_file = inchi_file+1;
    INCHI_IOSTREAM prb_file0, *prb_file = &prb_file0;


    if ( bLibInchiSemaphore ) {  /* does not work properly under sufficient stress */
        return inchi_Ret_BUSY;
    }
    bLibInchiSemaphore = 1;

#if( TRACE_MEMORY_LEAKS == 1 )
    _CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
/* for execution outside the VC++ debugger uncomment one of the following two */
#ifdef MY_REPORT_FILE 
   _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_WARN, MY_REPORT_FILE );
   _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ERROR, MY_REPORT_FILE );
   _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ASSERT, MY_REPORT_FILE );
#else
    _CrtSetReportMode(_CRT_WARN | _CRT_ERROR, _CRTDBG_MODE_DEBUG);
#endif

#if ( !defined(__STDC__) || __STDC__ != 1 )
    /* turn on floating point exceptions */
    {
        /* Get the default control word. */
        int cw = _controlfp( 0,0 );

        /* Set the exception masks OFF, turn exceptions on. */
        /*cw &=~(EM_OVERFLOW|EM_UNDERFLOW|EM_INEXACT|EM_ZERODIVIDE|EM_DENORMAL);*/
        cw &=~(EM_OVERFLOW|EM_UNDERFLOW|EM_ZERODIVIDE|EM_DENORMAL);

        /* Set the control word. */
        _controlfp( cw, MCW_EM );
 
    }
#endif
#endif




    szTitle[0] = '\0';

#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
repeat:
    inchi_ios_close(output_file);
    inchi_ios_close(log_file);
    inchi_ios_close(prb_file);
    pStr = NULL;
#endif

    /*^^^ Initialize internal for this function output streams as string buffers */
    inchi_ios_init(output_file, INCHI_IOSTREAM_STRING, NULL);
    inchi_ios_init(log_file, INCHI_IOSTREAM_STRING, NULL);
    inchi_ios_init(prb_file, INCHI_IOSTREAM_STRING, NULL);


    num_inp    = 0;
    num_err    = 0;
    sd->bUserQuit  = 0;

    /* clear original input structure */
    memset( pINChI,     0, sizeof(pINChI    ) );
    memset( pINChI_Aux, 0, sizeof(pINChI_Aux) );
    memset( sd,         0, sizeof(*sd) );
    memset( ip,         0, sizeof(*ip) );
    memset( orig_inp_data     , 0,   sizeof( *orig_inp_data  ) );
    memset( prep_inp_data     , 0, 2*sizeof( *prep_inp_data  ) );
    memset( szSdfDataValue    , 0, sizeof( szSdfDataValue    ) );

    if ( !out ) {
        nRet = _IS_ERROR;
        goto exit_function;
    }
    memset( out, 0, sizeof(*out) );

    /* options */
    if ( inp && inp->szOptions ) {
        szOptions = (char*)inchi_malloc( strlen(inp->szOptions) + 1 );
        if ( szOptions ) {
            strcpy( szOptions, inp->szOptions );
            argc = parse_options_string ( szOptions, argv, INCHI_MAX_NUM_ARG );
        } else {
            nRet = _IS_FATAL;
            goto translate_RetVal; /* emergency exit */
        }
    } else {
        argc = 1;
        argv[0] = "";
        argv[1] = NULL;
    }

    if ( argc == 1
#ifdef INCHI_LIBRARY
        && (!inp || inp->num_atoms <= 0 || !inp->atom)
#endif        
        || argc==2 && ( argv[1][0]==INCHI_OPTION_PREFX ) &&
        (!strcmp(argv[1]+1, "?") || !stricmp(argv[1]+1, "help") ) ) {
        HelpCommandLineParms(log_file);
        out->szLog = log_file->s.pStr;
        memset( log_file, 0, sizeof(*log_file) );
        nRet = _IS_EOF;
        goto translate_RetVal;
    }

    nRet1 = ReadCommandLineParms( argc, argv, ip, szSdfDataValue, &ulDisplTime, bReleaseVersion, log_file );
    if ( szOptions ) {
        inchi_free( szOptions );
        szOptions = NULL;
    }
    /* INChI DLL specific */
    ip->bNoStructLabels = 1;

    if ( 0 > nRet1 ) {
        nRet = _IS_FATAL;
        goto exit_function;
    }
    if ( ip->bNoStructLabels ) {
        ip->pSdfLabel = NULL;
        ip->pSdfValue = NULL;
    } else
    if ( ip->nInputType == INPUT_INCHI_XML || ip->nInputType == INPUT_INCHI_PLAIN  || ip->nInputType == INPUT_CMLFILE ) {
        /* the input may contain both the header and the label of the structure */
        if ( !ip->pSdfLabel ) 
            ip->pSdfLabel  = ip->szSdfDataHeader;
        if ( !ip->pSdfValue )
            ip->pSdfValue  = szSdfDataValue;
    }
    PrintInputParms( log_file, ip );
    if ( !(pStr = (char*)inchi_malloc(nStrLen))) {
        inchi_ios_eprint( log_file, "Cannot allocate output buffer. Terminating\n");
        goto exit_function;
    }
    pStr[0] = '\0';



    /**********************************************************************************************/
    /*  Main cycle */
    /*  read input structures and create their INChI */
    ulTotalProcessingTime = 0;

    if ( pStructPtrs ) {
        memset ( pStructPtrs, 0, sizeof(pStructPtrs[0]) );
    }

    /* === possible improvement: convert inp to orig_inp_data ==== */
    if ( !sd->bUserQuit && !bInterrupted )
    {
        if ( ip->last_struct_number && num_inp >= ip->last_struct_number ) {
            nRet = _IS_EOF; /*  simulate end of file */
            goto exit_function;
        }

        nRet = ExtractOneStructure( sd, ip, szTitle, inp, log_file, output_file, prb_file,
                                orig_inp_data, &num_inp, pStr, nStrLen );

        if ( pStructPtrs ) {
            pStructPtrs->cur_fptr ++;
        }

#ifndef INCHI_LIBRARY
        if ( sd->bUserQuit ) {
            break;
        }
#endif
        switch ( nRet ) {
        case _IS_FATAL:
            num_err ++;
            goto exit_function;
        case _IS_EOF:
            goto exit_function;
        case _IS_ERROR:
            num_err ++;
            goto exit_function;
#ifndef INCHI_LIBRARY
        case _IS_SKIP:
            continue;
#endif
        }

        /* create INChI for each connected component of the structure and optionally display them */
        /* output INChI for the whole structure */
        nRet1 = ProcessOneStructure( sd, ip, szTitle, pINChI, pINChI_Aux,
                                     NULL, /* inp_file is not necessary as all input is already saved in 'ip' */
                                     log_file, output_file, prb_file,
                                     orig_inp_data, prep_inp_data,
                                     num_inp, pStr, nStrLen );

        /*  free INChI memory */
        FreeAllINChIArrays( pINChI, pINChI_Aux, sd->num_components );
        
        /* free structure data */
        FreeOrigAtData( orig_inp_data );
        FreeOrigAtData( prep_inp_data );
        FreeOrigAtData( prep_inp_data+1 );

        ulTotalProcessingTime += sd->ulStructTime;
        nRet = inchi_max(nRet, nRet1);
        switch ( nRet ) {
        case _IS_FATAL:
            num_err ++;
            goto exit_function;
        case _IS_ERROR:
            num_err ++;
#ifndef INCHI_LIBRARY
            continue;
#endif
        }
    }

exit_function:
    if ( (ip->bINChIOutputOptions & INCHI_OUT_XML) && sd->bXmlStructStarted > 0 ) {
        if ( !OutputINChIXmlStructEndTag( output_file, pStr, nStrLen, 1 ) ) {
            inchi_ios_eprint( log_file, "Cannot create end xml tag for structure #%d.%s%s%s%s Terminating.\n", num_inp, SDF_LBL_VAL(ip->pSdfLabel,ip->pSdfValue) );
            sd->bXmlStructStarted = -1; /*  do not repeat same message */
        }
    }


    if ( (ip->bINChIOutputOptions & INCHI_OUT_XML) && ip->bXmlStarted ) {
        OutputINChIXmlRootEndTag( output_file );
        ip->bXmlStarted = 0;
    }

    
    /* avoid memory leaks in case of fatal error */
    if ( pStructPtrs && pStructPtrs->fptr ) {
        inchi_free( pStructPtrs->fptr );
    }

    /*  free INChI memory */
    FreeAllINChIArrays( pINChI, pINChI_Aux, sd->num_components );
    /* free structure data */
    FreeOrigAtData( orig_inp_data );
    FreeOrigAtData( prep_inp_data );
    FreeOrigAtData( prep_inp_data+1 );

#if( ADD_CMLPP == 1 )
        /* BILLY 8/6/04 */
        /* free CML memory */
        FreeCml ();
        FreeCmlDoc( 1 );
#endif

    if ( pStr ) {
        inchi_free( pStr );
    }

    for ( i = 0; i < MAX_NUM_PATHS; i ++ ) {
        if ( ip->path[i] ) {
            inchi_free( (char*) ip->path[i] ); /*  cast deliberately discards 'const' qualifier */
            ip->path[i] = NULL;
        }
    }

    SetBitFree( );


#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
    if ( num_repeat-- > 0 ) {
        goto repeat;
    }
#endif


    /* output */
    if ( sd->pStrErrStruct[0] ) {
        if ( out && (out->szMessage = (char *)inchi_malloc( strlen(sd->pStrErrStruct) + 1 )) ) {
            strcpy( out->szMessage, sd->pStrErrStruct );
        }
    }
    if ( output_file->s.pStr && output_file->s.nUsedLength > 0 && out ) {
        char *p;
        out->szInChI   = output_file->s.pStr;
        out->szAuxInfo = NULL;
        if ( !(INCHI_OUT_SDFILE_ONLY & ip->bINChIOutputOptions ) ) /* do not remove last LF from SDF output - 2008-12-23 DT */
        for ( p = strchr(out->szInChI, '\n'); p; p = strchr(p+1, '\n') ) {
            if ( !memcmp( p, "\nAuxInfo", 8 ) ) {
                *p = '\0';            /* remove LF after INChI */
                out->szAuxInfo = p+1; /* save pointer to AuxInfo */
            } else
            if ( out->szAuxInfo || !p[1]) {   /* remove LF after aux info or from the last char */
                *p = '\0';
                break;
            }
        }
       output_file->s.pStr = NULL;
    }


    if ( log_file->s.pStr && log_file->s.nUsedLength > 0 ) {
        while ( log_file->s.nUsedLength && '\n' == log_file->s.pStr[log_file->s.nUsedLength-1] ) {
            log_file->s.pStr[-- log_file->s.nUsedLength]  = '\0'; /* remove last LF */
        }
        if ( out ) {
            out->szLog = log_file->s.pStr;
            log_file->s.pStr = NULL;
        }
    }

    
    
translate_RetVal:

    /* Close inernal I/O streams */
    inchi_ios_close(log_file);
    inchi_ios_close(output_file);
    inchi_ios_close(prb_file);
    
    switch (nRet) { 
    case _IS_SKIP   : nRet = inchi_Ret_SKIP   ; break; /* not used in INChI dll */
    case _IS_EOF    : nRet = inchi_Ret_EOF    ; break; /* no structural data has been provided */
    case _IS_OKAY   : nRet = inchi_Ret_OKAY   ; break; /* Success; break; no errors or warnings */
    case _IS_WARNING: nRet = inchi_Ret_WARNING; break; /* Success; break; warning(s) issued */
    case _IS_ERROR  : nRet = inchi_Ret_ERROR  ; break; /* Error: no INChI has been created */
    case _IS_FATAL  : nRet = inchi_Ret_FATAL  ; break; /* Severe error: no INChI has been created (typically; break; memory allocation failed) */
    case _IS_UNKNOWN:
    default         : nRet = inchi_Ret_UNKNOWN; break; /* Unlnown program error */
    }
    bLibInchiSemaphore = 0;

    return nRet;
}


/*************************************************************************/
/******************************** from readmol.c *************************/
/*************************************************************************/
int AddMOLfileError( char *pStrErr, const char *szMsg )
{
    if ( pStrErr && szMsg && szMsg[0] ) {
        int lenStrErr = strlen( pStrErr );
        int lenMsg    = strlen( szMsg );
        char *p = strstr( pStrErr, szMsg );
        if ( p && (p==pStrErr || *(p-1) == ' ' && (*(p-2) == ';' || *(p-2) == ':' )) &&
                  (p+lenMsg == pStrErr+lenStrErr || 
                  p[lenMsg] == ';' && p[lenMsg+1] == ' ' ||
                  p[lenMsg-1]==':' && p[lenMsg]==' ') ) {
            return 1; /*  reject duplicates */
        }
        if ( lenStrErr + lenMsg + 2*(lenStrErr > 0) < STR_ERR_LEN ) {
            /*  enough room to add */
            if (lenStrErr > 0) {
                if ( pStrErr[lenStrErr-1] != ':' ) {
                    strcat( pStrErr, ";" );
                }
                strcat( pStrErr, " " );
            }
            strcat( pStrErr, szMsg );
            return 1;
        }
        /*  no room */
        if ( strstr( pStrErr, "..." ) ) {
            return 0; /*  no room mark has already been set */
        }
        if ( lenStrErr + 3 < STR_ERR_LEN ) {
            strcat( pStrErr, "..." );
        }
    }
    return 0;
}
/****************************************************************/
int CopyMOLfile(FILE *inp_file, long fPtrStart, long fPtrEnd, 
                FILE *prb_file,
                long lNumb)
{
    return 0; /* dummy */
}
/****************************************************************/
/************************** from mol2atom.c *********************/
/****************************************************************/
void SetNumImplicitH(inp_ATOM* at, int num_atoms)
{
    int bNonMetal;
    int a1/*, n1*/;

    /* special valences */
    for ( bNonMetal = 0; bNonMetal < 2; bNonMetal ++ ) {
        for ( a1 = 0; a1 < num_atoms; a1 ++ ) {
            int bHasMetalNeighbor /*, j*/;
            if ( bNonMetal != is_el_a_metal( at[a1].el_number ) ) {
                continue; /* first process all metals, after that all non-metals */
            }

            bHasMetalNeighbor = 0;
            /***********************************************************************
             *  Set number of hydrogen atoms
             */
            at[a1].num_H = get_num_H( at[a1].elname, at[a1].num_H, at[a1].num_iso_H,
                                    at[a1].charge, at[a1].radical,
                                    at[a1].chem_bonds_valence,
                                    0, /* instead of valence entered by the user: it does not exist here*/
                                    (at[a1].at_type & 1)  /* bAliased */,
                                    !(at[a1].at_type & 2) /* bDoNotAddH */,
                                    bHasMetalNeighbor );
            at[a1].at_type = 0;
        }
    }
}

/******************************************************************************************************/
void FreeInpAtom( inp_ATOM **at )
{
    if ( at && *at ) {
        inchi_free( *at );
        *at = NULL;
    }
}
/******************************************************************************************************/
inp_ATOM *CreateInpAtom( int num_atoms )
{
    /*
    void *p = inchi_calloc(num_atoms, sizeof(inp_ATOM) );
    if ( p == (void*)0x009143A8 ) {
        int stop = 1;
    }
    return (inp_ATOM* )p;
    */
   return (inp_ATOM* ) inchi_calloc(num_atoms, sizeof(inp_ATOM) );
}
/******************************************************************************************************/
void FreeInpAtomData( INP_ATOM_DATA *inp_at_data )
{
    if ( inp_at_data ) {
        if ( inp_at_data->at ) {
            FreeInpAtom( &inp_at_data->at );
        }
        if ( inp_at_data->at_fixed_bonds ) {
            FreeInpAtom( &inp_at_data->at_fixed_bonds );
        }
        memset( inp_at_data, 0, sizeof(*inp_at_data) );
    }
}
/******************************************************************************************************/
int CreateInpAtomData( INP_ATOM_DATA *inp_at_data, int num_atoms, int create_at_fixed_bonds )
{
    FreeInpAtomData( inp_at_data );
    if ( (inp_at_data->at = CreateInpAtom( num_atoms )) &&
         (!create_at_fixed_bonds || (inp_at_data->at_fixed_bonds = CreateInpAtom( num_atoms) ) ) ) {
        inp_at_data->num_at = num_atoms;
        return 1;
    }
    FreeInpAtomData( inp_at_data );
    return 0;
}
/******************************************************************************************************/
void FreeCompAtomData( COMP_ATOM_DATA *inp_at_data )
{
    FreeInpAtom( &inp_at_data->at );
    if ( inp_at_data->nOffsetAtAndH )
        inchi_free( inp_at_data->nOffsetAtAndH );
    memset( inp_at_data, 0, sizeof(*inp_at_data) );
}
/******************************************************************************************************/
#if( TEST_RENUMB_ATOMS == 1 )  /*  { */
/******************************************************************************************************/
int CopyInpAtomData( INP_ATOM_DATA *dest_inp_at_data, INP_ATOM_DATA *src_inp_at_data )
{
    int ret = 1;
    if ( !dest_inp_at_data->at  || dest_inp_at_data->num_at != src_inp_at_data->num_at ) {
        ret = CreateInpAtomData( dest_inp_at_data, src_inp_at_data->num_at, (NULL != src_inp_at_data->at_fixed_bonds) );
    } else {
        inp_ATOM *at  = dest_inp_at_data->at;  /*  save ptr to already allocated memory */
        inp_ATOM *at2 = dest_inp_at_data->at_fixed_bonds;
        *dest_inp_at_data = *src_inp_at_data; /*  copy all other (scalar) data */
        dest_inp_at_data->at = at;            /*  restore ptr to already allocated memory */
        dest_inp_at_data->at_fixed_bonds = at2;
    }
    if ( ret ) {
        memcpy( dest_inp_at_data->at, src_inp_at_data->at,
                src_inp_at_data->num_at*sizeof(dest_inp_at_data->at[0]) );
        if ( dest_inp_at_data->at_fixed_bonds && src_inp_at_data->at_fixed_bonds ) {
            memcpy( dest_inp_at_data->at_fixed_bonds, src_inp_at_data->at_fixed_bonds,
                src_inp_at_data->num_at*sizeof(dest_inp_at_data->at_fixed_bonds[0]) );
        }
    }
    return ret;
}
/******************************************************************************************************/
void RenumbInpAtomData( INP_ATOM_DATA *dest_inp_at_data, INP_ATOM_DATA *src_inp_at_data, AT_RANK *new_ord )
{
    int j, n, m, val;
#if( TEST_RENUMB_NEIGH == 1 )
    int i, k;
#endif
    int       num_atoms = src_inp_at_data->num_at;
    inp_ATOM *dest_at   = dest_inp_at_data->at;
    for ( n = 0; n < num_atoms; n ++ ) {
        m = new_ord[n];
        dest_at[m] = src_inp_at_data->at[n];
        dest_at[m].orig_compt_at_numb = (AT_NUMB)(m+1);  /*  new ordering number within the component */
        val = dest_at[m].valence;
        for ( j = 0; j < val; j ++ ) {
            dest_at[m].neighbor[j] = new_ord[dest_at[m].neighbor[j]];
        }
#if( TEST_RENUMB_NEIGH == 1 )
        for ( i = 0; i < 3*val; i ++ ) {
            j = (rand() * val) / (RAND_MAX+1);
            k = (rand() * val) / (RAND_MAX+1);
            if ( j >= val || k >= val || j == k ) {
                continue;
            }
            inchi_swap( (char*)&dest_at[m].neighbor[j],    (char*)&dest_at[m].neighbor[k],    sizeof(dest_at[0].neighbor[0]) );
            inchi_swap( (char*)&dest_at[m].bond_stereo[j], (char*)&dest_at[m].bond_stereo[k], sizeof(dest_at[0].bond_stereo[0]) );
            inchi_swap( (char*)&dest_at[m].bond_type[j],   (char*)&dest_at[m].bond_type[k],   sizeof(dest_at[0].bond_type[0]) );
            /* adjust stereo bond links */
            if ( dest_at[m].sb_parity[0] ) {
                int a;
                for ( a = 0; a < MAX_NUM_STEREO_BONDS && dest_at[m].sb_parity[a]; a ++ ) {
                    
                    if ( k == (int)dest_at[m].sb_ord[a] ) {
                        dest_at[m].sb_ord[a] = j;
                    } else
                    if ( j == (int)dest_at[m].sb_ord[a] ) {
                        dest_at[m].sb_ord[a] = k;
                    }

                    if ( k == (int)dest_at[m].sn_ord[a] ) {
                        dest_at[m].sn_ord[a] = j;
                    } else
                    if ( j == (int)dest_at[m].sn_ord[a] ) {
                        dest_at[m].sn_ord[a] = k;
                    }
                }
            }
        }
#endif
    }

}
/******************************************************************************************************/
void MakeNewOrd( int num_atoms, AT_RANK *new_ord )
{
    int i, j, k;
    for ( i = 0; i < 3*num_atoms; i ++ ) {
        j = (rand() * num_atoms) / (RAND_MAX+1);
        k = (rand() * num_atoms) / (RAND_MAX+1);
        if ( j >= num_atoms || k >= num_atoms || j == k ) {
            continue;
        }
        inchi_swap( (char*)&new_ord[j], (char*)&new_ord[k], sizeof(new_ord[0]) );
    }
}
#endif /*  } TEST_RENUMB_ATOMS == 1  */
/**********************************************************************************/
void FreeOrigAtData( ORIG_ATOM_DATA *orig_at_data )
{
    if ( !orig_at_data )
        return;
    FreeInpAtom( &orig_at_data->at );
    if ( NULL != orig_at_data->nCurAtLen ) {
        inchi_free( orig_at_data->nCurAtLen );
    }
    if ( NULL != orig_at_data->nOldCompNumber ) {
        inchi_free( orig_at_data->nOldCompNumber );
    }
    if ( NULL != orig_at_data->szCoord ) {
        inchi_free( orig_at_data->szCoord );
    }
    if ( NULL != orig_at_data->nEquLabels ) {
        inchi_free( orig_at_data->nEquLabels );
    }
    if ( NULL != orig_at_data->nSortedOrder ) {
        inchi_free( orig_at_data->nSortedOrder );
    }
    memset( orig_at_data, 0, sizeof(*orig_at_data) );
}
/********************************************************************/

#define REPEAT_ALL  0
/********************************************************************/
int parse_options_string ( char *cmd, const char *argv[], int maxargs )
{
    char    *p;
    char    *pArgCurChar;
    int      bInsideQuotes;
    int      bCopyCharToArg;
    int      nNumBackSlashes;
    int      i;

    i = 0;
    argv[i++] = ""; /* zeroth argument is not used */
    p = cmd;
    bInsideQuotes = 0;

    /* arguments, one by one */
    while( i < maxargs-1 ) {
        /* bypass spaces */
        while ( *p == ' ' || *p == '\t' )
            p ++;
        if ( !*p )
            break;
        /* scan an argument */
        argv[i++] = pArgCurChar = p;     /* store preliminary ptr to arg */
        while ( 1 ) {
            bCopyCharToArg = 1;
            nNumBackSlashes = 0;
            while (*p == '\\') {
                ++p;
                ++nNumBackSlashes;
            }
            /* each pair of backslashes => one backslash; one more backslash => literal quote */
            if ( *p == '\"' ) {
                /* one " found */
                if ( nNumBackSlashes % 2 == 0 ) {
                    if (bInsideQuotes) {
                        if (*(p+1) == '\"') {
                            p++;
                        } else {
                            bCopyCharToArg = 0;
                        }
                    } else {
                        bCopyCharToArg = 0;
                    }
                    bInsideQuotes = !bInsideQuotes;
                }
                nNumBackSlashes /= 2;          /* divide nNumBackSlashes by two */
            }
            while (nNumBackSlashes--) {
                *pArgCurChar++ = '\\';
            }
            if (!*p) {
                break;
            }
            if (!bInsideQuotes && (*p == ' ' || *p == '\t')) {
                p ++; 
                /* move to the next char because this char may become
                 * zero due to  *pArgCurChar++ = '\0'; line below */
                break;
            }
            if (bCopyCharToArg) {
                *pArgCurChar++ = *p;
            }
            ++p;
        }
        *pArgCurChar++ = '\0';  /* argument zero termination */
    }
    /* The last argument is NULL */
    argv[i] = NULL;
    return i;
}
/*****************************************************************/
#define MIN_BOND_LENGTH   (1.0e-6)
int SetAtomProperties( inp_ATOM *at, MOL_COORD *szCoord, inchi_Atom *ati, int a1, int *nDim, char *pStrErr, int *err )
{        
    S_CHAR      cRadical;
    /* element, check later */

    strcpy( at[a1].elname, ati[a1].elname );

    /* charge */

    at[a1].charge = ati[a1].charge;

    /* radical */

    switch ( ati[a1].radical ) {
    case   INCHI_RADICAL_NONE:
        cRadical = 0;
        break;
    case   INCHI_RADICAL_SINGLET:
#if( SINGLET_IS_TRIPLET == 1) /* 'singlet' means two electrons make a lone pair instead of 2 bonds*/
                              /* its effect on valence is same as the effect of a triplet */
        cRadical = RADICAL_TRIPLET;
#else
        cRadical = RADICAL_SINGLET;
#endif
        break;
    case   INCHI_RADICAL_DOUBLET:
        cRadical = RADICAL_DOUBLET;
        break;
    case   INCHI_RADICAL_TRIPLET:
        cRadical = RADICAL_TRIPLET;
        break;
    default:
        {
            char szRadicalType[16];
            int nRad = ati[a1].radical;
            while ( nRad > RADICAL_TRIPLET ) {
                nRad -= 2;
            }
            sprintf( szRadicalType, "%d->%d", ati[a1].radical, nRad );
            MOLFILE_ERR_SET (*err, 0, "Radical center type replaced:");
            MOLFILE_ERR_SET (*err, 0, szRadicalType);
            cRadical = nRad;
            if ( nRad < 0 ) {
                *err |= 8; /*  Unrecognized Radical replaced with non-radical */
            }
        }
        break;
    }
    at[a1].radical = cRadical;

    /* coordinates */
    at[a1].x = ati[a1].x;
    at[a1].y = ati[a1].y;
    at[a1].z = ati[a1].z;

    if ( szCoord ) {
        /* store text coordinates */
        char str[32];
        MOL_COORD * coord_p = szCoord + a1;
        WriteCoord( str, ati[a1].x );
        memcpy( *coord_p, str, 10 );
        WriteCoord( str, ati[a1].y );
        memcpy( *coord_p+10, str, 10 );
        WriteCoord( str, ati[a1].z );
        memcpy( *coord_p+20, str, 10 );
    }

    if ( MIN_BOND_LENGTH < fabs(ati[a1].x) || MIN_BOND_LENGTH < fabs(ati[a1].y) || MIN_BOND_LENGTH < fabs(ati[a1].z) ) {
        if ( MIN_BOND_LENGTH < fabs(ati[a1].z) ) {
            *nDim |= 3;
        } else {
            *nDim |= 2;
        }
    }

    /* orig. at. number */
    at[a1].orig_at_number = a1+1;
    return 0;
#undef MIN_BOND_LENGTH
}
/*********************************************************************/
int SetBondProperties( inp_ATOM *at, inchi_Atom *ati, int a1, int j,
                       int nNumAtoms, int *nNumBonds, char *pStrErr, int *err )
{ 
    int a2;
    S_CHAR     cBondType, cStereoType1, cStereoType2;
    AT_NUMB   *p1, *p2;
    int        n1, n2;

    /* bond type */
    switch( ati[a1].bond_type[j] ) {
    case INCHI_BOND_TYPE_SINGLE:
        cBondType = BOND_TYPE_SINGLE;
        break;
    case INCHI_BOND_TYPE_DOUBLE:
        cBondType = BOND_TYPE_DOUBLE;
        break;
    case INCHI_BOND_TYPE_TRIPLE:
        cBondType = BOND_TYPE_TRIPLE;
        break;
    case INCHI_BOND_TYPE_ALTERN:
        cBondType = BOND_TYPE_ALTERN;
        break;
    default:
        {
        char szBondType[16];
        sprintf( szBondType, "%d", ati[a1].bond_type[j] );
        MOLFILE_ERR_SET (*err, 0, "Unrecognized bond type:");
        MOLFILE_ERR_SET (*err, 0, szBondType);
        *err |= 8; /*  Unrecognized Bond type replaced with single bond */
        cBondType = BOND_TYPE_SINGLE;
        }
        break;
    }

    /* 2D stereo */

    switch( ati[a1].bond_stereo[j] ) {
    /* stereocenter-related; positive: the sharp end points to this atom  */
    case   INCHI_BOND_STEREO_NONE:
        cStereoType1 = 0;
        cStereoType2 = 0;
        break;
    case   INCHI_BOND_STEREO_SINGLE_1UP:
        cStereoType1 =  STEREO_SNGL_UP;
        cStereoType2 = -STEREO_SNGL_UP;
        break;
    case   INCHI_BOND_STEREO_SINGLE_1EITHER:
        cStereoType1 =  STEREO_SNGL_EITHER;
        cStereoType2 = -STEREO_SNGL_EITHER;
        break;
    case   INCHI_BOND_STEREO_SINGLE_1DOWN:
        cStereoType1 =  STEREO_SNGL_DOWN;
        cStereoType2 = -STEREO_SNGL_DOWN;
        break;
    /* stereocenter-related; negative: the sharp end points to the opposite atom  */
    case   INCHI_BOND_STEREO_SINGLE_2UP:
        cStereoType1 = -STEREO_SNGL_UP;
        cStereoType2 =  STEREO_SNGL_UP;
        break;
    case   INCHI_BOND_STEREO_SINGLE_2EITHER:
        cStereoType1 = -STEREO_SNGL_EITHER;
        cStereoType2 =  STEREO_SNGL_EITHER;
        break;
    case   INCHI_BOND_STEREO_SINGLE_2DOWN:
        cStereoType1 = -STEREO_SNGL_DOWN;
        cStereoType2 =  STEREO_SNGL_DOWN;
        break;
    /* stereobond-related */
    case   INCHI_BOND_STEREO_DOUBLE_EITHER:
    case  -INCHI_BOND_STEREO_DOUBLE_EITHER:
        cStereoType1 = STEREO_DBLE_EITHER;
        cStereoType2 = STEREO_DBLE_EITHER;
        break;
    default:
        {
        char szBondType[16];
        sprintf( szBondType, "%d", ati[a1].bond_stereo[j] );
        MOLFILE_ERR_SET (*err, 0, "Unrecognized bond stereo:");
        MOLFILE_ERR_SET (*err, 0, szBondType);
        *err |= 8; /*  Unrecognized Bond stereo replaced with non-stereo bond */
        cStereoType1 = 0;
        cStereoType2 = 0;
        }
        break;
    }

    /* neighbor */

    if ( ati[a1].neighbor[j] < 0 || ati[a1].neighbor[j] >= nNumAtoms ) {
        *err |= 1; /*  bond for impossible atom number(s); ignored */
        MOLFILE_ERR_SET (*err, 0, "Bond to nonexistent atom");
        goto err_exit;
    }
    a2 = (AT_NUMB) ati[a1].neighbor[j];
    if ( a2 == a1 ) {
        *err |= 1; /*  bond for impossible atom number(s); ignored */
        MOLFILE_ERR_SET (*err, 0, "Atom has a bond to itself");
        goto err_exit;
    }

    /* consistency check; locate the bond in the opposite atom */

    p1 = is_in_the_list( at[a1].neighbor, (AT_NUMB)a2, at[a1].valence );
    p2 = is_in_the_list( at[a2].neighbor, (AT_NUMB)a1, at[a2].valence );
    if ( p1 && p2 ) {
        n1 = (p1 - at[a1].neighbor);
        n2 = (p2 - at[a2].neighbor);
        if ( n1+1 < at[a1].valence && is_in_the_list( at[a1].neighbor+n1+1, (AT_NUMB)a2, at[a1].valence-n1-1 ) ||
             n2+1 < at[a2].valence && is_in_the_list( at[a2].neighbor+n2+1, (AT_NUMB)a1, at[a2].valence-n2-1 ) ) {
            MOLFILE_ERR_SET (*err, 0, "Multiple bonds between two atoms");
            *err |= 2; /*  multiple bonds between atoms */
        } else
        if ( n1 < at[a1].valence && n2 < at[a2].valence &&
             cBondType == at[a2].bond_type[n2] &&
             cBondType == at[a1].bond_type[n1] &&
             cStereoType1 == at[a1].bond_stereo[n1] &&
             cStereoType2 == at[a2].bond_stereo[n2] ) {
            /*MOLFILE_ERR_SET (*err, 0, "Duplicated bond(s) between two atoms");*/
        } else {
            MOLFILE_ERR_SET (*err, 0, "Multiple bonds between two atoms");
            *err |= 2; /*  multiple bonds between atoms */
        }
    } else
    if ( (p1 || p2) && (p1 || at[a1].valence < MAXVAL) && (p2 || at[a2].valence < MAXVAL) ) {
        n1 = p1? (p1 - at[a1].neighbor) : at[a1].valence ++;
        n2 = p2? (p2 - at[a2].neighbor) : at[a2].valence ++;
        /* the bond is present in one atom only: possibly program error */
        if ( p1 && (cBondType != at[a1].bond_type[n1] || at[a1].bond_stereo[n1] != cStereoType1 )||
             p2 && (cBondType != at[a2].bond_type[n2] || at[a2].bond_stereo[n2] != cStereoType2 ) ) {
            MOLFILE_ERR_SET (*err, 0, "Multiple bonds between two atoms");
            *err |= 2; /*  multiple bonds between atoms */
        } else {
            MOLFILE_ERR_SET (*err, 0, "Duplicated bond(s) between two atoms");
            /* warning */
        }
    } else
    if ( !p1 && !p2 && at[a1].valence < MAXVAL && at[a2].valence < MAXVAL ) {
        n1 = at[a1].valence ++;
        n2 = at[a2].valence ++;
        (*nNumBonds) ++;
    } else {
        char szMsg[64];
        *err |= 4; /*  too large number of bonds. Some bonds ignored. */
        sprintf( szMsg, "Atom '%s' has more than %d bonds",
                        at[a1].valence>= MAXVAL? at[a1].elname:at[a2].elname, MAXVAL );
        MOLFILE_ERR_SET (*err, 0, szMsg);
        goto err_exit;
    }
    
    /* store the connection */

    /* bond type */
    at[a1].bond_type[n1] =
    at[a2].bond_type[n2] = cBondType;
    /* connection */
    at[a1].neighbor[n1] = (AT_NUMB)a2;
    at[a2].neighbor[n2] = (AT_NUMB)a1;
    /* stereo */
    at[a1].bond_stereo[n1] =  cStereoType1; /*  >0: the wedge (pointed) end is at this atom */
    at[a2].bond_stereo[n2] =  cStereoType2; /*  <0: the wedge (pointed) end is at the opposite atom */
    return 0;
err_exit:
    return 1;
}
/******************************************************************/
int SetAtomAndBondProperties( inp_ATOM *at, inchi_Atom *ati, int a1,
                              int bDoNotAddH, char *pStrErr, int *err )
{
    int valence, chem_valence, num_alt_bonds, j, n1;
    int nRadical, nCharge;
    static int el_number_H = 0;
    
    if ( !el_number_H ) {
        el_number_H = get_periodic_table_number( "H" );
    }

    nRadical = nCharge = 0;
    valence = at[a1].valence;
    chem_valence = num_alt_bonds = 0;
    for ( j = 0; j < valence; j ++ ) {
        if ( at[a1].bond_type[j] <= BOND_TYPE_TRIPLE ) {
            chem_valence += at[a1].bond_type[j];
        } else {
            num_alt_bonds ++;
        }
    }
    switch( num_alt_bonds ) {
    case 0:
        break;
    case 2:
        chem_valence += 3; /* -C= */
        break;
    case 3:
        chem_valence += 4;  /* >C= */
        break;
    default:
        {
        char szMsg[64];
        *err |= 8; /*  wrong number of alt. bonds */
        sprintf( szMsg, "Atom '%s' has %d alternating bonds",
                        at[a1].elname, num_alt_bonds );
        MOLFILE_ERR_SET (*err, 0, szMsg);
        }
        break;
    }
    at[a1].chem_bonds_valence = chem_valence;

    /* aliased hydrogen atoms */
    if ( ERR_ELEM == (n1 = get_periodic_table_number( at[a1].elname ) ) ) {
        /*  Case when elname contains more than 1 element: extract number of H if possible */
        if ( extract_ChargeRadical( at[a1].elname, &nRadical, &nCharge ) ) {
            if ( nRadical && at[a1].radical && nRadical != at[a1].radical ||
                 nCharge  && at[a1].charge  && nCharge  != at[a1].charge ) {
                MOLFILE_ERR_SET (*err, 0, "Ignored charge/radical redefinition:");
                MOLFILE_ERR_SET (*err, 0, ati[a1].elname);
            } else {
                if ( nRadical )
                    at[a1].radical = nRadical;
                if ( nCharge )
                    at[a1].charge  = nCharge;
            }
        }
        at[a1].num_H = extract_H_atoms( at[a1].elname, at[a1].num_iso_H );
        if ( !at[a1].elname[0] && NUMH(at, a1) ) {
            /* alias contains only H. Added 2004-07-21, fixed 2004-07-22
             * move the heaviest isotope to the "central atom"
             * Note: this must be consistent with H-H treatment in remove_terminal_HDT()
             */
            strcpy( at[a1].elname, "H" );
            if ( NUM_ISO_H(at,a1) ) {
                for ( j = NUM_H_ISOTOPES-1; 0 <= j; j -- ) {
                    if ( at[a1].num_iso_H[j] ) {
                        at[a1].num_iso_H[j] --;
                        at[a1].iso_atw_diff = 1 + j;
                        break;
                    }
                }
            } else {
                at[a1].num_H --;
            }
        }
        if ( ERR_ELEM == (n1 = get_periodic_table_number( at[a1].elname ) ) ) {
            n1 = 0;
        }
        if ( n1 ) {
            at[a1].at_type |= 1; /* "Aliased" atom: data in the element name */
            MOLFILE_ERR_SET (*err, 0, "Parsed compound atom(s):");
            MOLFILE_ERR_SET (*err, 0, ati[a1].elname);
        }
    }

    at[a1].el_number = (U_CHAR) n1;
    if ( !n1 ) {
        *err |= 64; /*  Unrecognized aromatic bond(s) replaced with single */
        MOLFILE_ERR_SET (*err, 0, "Unknown element(s):");
        MOLFILE_ERR_SET (*err, 0, at[a1].elname);
    } else
    /* replace explicit D or T with isotopic H (added 2003-06-02) */
    if ( el_number_H == n1 && !at[a1].iso_atw_diff ) {
        switch( at[a1].elname[0] ) {
        case 'D':
            at[a1].iso_atw_diff = 2;
            mystrncpy( at[a1].elname, "H", sizeof(at->elname) );
            break;
        case 'T':
            at[a1].iso_atw_diff = 3;
            mystrncpy( at[a1].elname, "H", sizeof(at->elname) );
            break;
        case 'H':
            if ( 1 <= ati[a1].isotopic_mass ) {
                AT_NUM iso_atw_diff;
                if ( ISOTOPIC_SHIFT_FLAG - ISOTOPIC_SHIFT_MAX <=  ati[a1].isotopic_mass &&
                     ISOTOPIC_SHIFT_FLAG + ISOTOPIC_SHIFT_MAX >=  ati[a1].isotopic_mass ) {
                    /* ati[a1].isotopic_mass is isotopic iso_atw_diff + ISOTOPIC_SHIFT_FLAG */
                    iso_atw_diff = ati[a1].isotopic_mass - ISOTOPIC_SHIFT_FLAG;
                } else {
                    /* ati[a1].isotopic_mass is isotopic mass */
                    iso_atw_diff = get_atw_from_elnum( (int) at[a1].el_number );
                    iso_atw_diff = ati[a1].isotopic_mass - iso_atw_diff;
                }
                if ( iso_atw_diff >= 0 )
                    iso_atw_diff ++;
                /* reproduce Bug04: allowed non-terminal H heavier than T */
                if ( 1 <= iso_atw_diff &&
                     (at[a1].valence != 1 || iso_atw_diff <= NUM_H_ISOTOPES) ) {
                    at[a1].iso_atw_diff = (S_CHAR)iso_atw_diff;
                }
            }
        }
    } else
    /* isotopic shift */
    if ( ati[a1].isotopic_mass ) {
        AT_NUM iso_atw_diff;
        if ( ISOTOPIC_SHIFT_FLAG - ISOTOPIC_SHIFT_MAX <=  ati[a1].isotopic_mass &&
             ISOTOPIC_SHIFT_FLAG + ISOTOPIC_SHIFT_MAX >=  ati[a1].isotopic_mass ) {
            /* ati[a1].isotopic_mass is isotopic iso_atw_diff + ISOTOPIC_SHIFT_FLAG */
            iso_atw_diff = ati[a1].isotopic_mass - ISOTOPIC_SHIFT_FLAG;
        } else {
            /* ati[a1].isotopic_mass is isotopic mass */
            iso_atw_diff = get_atw_from_elnum( (int) at[a1].el_number );
            iso_atw_diff = ati[a1].isotopic_mass - iso_atw_diff;
        }
        if ( iso_atw_diff >= 0 )
            iso_atw_diff ++;
        at[a1].iso_atw_diff = (S_CHAR)iso_atw_diff;
    }

    /* add implicit hydrogen atoms flag */

    if ( ati[a1].num_iso_H[0] == -1 ) {
        if ( !bDoNotAddH ) {
            at[a1].at_type |= 2; /* user requested to add H */
        }
    } else {
        at[a1].num_H = ati[a1].num_iso_H[0];
    }
    for ( j = 0; j < NUM_H_ISOTOPES; j ++ ) {
        at[a1].num_iso_H[j] = ati[a1].num_iso_H[j+1];
    }
    if ( num_alt_bonds ) {
        /* atom has aromatic bonds AND the chemical valence is not known */
        int num_H = NUMH(at, a1);
        int chem_valence_alt = at[a1].chem_bonds_valence + num_H;
        int bUnusualValenceArom = 
            detect_unusual_el_valence( (int)at[a1].el_number, at[a1].charge,
                                        at[a1].radical, chem_valence_alt,
                                        num_H, at[a1].valence );
        int bUnusualValenceNoArom = 
            detect_unusual_el_valence( (int)at[a1].el_number, at[a1].charge,
                                        at[a1].radical, chem_valence_alt-1,
                                        num_H, at[a1].valence );
        if ( bUnusualValenceArom && !bUnusualValenceNoArom && 0 == nBondsValToMetal( at, a1) ) {
            /* typically NH in 5-member aromatic ring */
            at[a1].chem_bonds_valence --;
        }
    }

    return 0;
}
/****************************************************************************************/
int InpAtom0DToInchiAtom( inp_ATOM *at, int num_atoms, inchi_OutputStruct *outStruct )
{
    int num_stereo_centers, num_stereo_bonds, num_stereo0D, i, m, m1, m2, n, ret=0;
    /* count stereobonds, allenes. cumulenes. and stereoatoms */
    num_stereo_centers = num_stereo_bonds = ret = 0;
    
    outStruct->atom = NULL;
    outStruct->num_atoms = 0;
    outStruct->stereo0D = NULL;
    outStruct->num_stereo0D = 0;

    for ( i = 0; i < num_atoms; i ++ ) {
        if ( at[i].p_parity ) {
            /* stereocenter */
            num_stereo_centers ++;
        } else {
            for ( m = 0; m < MAX_NUM_STEREO_BONDS && at[i].sb_parity[m]; m ++ )
                ;
            num_stereo_bonds += m;
        }
    }
    num_stereo_bonds /= 2;
    num_stereo0D = num_stereo_bonds + num_stereo_centers;

    if ( num_atoms > 0 ) {
        outStruct->atom = (inchi_Atom *)inchi_calloc( num_atoms, sizeof( outStruct->atom[0] ) );
    }
    outStruct->num_atoms = num_atoms;
    if ( num_stereo0D > 0 ) {
        outStruct->stereo0D = (inchi_Stereo0D *)inchi_calloc( num_stereo0D, sizeof(outStruct->stereo0D[0]));
    }
    if ( num_atoms && !outStruct->atom || num_stereo0D > 0 && !outStruct->stereo0D ) {
        /* allocation failed */
        ret = -1;
        goto exit_function;
    }

    /* copy atom properties */
    for ( i = 0; i < num_atoms; i ++ ) {
        outStruct->atom[i].num_bonds = at[i].valence;
        for ( m = 0; m < at[i].valence; m ++ ) {
            outStruct->atom[i].bond_type[m] = at[i].bond_type[m];
            outStruct->atom[i].neighbor[m]  = at[i].neighbor[m];
        }
        outStruct->atom[i].charge = at[i].charge;
        memcpy( outStruct->atom[i].elname, at[i].elname, ATOM_EL_LEN );
        if ( at[i].iso_atw_diff ) {
            outStruct->atom[i].isotopic_mass = ISOTOPIC_SHIFT_FLAG + (at[i].iso_atw_diff > 0? at[i].iso_atw_diff-1 : at[i].iso_atw_diff);
        }
        outStruct->atom[i].num_iso_H[0] = at[i].num_H;
        for ( m = 0; m < NUM_H_ISOTOPES; m ++ ) {
            outStruct->atom[i].num_iso_H[m+1] = at[i].num_iso_H[m];
        }
        outStruct->atom[i].radical = at[i].radical;
    }
    /* stereo */
    for ( i = n = 0; i < num_atoms; i ++ ) {
        if ( at[i].p_parity ) {
            if ( n < num_stereo0D ) {
                outStruct->stereo0D[n].central_atom = i;
                outStruct->stereo0D[n].parity       = at[i].p_parity;
                outStruct->stereo0D[n].type         = INCHI_StereoType_Tetrahedral;
                for ( m = 0; m < MAX_NUM_STEREO_ATOM_NEIGH; m ++ ) {
                    outStruct->stereo0D[n].neighbor[m] = at[i].p_orig_at_num[m] - 1;
                }
                n ++;
            } else {
                ret |= 1;
                break;
            }
        } else {
            for ( m1 = 0; m1 < MAX_NUM_STEREO_BONDS && at[i].sb_parity[m1]; m1 ++ ) {
                
                /* find the opposite atom at the other end of double bond, allene, or cumulene */
                int chain[12], len = 0, nxt_neigh, nxt, cur;
                cur = chain[len++] = i;
                nxt_neigh = at[cur].sb_ord[m1];
                
                do {
                    /* add next atom */
                    chain[len ++] = nxt = at[cur].neighbor[nxt_neigh];
                    nxt_neigh = (at[nxt].neighbor[0] == cur);
                    cur = nxt;
                    /* find nxt_neigh */
                } while ( !at[cur].sb_parity[0] && len < 12 && at[cur].valence == 2 );

                if ( at[cur].sb_parity[0] && len <= 4 && i < cur /* count bonds only one time */ ) {
                    /* double bond, cumulene, or allene has been found */
                    for ( m2 = 0; m2 < MAX_NUM_STEREO_BONDS && at[cur].sb_parity[m2]; m2 ++ ) {
                        if ( chain[len-2] == at[cur].neighbor[(int)at[cur].sb_ord[m2]] ) {
                            if ( n < num_stereo0D ) {
                                int parity1 = at[i].sb_parity[m1];
                                int parity2 = at[cur].sb_parity[m2];
                                int parity;
                                if ( (INCHI_PARITY_ODD == parity1 || INCHI_PARITY_EVEN == parity1) &&
                                    (INCHI_PARITY_ODD == parity2 || INCHI_PARITY_EVEN == parity2) ) {
                                    /* well-defined parity */
                                    parity = (parity1==parity2)? INCHI_PARITY_EVEN : INCHI_PARITY_ODD;
                                } else {
                                    parity = inchi_max(parity1, parity2);
                                }
                                outStruct->stereo0D[n].central_atom = (len==3)? chain[1] : NO_ATOM;
                                outStruct->stereo0D[n].parity       = parity;
                                outStruct->stereo0D[n].type         = len == 3? INCHI_StereoType_Allene : INCHI_StereoType_DoubleBond;
                                outStruct->stereo0D[n].neighbor[0]  = at[i].sn_orig_at_num[m1]-1;
                                outStruct->stereo0D[n].neighbor[1]  = i;
                                outStruct->stereo0D[n].neighbor[2]  = cur;
                                outStruct->stereo0D[n].neighbor[3]  = at[cur].sn_orig_at_num[m2] - 1;
                                n ++;
                            } else {
                                ret |= 1;
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
    outStruct->num_stereo0D = n;
exit_function:
    if ( ret < 0 ) {
        if ( outStruct->atom ) inchi_free( outStruct->atom );
        if ( outStruct->stereo0D ) inchi_free( outStruct->stereo0D );
        outStruct->atom = NULL;
        outStruct->stereo0D = NULL;
        outStruct->num_atoms = 0;
        outStruct->num_stereo0D = 0;
    }
    return ret;
}
/****************************************************************************************/
int ExtractOneStructure( STRUCT_DATA *sd, INPUT_PARMS *ip, char *szTitle,
         inchi_Input *inp, 
         INCHI_IOSTREAM *log_file, INCHI_IOSTREAM *output_file, INCHI_IOSTREAM *prb_file,
         ORIG_ATOM_DATA *orig_inp_data, long *num_inp,
         char *pStr, int nStrLen )
{
    int         *err           = &sd->nStructReadError;
    char        *pStrErr       = sd->pStrErrStruct;
    inp_ATOM    *at            = NULL;
    MOL_COORD   *szCoord       = NULL; 
    inchi_Atom  *ati           = NULL;  
    int       nNumAtoms = 0;
    int       a1, j, valence, nDim, nNumBonds, nRet = 0;

    /********************************************************
     *
     *   Extract the structure
     *
     ********************************************************/

    FreeOrigAtData( orig_inp_data );
    nDim      = 0;
    nNumBonds = 0;

    if ( !inp || (nNumAtoms = inp->num_atoms) <= 0 || !(ati = inp->atom) ) {
        MOLFILE_ERR_SET (*err, 0, "Empty structure");
        *err = 98;
        goto err_exit;
    }
    if ( nNumAtoms >= MAX_ATOMS ) {
        MOLFILE_ERR_SET (*err, 0, "Too many atoms");
        *err = 70;
        orig_inp_data->num_inp_atoms = -1;
        goto err_exit;
    }

    at      = (inp_ATOM  *) inchi_calloc( nNumAtoms, sizeof(at[0]) );
    szCoord = (MOL_COORD *) inchi_calloc (inchi_max(nNumAtoms, 1), sizeof (MOL_COORD));

    if ( !at || !szCoord ) {
        MOLFILE_ERR_SET (*err, 0, "Out of RAM");
        *err = -1;
        goto err_exit;
    }
    /********************************************************
     *
     *   Extract typical for Molfile structural data
     *
     ********************************************************/
    /* extract atoms and bonds */
    for ( a1 = 0; a1 < nNumAtoms; a1 ++ ) {
        /* extract atoms */
        SetAtomProperties( at, szCoord, ati, a1, &nDim, pStrErr, err );
        if ( *err ) {
            goto err_exit;
        }
        /* extract connections */
        valence = ati[a1].num_bonds;
        for ( j = 0; j < valence; j ++ ) {
            SetBondProperties( at, ati, a1, j, nNumAtoms, &nNumBonds, pStrErr, err );
        }
        if ( *err ) {
            goto err_exit;
        }
    }

    orig_inp_data->num_inp_atoms = nNumAtoms;
    orig_inp_data->num_inp_bonds = nNumBonds;
    orig_inp_data->num_dimensions = nDim;

    /* extract elements, chemical valences, implicit H, isotopic shifts */
    for ( a1 = 0; a1 < nNumAtoms; a1 ++ ) {
        /* set temp flags in at[a1].at_type (1: data in atom name; 2: request to add H) */
        SetAtomAndBondProperties( at, ati, a1, ip->bDoNotAddH, pStrErr, err );
        if ( *err ) {
            goto err_exit;
        }
    }
    /* clear temp flags in at[].at_type; add implicit H */
    SetNumImplicitH( at, nNumAtoms );
    if ( *err ) {
        goto err_exit;
    }
            
    /********************************************************
     *
     *   Extract the 0D parities (typical for CML)
     *
     ********************************************************/
    Extract0DParities( at, nNumAtoms, inp->stereo0D, inp->num_stereo0D, pStrErr, err );
    if ( *err ) {
        goto err_exit;
    }
    orig_inp_data->at             = at;          at     = NULL;
    orig_inp_data->num_dimensions = nDim;
    orig_inp_data->num_inp_atoms  = nNumAtoms;
    orig_inp_data->num_inp_bonds  = nNumBonds;
    orig_inp_data->szCoord        = szCoord;     szCoord = NULL;

    /* chiral flag */
    /* *****************************************************************************
     * Chiral flags are set in: 
     * - RunICHI.c #1610 -- ReadTheStructure()     -- cInChI, wInChI
     * - e_IchiMain.c #273 -- main()               -- C example of calling InChI dll  
     * - inchi_dll.c  #1662 -- ExtractOneStructure -- InChI dll code (here)
     *******************************************************************************/   
    if ( (ip->nMode & REQ_MODE_CHIR_FLG_STEREO) && (ip->nMode & REQ_MODE_STEREO) ) {
        if ( ip->bChiralFlag & FLAG_SET_INP_AT_CHIRAL ) {
            /* absolute stereo */
            ip->nMode &= ~(REQ_MODE_RELATIVE_STEREO | REQ_MODE_RACEMIC_STEREO);
            sd->bChiralFlag &= ~FLAG_INP_AT_NONCHIRAL;
            sd->bChiralFlag |= FLAG_INP_AT_CHIRAL; /* write AuxInfo as chiral */
        } else
        /*if ( ip->bChiralFlag & FLAG_SET_INP_AT_NONCHIRAL )*/ {
            /* relative stereo */
            ip->nMode &= ~(REQ_MODE_RACEMIC_STEREO);
            ip->nMode |=   REQ_MODE_RELATIVE_STEREO;
            sd->bChiralFlag &= ~FLAG_INP_AT_CHIRAL;
            sd->bChiralFlag |= FLAG_INP_AT_NONCHIRAL; /* write AuxInfo as non-chiral */
        }
    } else
    if ( ip->bChiralFlag & FLAG_SET_INP_AT_CHIRAL ) {
        sd->bChiralFlag &= ~FLAG_INP_AT_NONCHIRAL;
        sd->bChiralFlag |= FLAG_INP_AT_CHIRAL; /* write AuxInfo as chiral */
    } else
    if ( ip->bChiralFlag & FLAG_SET_INP_AT_NONCHIRAL ) {
        sd->bChiralFlag &= ~FLAG_INP_AT_CHIRAL;
        sd->bChiralFlag |= FLAG_INP_AT_NONCHIRAL; /* write AuxInfo as non-chiral */
    }

    *num_inp += 1;

err_exit:

    if ( at )
        inchi_free( at );
    if ( szCoord )
        inchi_free( szCoord );

    nRet = TreatReadTheStructureErrors( sd, ip, LOG_MASK_NO_WARN, NULL, log_file, output_file, prb_file,
                                                orig_inp_data, num_inp, pStr, nStrLen );

    return nRet;

}
/********************************************************/
int INCHI_DECL GetStringLength( char *p )
{
    if ( p ) {
        return strlen(p);
    } else {
        return 0;
    }
}
#define MAX_MSG_LEN 512



/* GetINCHIfromINCHI does same as -InChI2InChI option: converts InChI into InChI for validation purposes */
/* It may also be used to filter out specific layers. For instance, /Snon would remove stereochemical layer */
/* Omitting /FixedH and/or /RecMet would remove Fixed-H or Reconnected layers */
/* To keep all InChI layers use options string "/FixedH /RecMet"; option /InChI2InChI is not needed */
/* inchi_InputINCHI is created by the user; strings in inchi_Output are allocated and deallocated by InChI */
/* inchi_Output does not need to be initilized out to zeroes; see FreeINCHI() on how to deallocate it */
/*************************************************************/
static int INCHI_DECL GetINCHIfromINCHI( inchi_InputINCHI *inpInChI, inchi_Output *out )
{
    STRUCT_DATA struct_data;
    STRUCT_DATA *sd = &struct_data;    
    
    static char szMainOption[] = " ?InChI2InChI";

    int i;
    char      szSdfDataValue[MAX_SDF_VALUE+1];
    unsigned long  ulDisplTime = 0;    /*  infinite, milliseconds */

    INPUT_PARMS inp_parms;
    INPUT_PARMS *ip = &inp_parms;

    int             bReleaseVersion = bRELEASE_VERSION;
    int   nRet = 0, nRet1;

#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
    int  num_repeat = REPEAT_ALL;
#endif

    const char *argv[INCHI_MAX_NUM_ARG+1];
    int   argc;
    char *szOptions = NULL;

    INCHI_IOSTREAM inchi_file[3], *output_file = inchi_file, *log_file = inchi_file+1, *input_file = inchi_file+2;




    if ( bLibInchiSemaphore ) {  /* does not work properly under sufficient stress */
        return inchi_Ret_BUSY;
    }
    bLibInchiSemaphore = 1;

#if( TRACE_MEMORY_LEAKS == 1 )
    _CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
/* for execution outside the VC++ debugger uncomment one of the following two */
#ifdef MY_REPORT_FILE 
   _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_WARN, MY_REPORT_FILE );
   _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ERROR, MY_REPORT_FILE );
   _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ASSERT, MY_REPORT_FILE );
#else
    _CrtSetReportMode(_CRT_WARN | _CRT_ERROR, _CRTDBG_MODE_DEBUG);
#endif
    
    /* turn on floating point exceptions */
#if ( !defined(__STDC__) || __STDC__ != 1 )
    {
        /* Get the default control word. */
        int cw = _controlfp( 0,0 );

        /* Set the exception masks OFF, turn exceptions on. */
        /*cw &=~(EM_OVERFLOW|EM_UNDERFLOW|EM_INEXACT|EM_ZERODIVIDE|EM_DENORMAL);*/
        cw &=~(EM_OVERFLOW|EM_UNDERFLOW|EM_ZERODIVIDE|EM_DENORMAL);

        /* Set the control word. */
        _controlfp( cw, MCW_EM );
 
    }
#endif
#endif



    memset( out, 0, sizeof(*out) );   
#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
repeat:
    FreeStdINCHI( out );
    inchi_ios_close(output_file);
    inchi_ios_close(log_file);
    inchi_ios_reset(input_file);  /* do not close input_file - its string buffer may point to inpInChI->szInChI */
#endif

    /*^^^ Initialize internal for this function I/O streams as string buffers */
    inchi_ios_init(input_file, INCHI_IOSTREAM_STRING, NULL);
    inchi_ios_init(output_file, INCHI_IOSTREAM_STRING, NULL);
    inchi_ios_init(log_file, INCHI_IOSTREAM_STRING, NULL);


    sd->bUserQuit  = 0;

    /* clear original input structure */
    /*^^^ memset( inchi_file, 0, sizeof(inchi_file) ); */
    memset( sd,         0, sizeof(*sd) );
    memset( ip,         0, sizeof(*ip) );
    memset( szSdfDataValue    , 0, sizeof( szSdfDataValue    ) );
    szMainOption[1] = INCHI_OPTION_PREFX;

    if ( !inpInChI ) {
        nRet = _IS_ERROR;
        goto exit_function;
    }

    /* options */
    if ( inpInChI ) {
        int opt_len = (inpInChI->szOptions? strlen(inpInChI->szOptions) : 0) + sizeof(szMainOption) + 1;
        szOptions = (char*)inchi_calloc( opt_len+1, sizeof(szOptions[0]) );
        if ( szOptions ) {
            if ( inpInChI->szOptions ) {
                strcpy( szOptions, inpInChI->szOptions );
            }
            strcat( szOptions, szMainOption );
            argc = parse_options_string ( szOptions, argv, INCHI_MAX_NUM_ARG );
        } else {
            nRet = _IS_FATAL;
            goto translate_RetVal; /* emergency exit */
        }
    } else {
        argc = 1;
        argv[0] = "";
        argv[1] = NULL;
    }

    if ( argc == 1
#ifdef INCHI_LIBRARY
        && (!inpInChI || !inpInChI->szInChI)
#endif        
        || argc==2 && ( argv[1][0]==INCHI_OPTION_PREFX ) &&
        (!strcmp(argv[1]+1, "?") || !stricmp(argv[1]+1, "help") ) ) {
        HelpCommandLineParms(log_file);
        out->szLog = log_file->s.pStr;
        memset( log_file, 0, sizeof(*log_file) );
        nRet = _IS_EOF;
        goto translate_RetVal;
    }

    nRet1 = ReadCommandLineParms( argc, argv, ip, szSdfDataValue, &ulDisplTime, bReleaseVersion, log_file );
    if ( szOptions ) {
        /* argv pointed to strings in szOptions */
        inchi_free( szOptions );
        szOptions = NULL;
    }
    /* INChI DLL specific */
    ip->bNoStructLabels = 1;

    if ( 0 > nRet1 ) {
        goto exit_function;
    }
    if ( ip->bNoStructLabels ) {
        ip->pSdfLabel = NULL;
        ip->pSdfValue = NULL;
    } else
    if ( ip->nInputType == INPUT_INCHI_XML || ip->nInputType == INPUT_INCHI_PLAIN  ||
         ip->nInputType == INPUT_CMLFILE || ip->nInputType == INPUT_INCHI ) {
        /* the input may contain both the header and the label of the structure */
        if ( !ip->pSdfLabel ) 
            ip->pSdfLabel  = ip->szSdfDataHeader;
        if ( !ip->pSdfValue )
            ip->pSdfValue  = szSdfDataValue;
    }
    if ( ip->nInputType && ip->nInputType != INPUT_INCHI ) {
        inchi_ios_eprint( log_file, "Input type set to INPUT_INCHI\n" );
        ip->nInputType = INPUT_INCHI;
    }

    PrintInputParms( log_file, ip );
    /*********************************/
    /* InChI -> Structure conversion */
    /*********************************/

    /* input_file simulation */
    input_file->s.pStr = inpInChI->szInChI;
    input_file->s.nUsedLength = strlen(input_file->s.pStr)+1;
    input_file->s.nAllocatedLength = input_file->s.nUsedLength;
    input_file->s.nPtr = 0;

    /* buffer for the message */
    out->szMessage = (char *)inchi_calloc( MAX_MSG_LEN, sizeof(out->szMessage[0]));
    if ( !out->szMessage ) {
         inchi_ios_eprint( log_file, "Cannot allocate output message buffer.\n");
        nRet = -1;
    } else {
        nRet = ReadWriteInChI( input_file, output_file, log_file,
                               ip,  sd, NULL,  NULL, out->szMessage, MAX_MSG_LEN, NULL /*out->WarningFlags*/ );
    }
    
    if ( nRet >= 0 && output_file->s.pStr ) 
    {
        /* success */
        char *p;
        out->szInChI = output_file->s.pStr;
        out->szAuxInfo = NULL;
        for ( p = strchr(out->szInChI, '\n'); p; p = strchr(p+1, '\n') ) {
            if ( !memcmp( p, "\nAuxInfo", 8 ) ) {
                *p = '\0';            /* remove LF after INChI */
                out->szAuxInfo = p+1; /* save pointer to AuxInfo */
            } else
            if ( out->szAuxInfo || !p[1]) {   /* remove LF after aux info or from the last char */
                *p = '\0';
                break;
            }
        }
        output_file->s.pStr = NULL;
    }
    /*
    out->szLog = log_file->pStr;
    log_file->pStr   = NULL;
    */

exit_function:;

#if( ADD_CMLPP == 1 )
        /* BILLY 8/6/04 */
        /* free CML memory */
        FreeCml ();
        FreeCmlDoc( 1 );
#endif



    for ( i = 0; i < MAX_NUM_PATHS; i ++ ) {
        if ( ip->path[i] ) {
            inchi_free( (char*) ip->path[i] ); /*  cast deliberately discards 'const' qualifier */
            ip->path[i] = NULL;
        }
    }

    SetBitFree( );


#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
    if ( num_repeat-- > 0 ) {
        goto repeat;
    }
#endif


#ifdef INCHI_LIBRARY
    /* output */

    if ( log_file->s.pStr && log_file->s.nUsedLength > 0 ) {
        while ( log_file->s.nUsedLength && '\n' == log_file->s.pStr[log_file->s.nUsedLength-1] ) {
            log_file->s.pStr[-- log_file->s.nUsedLength]  = '\0'; /* remove last LF */
        }
        if ( out ) {
            out->szLog = log_file->s.pStr;
            log_file->s.pStr = NULL;
        }
    }


#endif

    
translate_RetVal:

    /* Close internal output streams */
    inchi_ios_close(output_file);
    inchi_ios_close(log_file);
    inchi_ios_reset(input_file);  /* do not close input_file - its string buffer may point to inpInChI->szInChI */


    switch (nRet) { 
    case -3         : nRet = inchi_Ret_ERROR  ; break; /* Error: no Structure has been created */
    case -2         : nRet = inchi_Ret_ERROR  ; break; /* Error: no Structure has been created */
    case -1         : nRet = inchi_Ret_FATAL  ; break; /* Severe error: no Structure has been created (typically; break; memory allocation failed) */
    default         :
        /*
        if ( !outStruct->atom || !outStruct->num_atoms ) {
            nRet = inchi_Ret_EOF;
        } else {
            int m,n,t=0;
            for ( m=0; m < 2; m ++ ) {
                for ( n=0; n < 2; n ++ ) {
                    if ( outStruct->WarningFlags[m][n] ) {
                        t ++;
                    }
                }
            }
            nRet = t? inchi_Ret_WARNING : inchi_Ret_OKAY;
        }
        */
        break;
    }

    bLibInchiSemaphore = 0;
    return nRet;
}


int INCHI_DECL GetStructFromStdINCHI( inchi_InputINCHI *inpInChI, inchi_OutputStruct *outStruct )
{
#ifdef ONLY_STDINCHI_STDKEY
    return GetStructFromINCHI( inpInChI, outStruct );
#else
    /* to be filled */
    return inchi_Ret_ERROR;
#endif
}



/*************************************************************/
static int INCHI_DECL GetStructFromINCHI( inchi_InputINCHI *inpInChI, inchi_OutputStruct *outStruct )
{

    STRUCT_DATA struct_data;
    STRUCT_DATA *sd = &struct_data;
    

    INCHI_IOSTREAM inchi_file[3];
    INCHI_IOSTREAM *output_file = inchi_file, *log_file = inchi_file+1, *input_file = inchi_file+2;
    

    static char szMainOption[] = " ?InChI2Struct";


    int i;
    char      szSdfDataValue[MAX_SDF_VALUE+1];
    unsigned long  ulDisplTime = 0;    /*  infinite, milliseconds */

    INPUT_PARMS inp_parms;
    INPUT_PARMS *ip = &inp_parms;

    int             bReleaseVersion = bRELEASE_VERSION;
    int   nRet = 0, nRet1;

    /* conversion result */
    inp_ATOM *at=NULL;
    int num_at = 0;

#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
    int  num_repeat = REPEAT_ALL;
#endif

    const char *argv[INCHI_MAX_NUM_ARG+1];
    int   argc;
    char *szOptions = NULL;

    if ( bLibInchiSemaphore ) {  /* does not work properly under sufficient stress */
        return inchi_Ret_BUSY;
    }
    bLibInchiSemaphore = 1;

#if( TRACE_MEMORY_LEAKS == 1 )
    _CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
/* for execution outside the VC++ debugger uncomment one of the following two */
#ifdef MY_REPORT_FILE 
   _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_WARN, MY_REPORT_FILE );
   _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ERROR, MY_REPORT_FILE );
   _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ASSERT, MY_REPORT_FILE );
#else
    _CrtSetReportMode(_CRT_WARN | _CRT_ERROR, _CRTDBG_MODE_DEBUG);
#endif
    
    /* turn on floating point exceptions */
#if ( !defined(__STDC__) || __STDC__ != 1 )    
    {
        /* Get the default control word. */
        int cw = _controlfp( 0,0 );

        /* Set the exception masks OFF, turn exceptions on. */
        /*cw &=~(EM_OVERFLOW|EM_UNDERFLOW|EM_INEXACT|EM_ZERODIVIDE|EM_DENORMAL);*/
        cw &=~(EM_OVERFLOW|EM_UNDERFLOW|EM_ZERODIVIDE|EM_DENORMAL);

        /* Set the control word. */
        _controlfp( cw, MCW_EM );
 
    }
#endif
#endif

    memset( outStruct, 0, sizeof(*outStruct) );

#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
repeat:
    FreeStructFromStdINCHI( &outStruct );
    inchi_ios_reset(input_file);  /* do not close input_file - its string buffer may point to inpInChI->szInChI */
    inchi_ios_close(output_file);
    inchi_ios_close(log_file);
#endif

    sd->bUserQuit  = 0;



    /*^^^ Initialize internal for this function I/O streams as string buffers */
    inchi_ios_init(input_file, INCHI_IOSTREAM_STRING, NULL);
    inchi_ios_init(output_file, INCHI_IOSTREAM_STRING, NULL);
    inchi_ios_init(log_file, INCHI_IOSTREAM_STRING, NULL);


    /* clear original input structure */

    memset( sd,         0, sizeof(*sd) );
    memset( ip,         0, sizeof(*ip) );
    memset( szSdfDataValue    , 0, sizeof( szSdfDataValue    ) );
    szMainOption[1] = INCHI_OPTION_PREFX;

    if ( !inpInChI ) {
        nRet = _IS_ERROR;
        goto exit_function;
    }

    /* options */
    if ( inpInChI /*&& inpInChI->szOptions*/ ) {  /* fix bug discovered by Burt Leland 2008-12-23 */
        int opt_len = (inpInChI->szOptions? strlen(inpInChI->szOptions) : 0) + sizeof(szMainOption) + 1;
        szOptions = (char*)inchi_calloc( opt_len+1, sizeof(szOptions[0]) );
        if ( szOptions ) {
            if ( inpInChI->szOptions ) /* fix bug discovered by Burt Leland 2008-12-23 */
                strcpy( szOptions, inpInChI->szOptions );
            strcat( szOptions, szMainOption );
            argc = parse_options_string ( szOptions, argv, INCHI_MAX_NUM_ARG );
        } else {
            nRet = _IS_FATAL;
            goto translate_RetVal; /* emergency exit */
        }
    } else {
        argc = 1;
        argv[0] = "";
        argv[1] = NULL;
    }

    if ( argc == 1
#ifdef INCHI_LIBRARY
        && (!inpInChI || !inpInChI->szInChI)
#endif        
        || argc==2 && ( argv[1][0]==INCHI_OPTION_PREFX ) &&
        (!strcmp(argv[1]+1, "?") || !stricmp(argv[1]+1, "help") ) ) {
        HelpCommandLineParms(log_file);
        outStruct->szLog = log_file->s.pStr;
        nRet = _IS_EOF;
        goto translate_RetVal;
    }

    nRet1 = ReadCommandLineParms( argc, argv, ip, szSdfDataValue, &ulDisplTime, bReleaseVersion, log_file );
    if ( szOptions ) {
        /* argv pointed to strings in szOptions */
        inchi_free( szOptions );
        szOptions = NULL;
    }
    /* INChI DLL specific */
    ip->bNoStructLabels = 1;

    if ( 0 > nRet1 ) {
        goto exit_function;
    }
    if ( ip->bNoStructLabels ) {
        ip->pSdfLabel = NULL;
        ip->pSdfValue = NULL;
    } else
    if ( ip->nInputType == INPUT_INCHI_XML || ip->nInputType == INPUT_INCHI_PLAIN  ||
         ip->nInputType == INPUT_CMLFILE || ip->nInputType == INPUT_INCHI ) {
        /* the input may contain both the header and the label of the structure */
        if ( !ip->pSdfLabel ) 
            ip->pSdfLabel  = ip->szSdfDataHeader;
        if ( !ip->pSdfValue )
            ip->pSdfValue  = szSdfDataValue;
    }
    if ( ip->nInputType && ip->nInputType != INPUT_INCHI ) {
        inchi_ios_eprint( log_file, "Input type set to INPUT_INCHI\n" );
        ip->nInputType = INPUT_INCHI;
    }

    PrintInputParms( log_file, ip );
    /*********************************/
    /* InChI -> Structure conversion */
    /*********************************/

    /* input_file simulation */
    input_file->s.pStr = inpInChI->szInChI;
    input_file->s.nUsedLength = strlen(input_file->s.pStr)+1;
    input_file->s.nAllocatedLength = input_file->s.nUsedLength;
    input_file->s.nPtr = 0;
    /* buffer for the message */
    outStruct->szMessage = (char *)inchi_calloc( MAX_MSG_LEN, sizeof(outStruct->szMessage[0]));
    if ( !outStruct->szMessage ) {
         inchi_ios_eprint( log_file, "Cannot allocate output message buffer.\n");
        nRet = -1;
    } else {
        nRet = ReadWriteInChI( input_file, output_file, log_file,
                               ip,  sd, &at,  &num_at, outStruct->szMessage, MAX_MSG_LEN, outStruct->WarningFlags );
    }
    if ( nRet >= 0 && at && num_at ) {
        /* success */
        nRet = InpAtom0DToInchiAtom( at, num_at, outStruct );
        if ( at ) {
            inchi_free( at );
            at = NULL;
        }
        if ( nRet < 0 ) {
            inchi_ios_eprint( log_file, "Final structure conversion failed\n" );
        }
    }
    outStruct->szLog = log_file->s.pStr;


exit_function:;

#if( ADD_CMLPP == 1 )
        /* BILLY 8/6/04 */
        /* free CML memory */
        FreeCml ();
        FreeCmlDoc( 1 );
#endif



    for ( i = 0; i < MAX_NUM_PATHS; i ++ ) {
        if ( ip->path[i] ) {
            inchi_free( (char*) ip->path[i] ); /*  cast deliberately discards 'const' qualifier */
            ip->path[i] = NULL;
        }
    }

    SetBitFree( );


#if ( defined(REPEAT_ALL) && REPEAT_ALL > 0 )
    if ( num_repeat-- > 0 ) {
        goto repeat;
    }
#endif


#ifdef INCHI_LIBRARY
    /* output */

    if ( log_file->s.pStr && log_file->s.nUsedLength > 0 ) {
        while ( log_file->s.nUsedLength && '\n' == log_file->s.pStr[log_file->s.nUsedLength-1] ) {
            log_file->s.pStr[-- log_file->s.nUsedLength]  = '\0'; /* remove last LF */
        }
        if ( outStruct ) {
            outStruct->szLog = log_file->s.pStr;
            log_file->s.pStr = NULL;
        }
    }
#endif
    
translate_RetVal:

    /* Close internal I/O streams */
    inchi_ios_reset(input_file);  /* do not close input_file - its string buffer may point to inpInChI->szInChI */
    inchi_ios_close(output_file);
    inchi_ios_close(log_file);

    switch (nRet) { 
    case -3         : nRet = inchi_Ret_ERROR  ; break; /* Error: no Structure has been created */
    case -2         : nRet = inchi_Ret_ERROR  ; break; /* Error: no Structure has been created */
    case -1         : nRet = inchi_Ret_FATAL  ; break; /* Severe error: no Structure has been created (typically; break; memory allocation failed) */
    default         :
        if ( !outStruct->atom || !outStruct->num_atoms ) {
            nRet = inchi_Ret_EOF;
        } else {
            int m,n,t=0;
            for ( m=0; m < 2; m ++ ) {
                for ( n=0; n < 2; n ++ ) {
                    if ( outStruct->WarningFlags[m][n] ) {
                        t ++;
                    }
                }
            }
            nRet = t? inchi_Ret_WARNING : inchi_Ret_OKAY;
        }
            break;
    }

    bLibInchiSemaphore = 0;
    return nRet;
}

/********************************************************************/

#if( defined( _WIN32 ) && defined( _MSC_VER ) && _MSC_VER >= 800 && defined(_USRDLL) && defined(INCHI_LINK_AS_DLL) )
    /* Win32 & MS VC ++, compile and link as a DLL */
/*********************************************************/
/*   C calling conventions export from Win32 dll         */
/*********************************************************/
/* prototypes */
#ifndef INCHI_ALL_CPP
#ifdef __cplusplus
extern "C" {
#endif
#endif

int cdecl_GetStdINCHI( inchi_Input *inp, inchi_Output *out );
void cdecl_FreeStdINCHI( inchi_Output *out );
int  cdecl_GetStringLength( char *p );
int  cdecl_Get_std_inchi_Input_FromAuxInfo
             ( char *szInchiAuxInfo, int bDoNotAddH, InchiInpData *pInchiInp );
void cdecl_Free_std_inchi_Input( inchi_Input *pInp );
int cdecl_GetStructFromStdINCHI( inchi_InputINCHI *inpInChI, inchi_OutputStruct *outStruct );
void cdecl_FreeStructFromStdINCHI( inchi_OutputStruct *outStruct );

#ifndef INCHI_ALL_CPP
#ifdef __cplusplus
}
#endif
#endif

/* implementation */
/* vc6_libstdinchi.def provides export withou cdecl_ prefixes */

/********************************************************/
int cdecl_GetStdINCHI( inchi_Input *inp, inchi_Output *out )
{
    return GetStdINCHI( inp, out );
}
/********************************************************/
void cdecl_FreeStdINCHI( inchi_Output *out )
{
    FreeStdINCHI( out );
}
/********************************************************/
int cdecl_GetStringLength( char *p )
{
    return GetStringLength( p );
}
/********************************************************/
int cdecl_Get_std_inchi_Input_FromAuxInfo
             ( char *szInchiAuxInfo, int bDoNotAddH, InchiInpData *pInchiInp )
{
    return Get_std_inchi_Input_FromAuxInfo
             ( szInchiAuxInfo, bDoNotAddH, pInchiInp );
}
/********************************************************/
void cdecl_Free_std_inchi_Input( inchi_Input *pInp )
{
    Free_std_inchi_Input( pInp );
}
/********************************************************/
int cdecl_GetStructFromStdINCHI( inchi_InputINCHI *inpInChI, inchi_OutputStruct *outStruct )
{
    return GetStructFromStdINCHI( inpInChI, outStruct );
}
/********************************************************/
void cdecl_FreeStructFromStdINCHI( inchi_OutputStruct *outStruct )
{
    FreeStructFromStdINCHI( outStruct );
}

#endif

#if( defined(__GNUC__) && __GNUC__ >= 3 && defined(__MINGW32__) && defined(_WIN32) )
#include <windows.h>
/*********************************************************/
/*   Pacal calling conventions export from Win32 dll     */
/*********************************************************/
#ifndef INCHI_ALL_CPP
#ifdef __cplusplus
extern "C" {
#endif
#endif
/* prototypes */

int  PASCAL pasc_GetStdINCHI( inchi_Input *inp, inchi_Output *out );
void PASCAL pasc_FreeStdINCHI( inchi_Output *out );
int  PASCAL pasc_GetStringLength( char *p );
int  PASCAL pasc_Get_std_inchi_Input_FromAuxInfo
             ( char *szInchiAuxInfo, int bDoNotAddH, InchiInpData *pInchiInp );
void PASCAL pasc_Free_std_inchi_Input( inchi_Input *pInp );
void PASCAL pasc_FreeStructFromStdINCHI( inchi_OutputStruct *out );
int PASCAL pasc_GetStructFromStdINCHI( inchi_InputINCHI *inp, inchi_OutputStruct *out );

#ifndef INCHI_ALL_CPP
#ifdef __cplusplus
}
#endif
#endif

/* implementation */
/* vc6_libstdinchi.def provides export without PASCAL pasc_ prefixes */
/********************************************************/
int PASCAL pasc_GetStdINCHI( inchi_Input *inp, inchi_Output *out )
{
    return GetStdINCHI( inp, out );
}
/********************************************************/
void PASCAL pasc_FreeStdINCHI( inchi_Output *out )
{
    FreeStdINCHI( out );
}
/********************************************************/
int PASCAL pasc_GetStringLength( char *p )
{
    return GetStringLength( p );
}
/********************************************************/
int PASCAL pasc_Get_std_inchi_Input_FromAuxInfo
             ( char *szInchiAuxInfo, int bDoNotAddH, InchiInpData *pInchiInp )
{
    return Get_std_inchi_Input_FromAuxInfo
             ( szInchiAuxInfo, bDoNotAddH, pInchiInp );
}
/********************************************************/
void PASCAL pasc_Free_std_inchi_Input( inchi_Input *pInp )
{
    Free_std_inchi_Input( pInp );
}
/********************************************************/
void PASCAL pasc_FreeStructFromStdINCHI( inchi_OutputStruct *out )
{
    FreeStructFromStdINCHI( out );
}
/********************************************************/
int PASCAL pasc_GetStructFromStdINCHI( inchi_InputINCHI *inp, inchi_OutputStruct *out )
{
    return GetStructFromStdINCHI( inp, out );
}
#endif 


