/*
 * International Chemical Identifier (InChI)
 * Version 1
 * Software version 1.07
 * April 30, 2024
 *
 * MIT License
 *
 * Copyright (c) 2024 IUPAC and InChI Trust
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
*
* 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.
 *
 * info@inchi-trust.org
 *
*/

#include <stdlib.h>
#include <string.h>

#include "mode.h"

#include "ichitaut.h"
#include "ichicomn.h"
#include "ichitime.h"
#include "ichicant.h"
#include "util.h"

#include "bcf_s.h"

/* Local prototypes */

int SetTautomericBonds( inp_ATOM *at, int nNumBondPos, T_BONDPOS *BondPos );
int CompRankTautomer( const void* a1, const void* a2, void * );
int RegisterEndPoints( CANON_GLOBALS *pCG,
                       T_GROUP_INFO *t_group_info,
                       /* T_GROUP *t_group, int *pnum_t, int max_num_t,*/
                       T_ENDPOINT *EndPoint,
                       int nNumEndPoints,
                       inp_ATOM *at,
                       int num_atoms,
                       C_GROUP_INFO *cgi,
                       struct BalancedNetworkStructure *pBNS );
int cmpTGroupNumber( const void *a1, const void *a2 );
int comp_candidates( const void *a1, const void *a2 );
int MoveEndpoint( inp_ATOM *at,
                  S_CANDIDATE *s_candidate,
                  AT_NUMB endpoint,
                  AT_NUMB *nTGroupNewNumbers,
                  AT_NUMB *nTGroupPosition,
                  int nNewTGroupOrd,
                  T_GROUP_INFO *t_group_info );
int FindAccessibleEndPoints( CANON_GLOBALS *pCG,
                             T_ENDPOINT *EndPoint,
                             int *nNumEndPoints,
                             T_BONDPOS *BondPos,
                             int *nNumBondPos,
                             struct BalancedNetworkStructure *pBNS,
                             struct BalancedNetworkData *pBD,
                             inp_ATOM *at,
                             int num_atoms,
                             C_GROUP_INFO *cgi,
                             int taut_mode );
int GetChargeType( inp_ATOM *atom, int iat, S_CHAR *cChargeSubtype );
int GetNeutralRepsIfNeeded( AT_NUMB *pri,
                            AT_NUMB *prj,
                            inp_ATOM *at,
                            int num_atoms,
                            T_ENDPOINT *EndPoint,
                            int nNumEndPoints,
                            C_GROUP_INFO *cgi );
int bCanBeACPoint( inp_ATOM *at,
                   S_CHAR cCharge,
                   S_CHAR cChangeValence,
                   S_CHAR neutral_bonds_valence,
                   S_CHAR neutral_valence,
                   S_CHAR nEndpointValence, S_CHAR *cChargeSubtype );
int CmpCCandidates( const void *a1, const void *a2 );
int RegisterCPoints( C_GROUP *c_group,
                     int *pnum_c,
                     int max_num_c,
                     T_GROUP_INFO *t_group_info,
                     int point1,
                     int point2,
                     int ctype,
                     inp_ATOM *at,
                     int num_atoms );
int GetSaltChargeType( inp_ATOM *at,
                       int at_no,
                       T_GROUP_INFO *t_group_info,
                       int *s_subtype );
int GetOtherSaltChargeType( inp_ATOM *at,
                            int at_no,
                            T_GROUP_INFO *t_group_info,
                            int *s_subtype,
                            int bAccept_O );
int MergeSaltTautGroupsBlind( inp_ATOM *at,
                              int s_type, int num_atoms,
                              S_GROUP_INFO *s_group_info,
                              int nNumCandidates,
                              T_GROUP_INFO *t_group_info,
                              C_GROUP_INFO *c_group_info,
                              struct BalancedNetworkStructure *pBNS );
int ConnectSaltTGroups2SuperTGroup( inp_ATOM *at,
                                    int num_atoms,
                                    S_GROUP_INFO *s_group_info,
                                    int nNumCandidates,
                                    T_GROUP_INFO *t_group_info,
                                    C_GROUP_INFO *c_group_info,
                                    struct BalancedNetworkStructure *pBNS,
                                    int *nNewTGroupNumber, int *vertSuperTGroup );
int bDoNotMergeNonTautAtom( inp_ATOM *at, int at_no );
int GetOtherSaltType( inp_ATOM *at, int at_no, int *s_subtype );

/* Bits for GetChargeType */
#define C_SUBTYPE_CHARGED     0
#define C_SUBTYPE_p_DONOR     1  /* new */
#define C_SUBTYPE_p_ACCEPT    2  /* new */
#define C_SUBTYPE_H_ACCEPT    4
#define C_SUBTYPE_H_DONOR     8
#define C_SUBTYPE_NEUTRAL    16

/* Internal stack array size */
#define MAX_STACK_ARRAY_LEN 127
#define MAX_TGROUP_ARRAY_LEN 127


/*
is_centerpoint... functions
*/


/****************************************************************************/
int is_centerpoint_elem( U_CHAR el_number )
{
    switch (el_number) {
        case EL_NUMBER_C: 
        case EL_NUMBER_N: 
        case EL_NUMBER_P: 
        case EL_NUMBER_S: 
        case EL_NUMBER_I: 
        case EL_NUMBER_AS: 
        case EL_NUMBER_SB: 
        case EL_NUMBER_SE: 
        case EL_NUMBER_TE: 
        case EL_NUMBER_CL:
        case EL_NUMBER_BR:
            return 1;
        default:
            return 0; 
    }
}


#if ( KETO_ENOL_TAUT == 1 )  /* post v.1 feature */


/****************************************************************************/
int is_centerpoint_elem_KET( U_CHAR el_number )
{
    return el_number == EL_NUMBER_C;
}
#endif


/****************************************************************************/
int is_centerpoint_elem_strict( U_CHAR el_number )
{
    switch (el_number) {
        case EL_NUMBER_C: 
        case EL_NUMBER_N: 
        case EL_NUMBER_P: 
        case EL_NUMBER_AS: 
        case EL_NUMBER_SB: 
            return 1;
        default:
            return 0; 
    }
}


/*
AddAtom2...
*/


/****************************************************************************/
int AddAtom2num( AT_RANK num[], inp_ATOM *atom, int at_no, int bSubtract )
{  /*  bSubtract: 0=> add, 1=>subtract, 2=> fill */
    inp_ATOM *at = atom + at_no;
    int       k;
    int       nMobile = ( at->charge == -1 );
    if (bSubtract == 1)
    {
        /* 1: subtract */
        num[1] -= nMobile;
        nMobile += at->num_H;
        num[0] -= nMobile;
        for (k = 0; k < T_NUM_ISOTOPIC; k++)
        {
            /*  T (3H isotope) first because it has higher weight */
            num[T_NUM_NO_ISOTOPIC + k] -= at->num_iso_H[NUM_H_ISOTOPES - k - 1];
        }
    }
    else
    {
        if (bSubtract == 2)
        {
            /* fill */
            memset( num, 0, ( T_NUM_NO_ISOTOPIC + T_NUM_ISOTOPIC ) * sizeof( num[0] ) ); /* djb-rwth: memset_s C11/Annex K variant? */
        }
        /* else (0): add */
        num[1] += nMobile;
        nMobile += at->num_H;
        num[0] += nMobile;
        for (k = 0; k < T_NUM_ISOTOPIC; k++)
        {
            /*  T (3H isotope) first because it has higher weight */
            num[T_NUM_NO_ISOTOPIC + k] += at->num_iso_H[NUM_H_ISOTOPES - k - 1];
        }
    }
    return nMobile;
}


/****************************************************************************/
void AddAtom2DA( AT_RANK num_DA[], inp_ATOM *atom, int at_no, int bSubtract )
{   /*  bSubtract: 0=> add, 1=>subtract, 2=> fill */
    inp_ATOM *at = atom + at_no;
    int       nDelta, nAcidic_O;

    if (at->charge < -1 || (at->charge == 1 && !at->c_point) || at->charge > 1) /* djb-rwth: addressing LLVM warning */
    {
        return;
    }

    nDelta = ( bSubtract == 1 ) ? -1 : 1;

    /* "Acidic" O, S, Se, Te recognition */
    if (at->at_type & ATT_ACIDIC_CO)
    {
        nAcidic_O = nDelta;
    }
    else
    {
        nAcidic_O = 0;
    }

    if (bSubtract == 2)
    {
        /* 2: fill, otherwise add */
        memset( num_DA, 0, TG_NUM_DA * sizeof( num_DA[0] ) ); /* djb-rwth: memset_s C11/Annex K variant? */
    }
    if ((at->charge <= 0 && at->valence == at->chem_bonds_valence) ||
         /* neutral or negative donor */
         (at->charge > 0 && at->valence + 1 == at->chem_bonds_valence)
         /* positively charged donor */
         ) /* djb-rwth: addressing LLVM warnings */
    {
        if (at->charge < 0)
        {
            num_DA[TG_Num_dM] += nDelta;
            num_DA[TG_Num_dO] += nAcidic_O;
        }
        else
        {
            if (at->num_H)
            {
                num_DA[TG_Num_dH] += nDelta;
                num_DA[TG_Num_dO] += nAcidic_O;
            }
        }
    }
    else
    {
        if ((at->charge <= 0 && at->valence + 1 == at->chem_bonds_valence) ||
             (at->charge  > 0 && at->valence + 2 == at->chem_bonds_valence)) /* djb-rwth: addressing LLVM warnings */
        {
            /* acceptor */
            if (at->charge < 0)
            {
                num_DA[TG_Num_aM] += nDelta;
            }
            else
            {
                if (at->num_H)
                {
                    num_DA[TG_Num_aH] += nDelta;
                }
                else
                {
                    num_DA[TG_Num_aO] += nAcidic_O; /* acidic O-acceptor has no H or charge */
                }
            }
        }
    }
    return;
}


/*

*/


/****************************************************************************/
int AddEndPoint( T_ENDPOINT *pEndPoint, inp_ATOM *at, int iat )
{
    pEndPoint->nAtomNumber = iat;
    pEndPoint->nEquNumber = 0;
    pEndPoint->nGroupNumber = at[iat].endpoint;
    if (at[iat].endpoint)
    {
        /* already an endpoint */
        memset( pEndPoint->num, 0, sizeof( pEndPoint->num ) ); /* djb-rwth: memset_s C11/Annex K variant? */
    }
    else
    {
        /* not an endpoint yet, make it an endpoint */
        AddAtom2num( pEndPoint->num, at, iat, 2 );  /* fill */
        AddAtom2DA( pEndPoint->num_DA, at, iat, 2 );
        /*
        nMobile  = pEndPoint->num[1] = (at[iat].charge == -1);
        nMobile  = pEndPoint->num[0] = at[iat].num_H + nMobile;
        for ( k = 0; k < T_NUM_ISOTOPIC; k ++ ) {
        pEndPoint->num[T_NUM_NO_ISOTOPIC+k] = at[iat].num_iso_H[NUM_H_ISOTOPES-k-1];
        }
        */
    }

    return 0;
}


/****************************************************************************/
int nGetEndpointInfo( inp_ATOM *atom, int iat, ENDPOINT_INFO *eif )
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;

    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
    {
        return 0; /* a radical */
    }
    if (!( nEndpointValence = get_endpoint_valence( atom[iat].el_number ) ))
    {
        return 0; /* not an endpoint */
    }
    if (nEndpointValence <= atom[iat].valence)
    {
        return 0; /* not an endpoint, for example >N(+)< or >N<  or >O(+)- or >O- or >N- or -O- */
    }

    if (atom[iat].charge == -1 || atom[iat].charge == 0)
    {
        /* not a positive charge-point */
        if (nEndpointValence < atom[iat].chem_bonds_valence)
        {
            return 0; /* abnormal valence > standard endpoint valence */
        }
        nMobile = atom[iat].num_H + ( atom[iat].charge == -1 );
        if (nMobile + atom[iat].chem_bonds_valence != nEndpointValence)
        {
            return 0; /* non-standard endpoint valence */
        }
        switch (atom[iat].chem_bonds_valence - atom[iat].valence)
        {
            case 0:
                eif->cDonor = 1;
                eif->cAcceptor = 0;
                break;
            case 1:
                eif->cDonor = 0;
                eif->cAcceptor = 1;
                break;
            default:
                return 0;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    else
    {
        if (atom[iat].c_point &&
             0 <= GetChargeType( atom, iat, &cChargeSubtype ) &&
             ( (int) cChargeSubtype & ( C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR ) ))
        {
            /* charge-point */
            {
                if (cChargeSubtype & C_SUBTYPE_H_ACCEPT)
                {
                    eif->cDonor = 0;
                    eif->cAcceptor = 1;
                }
                else
                {
                    if (cChargeSubtype & C_SUBTYPE_H_DONOR)
                    {
                        eif->cDonor = 1;
                        eif->cAcceptor = 0;
                    }
                    else
                    {
                        return 0;
                    }
                }
            }
            eif->cMobile = atom[iat].num_H;
            eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
            eif->cMoveableCharge = atom[iat].charge;
#if ( KETO_ENOL_TAUT == 1 )
            eif->cKetoEnolCode = 0;
#endif
            return nEndpointValence;
        }
    }

    return 0;
}


#if ( TAUT_PT_22_00 == 1 )
int nGetEndpointInfo_PT_22_00(inp_ATOM *atom, int iat, ENDPOINT_INFO *eif)
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;

    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
        return 0; /* a radical */
    nEndpointValence = atom[iat].el_number == EL_NUMBER_C ? 4 : 0;
    if (!nEndpointValence)
        return 0; /* not an endpoint */
    if (nEndpointValence <= atom[iat].valence)
        return 0; /* not an endpoint, for example >N(+)< or >N<  or >O(+)- or >O- or >N- or -O- */

    if (atom[iat].charge == -1 || atom[iat].charge == 0) {
        /* not a positive charge-point */
        if (nEndpointValence < atom[iat].chem_bonds_valence)
            return 0; /* abnormal valence > standard endpoint valence */
        nMobile = atom[iat].num_H + (atom[iat].charge == -1);
        if (nMobile + atom[iat].chem_bonds_valence != nEndpointValence)
            return 0; /* non-standard endpoint valence */
        switch (atom[iat].chem_bonds_valence - atom[iat].valence) {
        case 0:
            eif->cDonor = 1;
            eif->cAcceptor = 0;
            break;
        case 1:
            eif->cDonor = 0;
            eif->cAcceptor = 1;
            break;
        default:
            return 0;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    else
        if (atom[iat].c_point &&
            0 <= GetChargeType(atom, iat, &cChargeSubtype) &&
            ((int)cChargeSubtype & (C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR))
            ) {
            /* charge-point */
            if (cChargeSubtype & C_SUBTYPE_H_ACCEPT) {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
            else
                if (cChargeSubtype & C_SUBTYPE_H_DONOR) {
                    eif->cDonor = 1;
                    eif->cAcceptor = 0;
                }
                else {
                    return 0;
                }
                eif->cMobile = atom[iat].num_H;
                eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
                eif->cMoveableCharge = atom[iat].charge;
#if ( KETO_ENOL_TAUT == 1 )
                eif->cKetoEnolCode = 0;
#endif
                return nEndpointValence;
        }
    return 0;
}
#endif

#if ( TAUT_PT_16_00 == 1 )
int nGetEndpointInfo_PT_16_00(inp_ATOM *atom, int iat, ENDPOINT_INFO *eif)
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;

    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
        return 0; /* a radical */
    nEndpointValence = get_endpoint_valence_KET( atom[iat].el_number );
    if (!nEndpointValence)
        return 0; /* not an endpoint */
    if (nEndpointValence <= atom[iat].valence)
        return 0; /* not an endpoint, for example >N(+)< or >N<  or >O(+)- or >O- or >N- or -O- */
    if (nEndpointValence == 4 && atom[iat].valence < 2)
        return 0; /* exclude O==N--CH3  <=> HO--N==CH2 */
    if (nEndpointValence == 2 && atom[iat].valence > 1)
        return 0; /* exclude --O--N==CH-- */

    if (atom[iat].charge == -1 || atom[iat].charge == 0) {
        /* not a positive charge-point */
        if (nEndpointValence < atom[iat].chem_bonds_valence)
            return 0; /* abnormal valence > standard endpoint valence */
        nMobile = atom[iat].num_H + (atom[iat].charge == -1);
        if (nMobile + atom[iat].chem_bonds_valence != nEndpointValence)
            return 0; /* non-standard endpoint valence */
        switch (atom[iat].chem_bonds_valence - atom[iat].valence) {
        case 0:
            eif->cDonor = 1;
            eif->cAcceptor = 0;
            break;
        case 1:
            eif->cDonor = 0;
            eif->cAcceptor = 1;
            break;
        default:
            return 0;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    else
        if (atom[iat].c_point &&
            0 <= GetChargeType(atom, iat, &cChargeSubtype) &&
            ((int)cChargeSubtype & (C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR))
            ) {
            /* charge-point */
            if (cChargeSubtype & C_SUBTYPE_H_ACCEPT) {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
            else
                if (cChargeSubtype & C_SUBTYPE_H_DONOR) {
                    eif->cDonor = 1;
                    eif->cAcceptor = 0;
                }
                else {
                    return 0;
                }
                eif->cMobile = atom[iat].num_H;
                eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
                eif->cMoveableCharge = atom[iat].charge;
#if ( KETO_ENOL_TAUT == 1 )
                eif->cKetoEnolCode = 0;
#endif
                return nEndpointValence;
        }
    return 0;
}
#endif

#if ( TAUT_PT_06_00 == 1 )
int nGetEndpointInfo_PT_06_00(inp_ATOM *atom, int iat, ENDPOINT_INFO *eif)
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;

    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
        return 0; /* a radical */
    nEndpointValence = atom[iat].el_number == EL_NUMBER_C ? 4 :
        get_endpoint_valence( atom[iat].el_number );
    /*printf("Connectivity: %d\n", atom[iat].valence);
    printf("Charge: %d\n", atom[iat].charge);
    printf("Actual valence: %d\n", atom[iat].chem_bonds_valence);
    printf("Num H: %d\n", atom[iat].num_H);*/
    if (!nEndpointValence)
        return 0; /* not an endpoint */
    if (nEndpointValence <= atom[iat].valence)
        return 0; /* not an endpoint, for example >N(+)< or >N<  or >O(+)- or >O- or >N- or -O- */

    if (atom[iat].charge == -1 || atom[iat].charge == 0) {
        /* not a positive charge-point */
        if (nEndpointValence < atom[iat].chem_bonds_valence)
            return 0; /* abnormal valence > standard endpoint valence */
        nMobile = atom[iat].num_H + (atom[iat].charge == -1);
        if (nMobile + atom[iat].chem_bonds_valence != nEndpointValence)
            return 0; /* non-standard endpoint valence */
        switch (atom[iat].chem_bonds_valence - atom[iat].valence) {
        case 0:
            eif->cDonor = 1;
            eif->cAcceptor = 0;
            break;
        case 1:
            eif->cDonor = 0;
            eif->cAcceptor = 1;
            break;
        default:
            return 0;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    else
        if (atom[iat].c_point &&
            0 <= GetChargeType(atom, iat, &cChargeSubtype) &&
            ((int)cChargeSubtype & (C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR))
            ) {
            /* charge-point */
            if (cChargeSubtype & C_SUBTYPE_H_ACCEPT) {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
            else
                if (cChargeSubtype & C_SUBTYPE_H_DONOR) {
                    eif->cDonor = 1;
                    eif->cAcceptor = 0;
                }
                else {
                    return 0;
                }
                eif->cMobile = atom[iat].num_H;
                eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
                eif->cMoveableCharge = atom[iat].charge;
#if ( KETO_ENOL_TAUT == 1 )
                eif->cKetoEnolCode = 0;
#endif
                return nEndpointValence;
        }
    return 0;
}
#endif

#if ( TAUT_PT_39_00 == 1 )
int nGetEndpointInfo_PT_39_00(inp_ATOM *atom, int iat, ENDPOINT_INFO *eif)
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;

    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
        return 0; /* a radical */
    nEndpointValence = atom[iat].el_number == EL_NUMBER_C ? 4 :
        atom[iat].el_number == EL_NUMBER_N ? 3 : 0;

    if (!nEndpointValence)
        return 0; /* not an endpoint */
    if (nEndpointValence <= atom[iat].valence)
        return 0; /* not an endpoint, for example >N(+)< or >N<  or >O(+)- or >O- or >N- or -O- */
                  /*
                  if ( nEndpointValence == 4 && !(atom[iat].valence == 3 || atom[iat].valence == 4))
                  return 0;
                  if ( nEndpointValence == 3 && !(atom[iat].valence == 2 || atom[iat].valence == 3))
                  return 0;
                  */
    if (atom[iat].charge == -1 || atom[iat].charge == 0) {
        /* not a positive charge-point */
        if (nEndpointValence < atom[iat].chem_bonds_valence)
            return 0; /* abnormal valence > standard endpoint valence */
        nMobile = atom[iat].num_H + (atom[iat].charge == -1);
        if (nMobile + atom[iat].chem_bonds_valence != nEndpointValence)
            return 0; /* non-standard endpoint valence */
        switch (atom[iat].chem_bonds_valence - atom[iat].valence) {
        case 0:
            eif->cDonor = 1;
            eif->cAcceptor = 0;
            break;
        case 1:
            eif->cDonor = 0;
            eif->cAcceptor = 1;
            break;
        default:
            return 0;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    else
        if (atom[iat].c_point &&
            0 <= GetChargeType(atom, iat, &cChargeSubtype) &&
            ((int)cChargeSubtype & (C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR))
            ) {
            /* charge-point */
            if (cChargeSubtype & C_SUBTYPE_H_ACCEPT) {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
            else
                if (cChargeSubtype & C_SUBTYPE_H_DONOR) {
                    eif->cDonor = 1;
                    eif->cAcceptor = 0;
                }
                else {
                    return 0;
                }
                eif->cMobile = atom[iat].num_H;
                eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
                eif->cMoveableCharge = atom[iat].charge;
#if ( KETO_ENOL_TAUT == 1 )
                eif->cKetoEnolCode = 0;
#endif
                return nEndpointValence;
        }
    return 0;
}
#endif

#if ( TAUT_PT_13_00 == 1 )
int nGetEndpointInfo_PT_13_00(inp_ATOM *atom, int iat, ENDPOINT_INFO *eif)
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;

    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
        return 0; /* a radical */
    nEndpointValence = atom[iat].el_number == EL_NUMBER_C ? 4 :
        atom[iat].el_number == EL_NUMBER_S ? 2 :
        atom[iat].el_number == EL_NUMBER_O ? 2 :
        atom[iat].el_number == EL_NUMBER_SE ? 2 :
        atom[iat].el_number == EL_NUMBER_TE ? 2 : 0;
    /*printf("Connectivity: %d\n", atom[iat].valence);
    printf("Charge: %d\n", atom[iat].charge);
    printf("Actual valence: %d\n", atom[iat].chem_bonds_valence);
    printf("Num H: %d\n", atom[iat].num_H);*/
    if (!nEndpointValence)
        return 0; /* not an endpoint */
    if (nEndpointValence <= atom[iat].valence)
        return 0; /* not an endpoint, for example >N(+)< or >N<  or >O(+)- or >O- or >N- or -O- */

    if (atom[iat].charge == -1 || atom[iat].charge == 0) {
        /* not a positive charge-point */
        if (nEndpointValence < atom[iat].chem_bonds_valence)
            return 0; /* abnormal valence > standard endpoint valence */
        nMobile = atom[iat].num_H + (atom[iat].charge == -1);
        if (nMobile + atom[iat].chem_bonds_valence != nEndpointValence)
            return 0; /* non-standard endpoint valence */
        if (nMobile > 0) {
            eif->cDonor = 1;
            eif->cAcceptor = 0;
        }
        else {
            eif->cDonor = 0;
            eif->cAcceptor = 1;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    else
        if (atom[iat].c_point &&
            0 <= GetChargeType(atom, iat, &cChargeSubtype) &&
            ((int)cChargeSubtype & (C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR))
            ) {
            /* charge-point */
            if (cChargeSubtype & C_SUBTYPE_H_ACCEPT) {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
            else
                if (cChargeSubtype & C_SUBTYPE_H_DONOR) {
                    eif->cDonor = 1;
                    eif->cAcceptor = 0;
                }
                else {
                    return 0;
                }
                eif->cMobile = atom[iat].num_H;
                eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
                eif->cMoveableCharge = atom[iat].charge;
#if ( KETO_ENOL_TAUT == 1 )
                eif->cKetoEnolCode = 0;
#endif
                return nEndpointValence;
        }
    return 0;
}
#endif

#if ( TAUT_PT_18_00 == 1 )
int nGetEndpointInfo_PT_18_00(inp_ATOM *atom, int iat, ENDPOINT_INFO *eif)
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;
    /* int res; removed */ 
    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
        return 0; /* a radical */
    nEndpointValence = atom[iat].el_number == EL_NUMBER_O ? 2 :
        atom[iat].el_number == EL_NUMBER_N ? 3 : 0;
    /*printf("Connectivity: %d\n", atom[iat].valence);
    printf("Charge: %d\n", atom[iat].charge);
    printf("Actual valence: %d\n", atom[iat].chem_bonds_valence);
    printf("Num H: %d\n", atom[iat].num_H);
    printf("c-point: %d\n", atom[iat].c_point);
    res = GetChargeType( atom, iat, &cChargeSubtype );
    printf("Charge subtype: %d %d\n", res, cChargeSubtype);*/
    if (!nEndpointValence)
        return 0; /* not an endpoint */

    if (atom[iat].charge == -1 || atom[iat].charge == 0) {
        /* not a positive charge-point */
        nMobile = atom[iat].num_H;
        if (nMobile > 0) {
            eif->cDonor = 1;
            eif->cAcceptor = 0;
        }
        else {
            eif->cDonor = 0;
            eif->cAcceptor = 1;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    else {
        if (atom[iat].c_point &&
            0 <= GetChargeType(atom, iat, &cChargeSubtype) &&
            ((int)cChargeSubtype & (C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR))
            ) {
            /* charge-point */
            if (cChargeSubtype & C_SUBTYPE_H_ACCEPT) {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
            else
                if (cChargeSubtype & C_SUBTYPE_H_DONOR) {
                    eif->cDonor = 1;
                    eif->cAcceptor = 0;
                }
        }
        else {
            /* charge-point */
            if (atom[iat].num_H) {
                eif->cDonor = 1;
                eif->cAcceptor = 0;
            }
            else {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
        }
        eif->cMobile = atom[iat].num_H;
        eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
        eif->cMoveableCharge = atom[iat].charge;
#if ( KETO_ENOL_TAUT == 1 )
        eif->cKetoEnolCode = 0;
#endif
        return nEndpointValence;
    }
    return 0;
}
#endif


/*****************************************************************************/
#if ( KETO_ENOL_TAUT == 1 )  /* post v.1 feature */


/****************************************************************************/
int nGetEndpointInfo_KET( inp_ATOM *atom,
                          int iat,
                          ENDPOINT_INFO *eif )
{
    int  nEndpointValence;
    int  nMobile;
    S_CHAR cChargeSubtype;

    if (atom[iat].radical && atom[iat].radical != RADICAL_SINGLET)
    {
        return 0; /* a radical */
    }
    if (!( nEndpointValence = get_endpoint_valence_KET( atom[iat].el_number ) ))
    {
        return 0; /* not an endpoint; only O and C can be an endpoint for keto-enol tautomerism */
    }
    if (nEndpointValence <= atom[iat].valence)
    {
        return 0; /* not an endpoint, for example >N(+)< or >N<  or >O(+)- or >O- or >N- or -O- */
    }
    if (nEndpointValence == 4 && atom[iat].valence < 2)
    {
        return 0; /* exclude O==C--CH3  <=> HO--C==CH2 */
    }
    if (nEndpointValence == 2 && atom[iat].valence > 1)
    {
        return 0; /* exclude --O--C==CH-- */
    }

    if (atom[iat].charge == -1 || atom[iat].charge == 0)
    {
        /* not a positive charge-point */
        if (nEndpointValence < atom[iat].chem_bonds_valence)
        {
            return 0; /* abnormal valence > standard endpoint valence */
        }
        nMobile = atom[iat].num_H + ( atom[iat].charge == -1 );
        if (nMobile + atom[iat].chem_bonds_valence != nEndpointValence)
        {
            return 0; /* non-standard endpoint valence */
        }

        switch (atom[iat].chem_bonds_valence - atom[iat].valence)
        {
            case 0:
                eif->cDonor = 1;
                eif->cAcceptor = 0;
                break;
            case 1:
                eif->cDonor = 0;
                eif->cAcceptor = 1;
                break;
            default:
                return 0;
        }
        eif->cMobile = nMobile;
        eif->cNeutralBondsValence = nEndpointValence - nMobile;
        eif->cMoveableCharge = 0;
        eif->cKetoEnolCode = ( nEndpointValence == 2 ) ? 1 : ( nEndpointValence == 4 ) ? 2 : 0;
        return nEndpointValence;
    }
    else
    {
        if (atom[iat].c_point &&
             0 <= GetChargeType( atom, iat, &cChargeSubtype ) &&
             ( (int) cChargeSubtype & ( C_SUBTYPE_H_ACCEPT | C_SUBTYPE_H_DONOR ) ))
        {
            /* charge-point; currently only O for keto-enol tautomerism */
            if (cChargeSubtype & C_SUBTYPE_H_ACCEPT)
            {
                eif->cDonor = 0;
                eif->cAcceptor = 1;
            }
            else
            {
                if (cChargeSubtype & C_SUBTYPE_H_DONOR)
                {
                    eif->cDonor = 1;
                    eif->cAcceptor = 0;
                }
                else
                {
                    return 0;
                }
            }
            eif->cMobile = atom[iat].num_H;
            eif->cNeutralBondsValence = nEndpointValence - atom[iat].num_H;
            eif->cMoveableCharge = atom[iat].charge;
            eif->cKetoEnolCode = ( nEndpointValence == 2 ) ? 1 : ( nEndpointValence == 4 ) ? 2 : 0;
            return nEndpointValence;
        }
    }

    return 0;
}
#endif


/****************************************************************************
RegisterEndPoints

Returns >0    => new registration happened,
=0    => no changes,
-1    => program error (debug)
****************************************************************************/
int RegisterEndPoints( CANON_GLOBALS *pCG,
                       T_GROUP_INFO *t_group_info,
                       /*
                       T_GROUP *t_group,
                       int *pnum_t,
                       int max_num_t,
                       */
                       T_ENDPOINT *EndPoint,
                       int nNumEndPoints,
                       inp_ATOM *at,
                       int num_atoms,
                       C_GROUP_INFO *cgi,
                       struct BalancedNetworkStructure *pBNS )
{
    T_GROUP  *t_group = t_group_info->t_group;
    int      *pnum_t = &t_group_info->num_t_groups;
    int       max_num_t = t_group_info->max_num_t_groups;
    int       nNumZeroEqu, nNumNewTGroups;
    AT_NUMB   group, prev_group, prev_eqnum, nNextGroupNumber, nLeastGroupNumber;
    int       nNumGroups, num_t, difference;
    int       i, j, k, ret;
    AT_NUMB   nNewTgNumberStackArray[MAX_STACK_ARRAY_LEN + 1];
    AT_NUMB   nGroupNumberStackArray[MAX_STACK_ARRAY_LEN + 1];
    AT_NUMB   nGroupNewNumberStackArray[MAX_STACK_ARRAY_LEN + 1];
    AT_NUMB  *nNewTgNumber = nNewTgNumberStackArray;
    AT_NUMB  *nGroupNumber = nGroupNumberStackArray;
    AT_NUMB  *nGroupNewNumber = nGroupNewNumberStackArray;

    if (nNumEndPoints <= 0)
    {
        return 0; /* nothing to do */
    }
    num_t = *pnum_t;
    difference = 0;
    nNextGroupNumber = 0;
    nNumZeroEqu = 0;
    /* djb-rwth: removing redundant code */
    /* find max group number; increment it to obtain next available group number */
    for (i = 0; i < num_t; i++)
    {
        if (nNextGroupNumber < t_group[i].nGroupNumber)
        {
            nNextGroupNumber = t_group[i].nGroupNumber;
        }
    }
    nNextGroupNumber++;

    /* find min non-zero group number nLeastGroupNumber;
    count zero EndPoint[i].nEquNumber
    if all EndPoint[i].nGroupNumber are equal and non-zero then exit: nothing to do.
    */
    nLeastGroupNumber = nNextGroupNumber;
    prev_group = EndPoint[0].nGroupNumber;
    prev_eqnum = EndPoint[0].nEquNumber;
    for (i = j = k = 0; i < nNumEndPoints; i++)
    {
        if ((group = EndPoint[i].nGroupNumber)) /* djb-rwth: addressing LLVM warning */
        {
            if (group < nLeastGroupNumber)
            {
                nLeastGroupNumber = group;
            }
        }
        j += ( prev_group == EndPoint[i].nGroupNumber ); /* count endpoints that belong to the 1st group */
        k += ( prev_eqnum == EndPoint[i].nEquNumber );   /* count endpoints that belongo to a group equivalent to the 1st group */
        nNumZeroEqu += !EndPoint[i].nEquNumber;        /* count endpoints that have been processed by FindAccessibleEndPoints() */
    }
    if (j == nNumEndPoints && prev_group && k == nNumEndPoints)
    {
        /* all endpoints already belong to one t-group;
        the last comparison is not needed for now
        because EndPoint[i].nEquNumber cannot make
        endpont partitioning finer
        */
        return 0;
    }

    nNumNewTGroups = 0;

    if (!nNumZeroEqu)
    {
        /* EndPoint[] has been processed by FindAccessibleEndPoints;
        * equal EndPoint[i].nEquNumber mark endpoints belonging to
        * the same t-group
        * Since now the next available t-group number, nNextGroupNumber,
        * is known,replace fict. IDs assigned by FindAccessibleEndPoints
        * with correct new t-group numbers.
        */
        for (i = 0; i < nNumEndPoints; i++)
        {
            if (( group = EndPoint[i].nEquNumber ) >= nNextGroupNumber)
            {
                /* replace fict. IDs assigned by FindAccessibleEndPoints() with new t-group numbers */
                /* these fict. IDs have values = (num_atoms+1), (num_atoms+2),...; they may be non-contiguous */
                for (j = 0; j < nNumNewTGroups; j++)
                {
                    if (group == nGroupNewNumber[j])
                    {
                        break;
                    }
                }
                if (j == nNumNewTGroups)
                {
                    /* found new fict. ID = group */
                    if (j == MAX_STACK_ARRAY_LEN && nGroupNewNumber == nGroupNewNumberStackArray)
                    {
                        /* stack array overflow; allocate more memory than may be needed */
                        nGroupNewNumber = (AT_NUMB *) inchi_malloc( nNumEndPoints * sizeof( nGroupNewNumber[0] ) );
                        if (!nGroupNewNumber)
                        {
                            ret = -1;
                            goto exit_function;
                        }
                        memcpy(nGroupNewNumber, nGroupNewNumberStackArray, nNumNewTGroups * sizeof(nGroupNewNumber[0]));
                    }
                    /* save newly found fict. t-group ID to compare to the next values of EndPoint[].nEquNumber  */
                    nGroupNewNumber[j] = group;
                    nNumNewTGroups++;
                }
                EndPoint[i].nEquNumber = nNextGroupNumber + j;
            }
        }   /* after this point the values just stored in nGroupNewNumber[] will not
            be used. However, the obtained nNumNewTGroups value will be used */
    }
    else
    {
        if (nNumZeroEqu == nNumEndPoints)
        {
            /* EndPoint[] has NOT been processed by FindAccessibleEndPoints;
            all atoms and t-groups to which endpoints belong should
            be merged into a single t-group                              */
            if (nLeastGroupNumber == nNextGroupNumber)
            {
                /* flag to create a new t-group: none of the found
                endpoints belong to an already known t-group             */
                nNumNewTGroups = 1; /* otherwise 0 */
            }
            /* All EndPoint[*].nEquNumber are zeroes. All endpoints will
            * belong to one new or old t-group; its ID is nLeastGroupNumber.
            * Set  EndPoint[i].nEquNumber = nLeastGroupNumber;             */
            for (i = 0; i < nNumEndPoints; i++)
            {
                EndPoint[i].nEquNumber = nLeastGroupNumber;
            }
        }
        else
        {
            ret = -1; /* program error: only some of EndPoint[i].nEquNumber are zero */ /*   <BRKPT> */
            goto exit_function;
        }
    }

    if (nNumNewTGroups)
    {
        /* create new nNumNewTGroups t-group(s) */
        if (num_t + nNumNewTGroups > max_num_t)
        {
            ret = -1; /* found too many t-groups */ /*   <BRKPT> */
            goto exit_function;
        }
        /* initialize new t-group(s) */
        memset( t_group + num_t, 0, nNumNewTGroups * sizeof( t_group[0] ) ); /* djb-rwth: memset_s C11/Annex K variant? */
        for (i = 0; i < nNumNewTGroups; i++)
        {
            t_group[num_t + i].nGroupNumber = nNextGroupNumber + i;
        }
    }

    /* At this point:
    *   EndPoint[i].nGroupNumber == 0   => the endpoint atom does not belong to a t-group yet
    *   EndPoint[i].nGroupNumber  > 0   => current t-group ID of the endpoint atom
    *   EndPoint[i].nEquNumber      -->    new ID of a tautomeric group of this endpoint atom
    *   EndPoint[i].nAtomNumber     -->    number of the endpoint atom
    */

    nNumGroups = 0; /* counts the groups to be renumbered */
    for (i = j = 0; i < nNumEndPoints; i++)
    {
        if ((group = EndPoint[i].nGroupNumber)) /* djb-rwth: addressing LLVM warning */
        {
            if (group == EndPoint[i].nEquNumber)
            {
                continue; /* ignore: the endpoint belongs to the same t-group as before */
            }
            /* save information for renumbering of the existing t-groups */
            for (j = 0; j < nNumGroups; j++)
            {
                if (group == nGroupNumber[j])
                {
                    if (EndPoint[i].nEquNumber != nGroupNewNumber[j])
                    {
                        ret = -1; /* program error */ /*   <BRKPT> */
                        goto exit_function;
                    }
                    break;
                }
            }
            if (j == nNumGroups)
            {
                /* discovered a new t-group number; store it together with its nEquNumber */
                if (j == MAX_STACK_ARRAY_LEN)
                {
                    if (nGroupNewNumber == nGroupNewNumberStackArray)
                    {
                        nGroupNewNumber = (AT_NUMB *) inchi_malloc( nNumEndPoints * sizeof( nGroupNewNumber[0] ) );
                        if (!nGroupNewNumber)
                        {
                            ret = -1;
                            goto exit_function;
                        }
                        memcpy(nGroupNewNumber, nGroupNewNumberStackArray, nNumGroups * sizeof(nGroupNewNumber[0]));
                    }
                    if (nGroupNumber == nGroupNumberStackArray)
                    {
                        nGroupNumber = (AT_NUMB *) inchi_malloc( nNumEndPoints * sizeof( nGroupNumber[0] ) );
                        if (!nGroupNumber)
                        {
                            ret = -1;
                            goto exit_function;
                        }
                        memcpy(nGroupNumber, nGroupNumberStackArray, nNumGroups * sizeof(nGroupNumber[0]));
                    }
                }

                nGroupNumber[j] = group;  /* old t-group ID */
                nGroupNewNumber[j] = EndPoint[i].nEquNumber; /* new t-group ID */
                nNumGroups++;
            }
        }
        else
        {
            /* add a new endpoint to the newly created or previously existing t-groups */
            group = EndPoint[i].nEquNumber;
            if (group >= nNextGroupNumber)
            {
                /* get index of a new t-group from equ number */
                j = num_t + group - nNextGroupNumber; /* newly assigned IDs are contiguous */
            }
            else
            {
                /* old t-group */
                if (j >= num_t || group != t_group[j].nGroupNumber)
                {
                    /* search only if j is not a needed group index */
                    for (j = 0; j < num_t; j++)
                    {
                        if (group == t_group[j].nGroupNumber)
                        {
                            break;
                        }
                    }
                    if (j == num_t)
                    {
                        ret = -1; /* program error: t-group not found */ /*   <BRKPT> */
                        goto exit_function;
                    }
                }
            }
            /* add aton to existing or new t-group */
            t_group[j].nNumEndpoints++;
            for (k = 0; k < (int) ( sizeof( t_group->num ) / sizeof( t_group->num[0] ) ); k++)
            {
                t_group[j].num[k] += EndPoint[i].num[k];
            }
            for (k = 0; k < (int) ( sizeof( t_group->num_DA ) / sizeof( t_group->num_DA[0] ) ); k++)
            {
                t_group[j].num_DA[k] += EndPoint[i].num_DA[k];
            }
            /* mark endpoint */
            at[EndPoint[i].nAtomNumber].endpoint = group;
            difference++;
        }
    }

    difference += nNumGroups;
    num_t += nNumNewTGroups;
    if (!difference)
    {
        ret = 0; /* nothing to do. Not necessarily a program error: happens if all EndPoint[i].nGroupNumber==EndPoint[i].nEquNumber  */
        goto exit_function;
    }

    if (nNumGroups)
    {
        /* prepare for renumbering: find max t-group number */
        for (i = 0, nNextGroupNumber = 0; i < num_t; i++)
        {
            if (nNextGroupNumber < t_group[i].nGroupNumber)
            {
                nNextGroupNumber = t_group[i].nGroupNumber;
            }
        }
    }
    /* renumber and merge t-groups */
    for (i = 0; i < nNumGroups; i++)
    {
        int i1, i2;
        AT_NUMB group1 = nGroupNumber[i];
        AT_NUMB group2 = nGroupNewNumber[i];
        /* add group1 to group2, then delete group1. */
        for (j = 0, i1 = i2 = -1; j < num_t && ( i1 < 0 || i2 < 0 ); j++)
        {
            if (i1 < 0 && group1 == t_group[j].nGroupNumber)
            {
                i1 = j;
            }
            if (i2 < 0 && group2 == t_group[j].nGroupNumber)
            {
                i2 = j;
            }
        }
        if (i1 < 0 || i2 < 0)
        {
            ret = -1; /* program error */ /*   <BRKPT> */
            goto exit_function;
        }
        /*  add t_group[i1] to t_group[i2] and remove t_group[i1] */
        for (k = 0; k < (int) ( sizeof( t_group->num ) / sizeof( t_group->num[0] ) ); k++)
        {
            t_group[i2].num[k] += t_group[i1].num[k];
        }
        for (k = 0; k < (int) ( sizeof( t_group->num_DA ) / sizeof( t_group->num_DA[0] ) ); k++)
        {
            t_group[i2].num_DA[k] += t_group[i1].num_DA[k];
        }
        t_group[i2].nNumEndpoints += t_group[i1].nNumEndpoints;
        num_t--;
        if (num_t > i1)
        {
            memmove(t_group + i1, t_group + i1 + 1, ((long long)num_t - (long long)i1) * sizeof(t_group[0])); /* djb-rwth: cast operators added */
        }
    }

    if (nNumGroups)
    {
        /* there are groups to merge */
        if (nNextGroupNumber >= MAX_STACK_ARRAY_LEN)
        {
            nNewTgNumber = (AT_NUMB *) inchi_malloc( ( (long long)nNextGroupNumber + 1 ) * sizeof( *nNewTgNumber ) ); /* djb-rwth: cast operator added */
            if (!nNewTgNumber)
            {
                ret = -1;
                goto exit_function; /* error: out of RAM */
            }
        }
        memset( nNewTgNumber, 0, ( (long long)nNextGroupNumber + 1 ) * sizeof( *nNewTgNumber ) ); /* djb-rwth: cast operator added; memset_s C11/Annex K variant? */
        for (i = 0; i < num_t; i++)
        {
            nNewTgNumber[t_group[i].nGroupNumber] = i + 1; /* new t-group numbers */
        }
        for (j = 0; j < nNumGroups; j++)
        {
            if (!nNewTgNumber[nGroupNumber[j]] && nNewTgNumber[nGroupNewNumber[j]])
            {
                nNewTgNumber[nGroupNumber[j]] = nNewTgNumber[nGroupNewNumber[j]];
            }
            else
            {
                ret = -1; /* program error: all new numbers must have been marked */
                goto exit_function;
            }
        }
        /* renumber t-groups */
        for (i = 0; i < num_t; i++)
        {
            t_group[i].nGroupNumber = nNewTgNumber[t_group[i].nGroupNumber];
        }
#if ( bRELEASE_VERSION != 1 )
        /* Check: debug only */
        for (i = 1; i < num_t; i++)
        {
            if (1 != t_group[i].nGroupNumber - t_group[i - 1].nGroupNumber)
            {
                ret = -1; /* debug */
                goto exit_function;
            }
        }
#endif
        /* renumber endpoints */
        for (i = 0; i < num_atoms; i++)
        {
            if ((group = at[i].endpoint)) /* djb-rwth: addressing LLVM warning */
            {
                if (!( at[i].endpoint = nNewTgNumber[group] ) || nNextGroupNumber <= nNewTgNumber[group])
                {
                    ret = -1; /* program error */
                    goto exit_function;
                }
            }
        }
    }
    if (nNewTgNumber != nNewTgNumberStackArray)
    {
        inchi_free( nNewTgNumber );
        nNewTgNumber = nNewTgNumberStackArray;
    }
    if (nGroupNumber != nGroupNumberStackArray)
    {
        inchi_free( nGroupNumber );
        nGroupNumber = nGroupNumberStackArray;
    }
    if (nGroupNewNumber != nGroupNewNumberStackArray)
    {
        inchi_free( nGroupNewNumber );
        nGroupNewNumber = nGroupNewNumberStackArray;
    }
    if (!t_group_info->tGroupNumber)
    {
        t_group_info->tGroupNumber = (AT_NUMB *) inchi_malloc( 2 * (long long)max_num_t * sizeof( t_group_info->tGroupNumber[0] ) ); /* djb-rwth: cast operator added */
        if (!t_group_info->tGroupNumber)
        {
            ret = -1;
            goto exit_function;
        }
    }
    /* fill out t-group index 2004-02-27 */
    memset( t_group_info->tGroupNumber, 0, 2 * (long long)max_num_t * sizeof( t_group_info->tGroupNumber[0] ) ); /* djb-rwth: cast operator added; memset_s C11/Annex K variant? */
    for (i = 0; i < num_t; i++)
    {
        if (t_group[i].nNumEndpoints && t_group[i].nGroupNumber)
        {
            t_group_info->tGroupNumber[t_group[i].nGroupNumber] = i + 1;
        }
    }

    if (pBNS && ( pBNS->tot_st_cap == pBNS->tot_st_flow || ALWAYS_ADD_TG_ON_THE_FLY ))
    {
        T_GROUP_INFO tgi;
        int ret_bns;
        memset( &tgi, 0, sizeof( tgi ) ); /* djb-rwth: memset_s C11/Annex K variant? */
        tgi.num_t_groups = num_t;
        tgi.t_group = t_group;
#if ( KETO_ENOL_TAUT == 1 )
        tgi.bTautFlags |= ( t_group_info->bTautFlags & TG_FLAG_KETO_ENOL_TAUT ); /* needed in AddTGroups2BnStruct() */
#endif

#if ( TAUT_PT_22_00 == 1 )
        tgi.bTautFlags |= (t_group_info->bTautFlags & TG_FLAG_PT_22_00); /* needed in AddTGroups2BnStruct() */
#endif
#if ( TAUT_PT_16_00 == 1 )
        tgi.bTautFlags |= (t_group_info->bTautFlags & TG_FLAG_PT_16_00); /* needed in AddTGroups2BnStruct() */
#endif
#if ( TAUT_PT_06_00 == 1 )
        tgi.bTautFlags |= (t_group_info->bTautFlags & TG_FLAG_PT_06_00); /* needed in AddTGroups2BnStruct() */
#endif
#if ( TAUT_PT_39_00 == 1 )
        tgi.bTautFlags |= (t_group_info->bTautFlags & TG_FLAG_PT_39_00); /* needed in AddTGroups2BnStruct() */
#endif
#if ( TAUT_PT_13_00 == 1 )
        tgi.bTautFlags |= (t_group_info->bTautFlags & TG_FLAG_PT_13_00); /* needed in AddTGroups2BnStruct() */
#endif
#if ( TAUT_PT_18_00 == 1 )
        tgi.bTautFlags |= (t_group_info->bTautFlags & TG_FLAG_PT_18_00); /* needed in AddTGroups2BnStruct() */
#endif	
                                                                         /* reinitialize BN Structure */
        ret_bns = ReInitBnStruct( pBNS, at, num_atoms, 0 );
        if (IS_BNS_ERROR( ret_bns ))
        {
            return ret_bns;
        }
        if (*pBNS->pbTautFlags & TG_FLAG_MOVE_POS_CHARGES)
        {
            /* set new charge groups */
            ret_bns = AddCGroups2BnStruct( pCG, pBNS, at, num_atoms, cgi );
            if (IS_BNS_ERROR( ret_bns ))
            {
                return ret_bns;
            }
        }
        /* set new tautomeric groups */
        ret_bns = AddTGroups2BnStruct( pCG, pBNS, at, num_atoms, &tgi );
        if (IS_BNS_ERROR( ret_bns ))
        {
            return ret_bns;
        }
    }

    *pnum_t = num_t;
    return difference;

exit_function:
    if (nNewTgNumber != nNewTgNumberStackArray)
    {
        inchi_free( nNewTgNumber );
    }
    if (nGroupNumber != nGroupNumberStackArray)
    {
        inchi_free( nGroupNumber );
    }
    if (nGroupNewNumber != nGroupNewNumberStackArray)
    {
        inchi_free( nGroupNewNumber );
    }

    return ret;
}


/****************************************************************************
Change non-alternating and non-tautomeric bonds
(that is, single and double bonds) to tautomeric
****************************************************************************/
int SetTautomericBonds( inp_ATOM *at,
                        int nNumBondPos,
                        T_BONDPOS *BondPos )
{
    int k, n;
    for (k = n = 0; k < nNumBondPos; k++)
    {
        int neighbor_index = BondPos[k].neighbor_index;
        int center = BondPos[k].nAtomNumber;
        int bond_mark = at[center].bond_type[neighbor_index];
        int bond_type = bond_mark & ~BOND_MARK_ALL;
        int neighbor;
#if ( REPLACE_ALT_WITH_TAUT == 1 )
        if (bond_type != BOND_TAUTOM)
#else
        if (bond_type != BOND_ALTERN && bond_type != BOND_TAUTOM)
#endif
        {
            int ii;
            /*  change bond type to BOND_TAUTOM presering higher bits marks */
            bond_type = ( bond_mark & BOND_MARK_ALL ) | BOND_TAUTOM;
            /*  change center-neighbor bond */
            at[center].bond_type[neighbor_index] = bond_type;
            neighbor = at[center].neighbor[neighbor_index];
            for (ii = 0; ii < at[neighbor].valence; ii++)
            {
                if (at[neighbor].neighbor[ii] == center)
                {
                    /*  neighbor-center bond found */
                    at[neighbor].bond_type[ii] = bond_type;
                    break;
                }
            }
            n++;
        }
    }

    return n;
}


/****************************************************************************/
int GetNeutralRepsIfNeeded( AT_NUMB      *pri,
                            AT_NUMB      *prj,
                            inp_ATOM     *at,
                            int          num_atoms,
                            T_ENDPOINT   *EndPoint,
                            int          nNumEndPoints,
                            C_GROUP_INFO *cgi )
{
    AT_NUMB ri = *pri;
    AT_NUMB rj = *prj;
    int     i, k;
    AT_NUMB c_point, endpoint, r;

    if (( c_point = at[ri].c_point ) &&
        ( at[rj].c_point == c_point ) &&
         ( at[ri].charge == 1 || at[rj].charge == 1 ) &&
         cgi                                        &&
         cgi->num_c_groups > 0)
    {
        /* at[ri] and at[rj] belong to the same charge group, at least one is charged   */
        for (k = 0; k < cgi->num_c_groups; k++) /* MS VC++ 2008 reports unreachable code here ??? */
        {
            if (cgi->c_group[k].nGroupNumber == c_point)
            {
                /* cgi->c_group[k] is found to be this charge group */
                if (cgi->c_group[k].num_CPoints - cgi->c_group[k].num[0] < 2)
                {
                    /* Only one neutral in the c-group: we will not be able to neutralize both */
                    /* when looking for the alt path to discover the tautomerism.              */
                    /* Therefore we need to find a neutral t-group representative at[rj]       */
                    if ((endpoint = at[rj].endpoint)) /* djb-rwth: addressing LLVM warning */
                    {
                        for (i = 0; i < nNumEndPoints; i++)
                        {
                            if (( r = EndPoint[i].nAtomNumber ) == *prj)
                            {
                                continue; /* ignore at[*prj] */
                            }
                            if (at[r].endpoint != endpoint)
                            {
                                continue; /* at[r] does not belong to the same t-group as at[*prj]; ignore the atom */
                            }
                            if (!at[r].c_point)
                            {
                                rj = r; /* found a neutral t-group representative */
                                break;
                            }
                            if (at[r].c_point != c_point && c_point == at[rj].c_point)
                            {
                                /* replace only once because of (c_point == at[rj].c_point) condition  */
                                rj = r;
                            }
                        }
                        if (rj == *prj /*&& at[ri].endpoint*/)
                        {
                            /* !!! "&& at[ri].endpoint": only between 2 t-groups 2004-02-27;
                            the change disabled due to undiscovered yet possibility of ambiguity*/
                            /* no replacement has been found in EndPoint[]; try all atoms in the t-group */
                            for (i = 0; i < num_atoms; i++)
                            {
                                if (at[i].endpoint != endpoint)
                                {
                                    continue;
                                }
                                if (i == (int) *prj)
                                {
                                    continue;
                                }
                                if (!at[i].c_point)
                                {
                                    rj = (AT_NUMB) i; /* found neutral t-group representative */
                                    break;
                                }
                                if (at[i].c_point != c_point && c_point == at[rj].c_point)
                                {
                                    /* replace only once */
                                    rj = (AT_NUMB) i;
                                }
                            }
                        }
                    }
                    /* at[ri] */
                    if ((endpoint = at[ri].endpoint)) /* djb-rwth: addressing LLVM warning */
                    {
                        for (i = 0; i < nNumEndPoints; i++)
                        {
                            if (( r = EndPoint[i].nAtomNumber ) == *pri)
                            {
                                continue;
                            }
                            if (at[r].endpoint != endpoint)
                            {
                                continue;
                            }
                            if (!at[r].c_point)
                            {
                                ri = r; /* found neutral t-group representative */
                                break;
                            }
                            if (at[r].c_point != c_point && c_point == at[ri].c_point &&
                                 at[r].c_point != at[rj].c_point)
                            {
                                /* replace only once */
                                ri = r;
                            }
                        }
                        if (ri == *pri && at[rj].endpoint)
                        {
                            /* !!! "&& at[rj].endpoint": only between 2 t-groups 2004-02-27;
                            the change disabled due to undiscovered yet possibility of ambiguity */
                            for (i = 0; i < num_atoms; i++)
                            {
                                if (at[i].endpoint != endpoint)
                                {
                                    continue;
                                }
                                if (i == (int) *pri)
                                {
                                    continue;
                                }
                                if (!at[i].c_point)
                                {
                                    ri = (AT_NUMB) i; /* found neutral t-group representative */
                                    break;
                                }
                                if (at[i].c_point != c_point && c_point == at[ri].c_point &&
                                     at[i].c_point != at[rj].c_point)
                                {
                                    /* replace only once */
                                    ri = (AT_NUMB) i;
                                }
                            }
                        }
                    }
                }
            }
            break;
        }
        *prj = rj;
        *pri = ri;
    }

    return 0;
}


/****************************************************************************/
int FindAccessibleEndPoints( CANON_GLOBALS *pCG,
                             T_ENDPOINT *EndPoint,
                             int *nNumEndPoints,
                             T_BONDPOS *BondPos,
                             int *nNumBondPos,
                             struct BalancedNetworkStructure *pBNS,
                             struct BalancedNetworkData *pBD,
                             inp_ATOM *at,
                             int num_atoms,
                             C_GROUP_INFO *cgi,
                             int taut_mode )
{
    AT_NUMB nTGroupRepresenative[MAXVAL], nTGroupEqu[MAXVAL], nTGEndPointNo[MAXVAL], ri, rj;
    AT_NUMB nCurTGroupNumber, nMaxTGroupNumber, nNumTgroupNumbers, nMaxEquNumber;
    int   i, j, k, nNumDiffTGroupNumbers = 0, nNumFoundEqu, nErr;

    if (*nNumEndPoints != *nNumBondPos)
        return 0;
    /* collect all group numbers. Fill EndPoint[i].nEquNumber */
    for (i = 0; i < *nNumEndPoints; i++)
    {
        nCurTGroupNumber = EndPoint[i].nEquNumber = EndPoint[i].nGroupNumber; /* initial equivalence */
        if (nCurTGroupNumber)
        {
            /* found endpoint that already belongs to a t-group */
            for (j = 0; j < nNumDiffTGroupNumbers; j++)
            {
                if (nTGroupEqu[j] == nCurTGroupNumber)
                {
                    break;
                }
            }
            if (j == nNumDiffTGroupNumbers)
            {
                nTGroupRepresenative[nNumDiffTGroupNumbers] = EndPoint[i].nAtomNumber;
                nTGroupEqu[nNumDiffTGroupNumbers] = EndPoint[i].nGroupNumber;
                nTGEndPointNo[nNumDiffTGroupNumbers] = i;
                nNumDiffTGroupNumbers++;
            }
        }
    }

    /* check whether each pair belongs to the same t-group and establish the equivalence(s) */
    for (i = 0, nNumFoundEqu = 0; i < nNumDiffTGroupNumbers; i++)
    {
        for (j = i + 1; j < nNumDiffTGroupNumbers; j++)
        {
            ri = nTGroupRepresenative[i];
            rj = nTGroupRepresenative[j];
            /* both at[ri] and at[rj] are known to belong to tautomeric groups */
            GetNeutralRepsIfNeeded( &ri, &rj, at, num_atoms, EndPoint, *nNumEndPoints, cgi );
            nErr = bExistsAnyAltPath( pCG, pBNS, pBD, at, num_atoms, ri, rj, taut_mode );
            if (IS_BNS_ERROR( nErr ))
            {
                return nErr;
            }
            if (0 == nErr)
            {
                continue; /* alt path between at[ri] and at[rj] not found */
            }
            nCurTGroupNumber = inchi_min( nTGroupEqu[i], nTGroupEqu[j] );
            nMaxTGroupNumber = inchi_max( nTGroupEqu[i], nTGroupEqu[j] );
            for (k = 0; k < nNumDiffTGroupNumbers; k++)
            {
                if (nTGroupEqu[k] == nMaxTGroupNumber)
                {
                    nTGroupEqu[k] = nCurTGroupNumber;
                    nNumFoundEqu++;
                }
            }
            for (k = 0; k < *nNumEndPoints; k++)
            {
                if (EndPoint[k].nEquNumber == nMaxTGroupNumber)
                {
                    EndPoint[k].nEquNumber = nCurTGroupNumber;
                }
            }
        }
    }

    if (nNumFoundEqu)
    {
        /* leave in only non-equivalent representatives */
        for (i = 1; i < nNumDiffTGroupNumbers; i++) /* djb-rwth: removing redundant code */
        {
            for (j = 0; j < i; j++)
            {
                if (nTGroupEqu[j] == nTGroupEqu[i])
                {
                    nTGroupEqu[i] = 0;  /* i > j; mark equivalent for removal*/
                    break;
                }
            }
        }
        for (i = j = 0; i < nNumDiffTGroupNumbers; i++)
        {
            if (nTGroupEqu[i])
            {
                if (i != j)
                { /* remove the marked */
                    nTGroupEqu[j] = nTGroupEqu[i];
                    nTGroupRepresenative[j] = nTGroupRepresenative[i];
                    nTGEndPointNo[j] = nTGEndPointNo[i];
                }
                j++;
            }
        }
        nNumDiffTGroupNumbers = j; /* number of known t-group representatives */
    }

    /* collect endpoints that have not been assigned to t-groups */
    for (i = 0, j = nNumDiffTGroupNumbers; i < *nNumEndPoints; i++)
    {
        if (EndPoint[i].nEquNumber)
        {
            continue;
        }
        nTGroupEqu[j] = 0;
        nTGroupRepresenative[j] = EndPoint[i].nAtomNumber;
        nTGEndPointNo[j] = i;
        j++;
    }
    nNumTgroupNumbers = j;
    nMaxEquNumber = num_atoms + 1; /* impossible atom or t-group number */

                                   /* check whether each pair belongs to the same group and establish the equivalence(s) */
    for (i = 0, nNumFoundEqu = 0; i < nNumTgroupNumbers; i++)
    {
        for (j = i + 1; j < nNumTgroupNumbers; j++)
        {
            if ((nTGroupEqu[i] != nTGroupEqu[j] && ( i >= nNumDiffTGroupNumbers || j >= nNumDiffTGroupNumbers )) ||
                 /* equivalence of a t-group and a non-t-group atom */
                 (!nTGroupEqu[i] && !nTGroupEqu[j])
                 /* equivalence of two non-t-group atoms */
                 ) /* djb-rwth: addressing LLVM warnings */
            {
                ri = nTGroupRepresenative[i];
                rj = nTGroupRepresenative[j];
                /*------------------------------!!!---------------------------------------------
                Explanation why GetNeutralRepsIfNeeded() may need to be changed 2004-02-27
                The change has been disabled due to undiscovered yet possibility of ambiguity
                to search for neutral only among EndPoint[] in case taut-not_taut pairs

                Counterexample:   O=C-NH(+)=C-NH2
                1   2       3

                Has already been found: 2-3 (+)-charge exchange
                1-2 tautomerism (charge removed to 3)
                Now testing: 2-3 tautomerism. If not commented out,
                GetNeutralRepsIfNeeded() would replace 2-3 test with 1-3 test because:
                o Charge group has only one neutral and both 2 and 3 belong to it,
                therefore we cannot neutralize both; search for neutral representative;
                o Since 1 and 2 belong to the same t-group and 1 is neutral,
                test 1-3 instead of 2-3.
                This breaks our condition:
                Test tautomeric H movement only between neutral atoms.
                -----------------------------------------------------------------------------*/
                GetNeutralRepsIfNeeded( &ri, &rj, at, num_atoms, EndPoint, *nNumEndPoints, cgi );

                nErr = bExistsAnyAltPath( pCG, pBNS, pBD, at, num_atoms, ri, rj, taut_mode );
                if (IS_BNS_ERROR( nErr ))
                {
                    return nErr;
                }
                if (nErr <= 0)
                {
                    continue;
                }
                if (nTGroupEqu[i] && nTGroupEqu[j])
                {
                    /* found equivalence of two t-groups; at least one of them must be a new one */
                    nCurTGroupNumber = inchi_min( nTGroupEqu[i], nTGroupEqu[j] );
                    nMaxTGroupNumber = inchi_max( nTGroupEqu[i], nTGroupEqu[j] );
                    for (k = 0; k < nNumTgroupNumbers; k++)
                    {
                        if (nTGroupEqu[k] == nMaxTGroupNumber)
                        {
                            nTGroupEqu[k] = nCurTGroupNumber;
                            nNumFoundEqu++;
                        }
                    }
                    for (k = 0; k < *nNumEndPoints; k++)
                    {
                        if (EndPoint[k].nEquNumber == nMaxTGroupNumber)
                        {
                            EndPoint[k].nEquNumber = nCurTGroupNumber;
                        }
                    }
                }
                else
                {
                    if (nTGroupEqu[i])
                    { /* extend existing t-group */
                        nTGroupEqu[j] = nTGroupEqu[i];
                        EndPoint[nTGEndPointNo[j]].nEquNumber = nTGroupEqu[i];
                    }
                    else
                    {
                        if (nTGroupEqu[j])
                        { /* extend existing t-group */
                            nTGroupEqu[i] = nTGroupEqu[j];
                            EndPoint[nTGEndPointNo[i]].nEquNumber = nTGroupEqu[j];
                        }
                        else
                        { /* establis a new t-group */
                            nTGroupEqu[i] =
                                nTGroupEqu[j] = nMaxEquNumber; /* assign a fict. ID to establish equivalence */
                            EndPoint[nTGEndPointNo[i]].nEquNumber =
                                EndPoint[nTGEndPointNo[j]].nEquNumber = nMaxEquNumber;
                            nMaxEquNumber++;
                        }
                    }
                }
            }
        }
    }

    /* eliminate endpoints and bonds that do not belong to t-group(s)
    (they have not been found connected by an alt path to any other endpoint)
    */
    for (i = 0, j = 0; i < *nNumEndPoints; i++)
    {
        if (EndPoint[i].nEquNumber)
        {
#if ( IGNORE_SINGLE_ENDPOINTS == 1 )  /* 1-28-2003 */
            for (k = 0, nNumFoundEqu = 0; k < *nNumEndPoints; k++)
            {
                nNumFoundEqu += ( EndPoint[i].nEquNumber == EndPoint[k].nEquNumber );
            }
            if (nNumFoundEqu <= 1)
            {
                /* one time it is equal to itself when i == k above */
                /* if EndPoint[i] is not "equivalent" to any other EndPoint then ignore it */
                continue;
            }
#endif
            if (i != j)
            {
                /* save endpoints that are found to be connected to other endpoints by alt paths */
                EndPoint[j] = EndPoint[i];
                BondPos[j] = BondPos[i];
            }
            j++;
        }
    }

#if ( IGNORE_SINGLE_ENDPOINTS != 1 )  /* 1-28-2003 */
    /* Do not allow a centerpoint to have only one tautomeric bond */
    /* Hack: we may have only one centerpoint */
    /* BondPos[*].nAtomNumber are centerpoints */
    if (j == 1)
    {
        /* check if there exist other centerpoint neighbors
        * connected to it by another tautomeric-bond
        */
        for (i = 0, k = 0; i < at[BondPos[0].nAtomNumber].valence; i++)
        {
            k += ( i != BondPos[0].neighbor_index &&
                   BOND_TAUTOM == ( at[BondPos[0].nAtomNumber].bond_type[i] & ~BOND_MARK_ALL ) );
        }
        if (!k)
        {
            j = 0;
        }
    }
#endif

    *nNumEndPoints = *nNumBondPos = j;

    return j;
}


/****************************************************************************/
/*#if ( MOVE_CHARGES == 1 ) */  /* { */
                                /****************************************************************************/

                                /**********************************************/
                                /*                                            */
                                /* definitions for positive ion recognition   */
                                /*                                            */
                                /**********************************************/


                                /****************************************************************************/
typedef struct tagChargeType
{
    /* meaning see in bCanBeACPoint() */
    char    elname[3];
    S_CHAR  charge;
    S_CHAR  neutral_valence;
    S_CHAR  neutral_bonds_valence; /* valence of a neutral atom */
    S_CHAR  cChangeValence; /* charge increases valence by this value */
    S_CHAR  cChargeType;    /* different types are treated separately */
    S_CHAR  num_bonds;      /* added 02-06-2005 */
} CHARGE_TYPE;

const CHARGE_TYPE CType[] =
{
    { "N\0",  1, 3, 3, 1, 0, 0 },
    { "P\0",  1, 3, 3, 1, 1, 0 },
#if ( ADD_MOVEABLE_O_PLUS == 1 )
    { "O\0",  1, 2, 2, 1, 2, 2 }, /* added 02-06-2005 */
    { "S\0",  1, 2, 2, 1, 3, 2 }, /* added 03-18-2005 */
    { "Se",   1, 2, 2, 1, 4, 2 }, /* added 03-18-2005 */
    { "Te",   1, 2, 2, 1, 5, 2 }, /* added 03-18-2005 */
#endif
};

/* bits */

#define C_SUBTYPE_CHARGED     0
#define C_SUBTYPE_p_DONOR     1  /* new */
#define C_SUBTYPE_p_ACCEPT    2  /* new */
#define C_SUBTYPE_H_ACCEPT    4
#define C_SUBTYPE_H_DONOR     8
#define C_SUBTYPE_NEUTRAL    16

/* make sure any C_SUBTYPE_CHARGED_... < any C_SUBTYPE_NEUTRAL_... */
/* charged */
#define C_SUBTYPE_CHARGED_NON_TAUT          (C_SUBTYPE_CHARGED)
#define C_SUBTYPE_CHARGED_p_DONOR           (C_SUBTYPE_CHARGED|C_SUBTYPE_p_DONOR)
#define C_SUBTYPE_CHARGED_H_ACCEPT          (C_SUBTYPE_CHARGED|C_SUBTYPE_H_ACCEPT)
#define C_SUBTYPE_CHARGED_H_ACCEPT_p_DONOR  (C_SUBTYPE_CHARGED|C_SUBTYPE_H_ACCEPT|C_SUBTYPE_p_DONOR)
#define C_SUBTYPE_CHARGED_H_DONOR           (C_SUBTYPE_CHARGED|C_SUBTYPE_H_DONOR |C_SUBTYPE_p_DONOR)
/* neutral */
#define C_SUBTYPE_NEUTRAL_NON_TAUT          (C_SUBTYPE_NEUTRAL)
#define C_SUBTYPE_NEUTRAL_H_ACCEPT          (C_SUBTYPE_NEUTRAL|C_SUBTYPE_H_ACCEPT)
#define C_SUBTYPE_NEUTRAL_H_ACCEPT_p_ACCEPT (C_SUBTYPE_NEUTRAL|C_SUBTYPE_H_ACCEPT|C_SUBTYPE_p_ACCEPT)
#define C_SUBTYPE_NEUTRAL_H_DONOR           (C_SUBTYPE_NEUTRAL|C_SUBTYPE_H_DONOR)

#define NUM_C_TYPES  (int)(sizeof( CType )/sizeof(CType[0]))


/****************************************************************************/
int bCanBeACPoint( inp_ATOM *at,
                   S_CHAR cCharge,
                   S_CHAR cChangeValence,
                   S_CHAR neutral_bonds_valence,
                   S_CHAR neutral_valence,
                   S_CHAR nEndpointValence,
                   S_CHAR *cChargeSubtype )
{
    int nChangeValence;
    int nNumBonds;
    int nBondsValence;
    int bNegCharge = ( at->charge == -1 );  /* add fict. bonds to (-) 2004-02-24*/

    if (at->charge == cCharge && at->valence == at->chem_bonds_valence && at->num_H)
    {
        /* proton donors candidates >NH(+)-, >NH2(+), -NH3(+), >OH(+), -OH2(+) */
        /* charged, added p-transfer -- 01-28-2004 */
        nChangeValence = at->charge * cChangeValence; /* +1 or -1; currently only +1 */
        nBondsValence = at->chem_bonds_valence + at->num_H;
        if (nBondsValence == neutral_bonds_valence + nChangeValence && nEndpointValence)
        {
            *cChargeSubtype = C_SUBTYPE_CHARGED_p_DONOR; /* ignore Phosphorus p-donors for now */
        }
        return 0;
    }

    else
    {
        if (at->charge == cCharge && at->valence < at->chem_bonds_valence)
        {
            /* the requirement at->valence < at->chem_bonds_valence rejects
            candidates >NH(+)-, >NH2(+), -NH3(+), >N(+)<, >OH(+), -OH2(+), >O(+)-
            Moveable charge requires double bonds; these ions have no double bonds
            */

            /* charged */
            nChangeValence = at->charge * cChangeValence; /* +1 or -1; currently only +1 */
            nBondsValence = at->chem_bonds_valence + at->num_H;
            nNumBonds = at->valence + at->num_H;
            if (nBondsValence == neutral_bonds_valence + nChangeValence)
            {
                /* known valence */
                if (nNumBonds == neutral_valence)
                {
                    /* non-tautomeric: >N(+)=, =O(+)-
                    possibly tautomeric donor: =NH(+)-, =NH2(+), =OH(+) */
                    if (at->valence == neutral_valence || !nEndpointValence)
                    {
                        /* non-tautomeric: >N(+)=, =O(+)-; any suitable P+: >P(+)=, =PH(+)-, =PH2(+) */
                        *cChargeSubtype = C_SUBTYPE_CHARGED_NON_TAUT;
                    }
                    else
                    {
                        /* possibly tautomeric donor: =NH(+)-, =NH2(+), =OH(+) */
                        *cChargeSubtype = C_SUBTYPE_CHARGED_H_DONOR;
                    }
                    return 1;
                }
                if (nNumBonds == neutral_valence - 1)
                {
                    /* possibly tutomeric acceptor: =N(+)=, #N(+)-, #NH(+), #O(+) */
                    if (nEndpointValence)
                    {
                        *cChargeSubtype = at->num_H ? C_SUBTYPE_CHARGED_H_ACCEPT_p_DONOR : C_SUBTYPE_CHARGED_H_ACCEPT;
                    }
                    else
                    {
                        /* =P(+)=, #P(+)-, #PH(+) */
                        *cChargeSubtype = C_SUBTYPE_CHARGED_NON_TAUT;
                    }
                    return 1; /* charge type, charged */
                }
            }
        }
        else
        {
            if (at->charge == 0 || bNegCharge)
            {
                /* neutral atom or anion, all bonds are single */
                nBondsValence = at->chem_bonds_valence + at->num_H + bNegCharge; /* add fict. bonds to (-) 2004-02-24*/
                nNumBonds = at->valence + at->num_H + bNegCharge; /* add fict. bonds to (-) 2004-02-24*/
                if (nBondsValence == neutral_bonds_valence)
                {
                    if (nNumBonds == neutral_valence)
                    {
                        /* only single bonds: >N-, >NH, -NH2, -O-, -OH, >P- >PH -PH2 */
                        /*                    >N(-), -NH(-), -O(-). >P(-) -PH(-) */
                        {
                            if (at->valence == neutral_valence || !nEndpointValence)
                            {
                                /* >N-, -O-, any P(3 single bonds): >P- >PH -PH2  */
                                *cChargeSubtype = C_SUBTYPE_NEUTRAL_NON_TAUT;
                            }
                            else
                            {
                                if (at->valence < neutral_valence /*&& nEndpointValence */)
                                {
                                    /* num_H > 0: >NH -NH2 -OH */
                                    /* num_H = 0: none C_SUBTYPE_NEUTRAL_H_ACCEPT for now */
                                    *cChargeSubtype = at->num_H ? C_SUBTYPE_NEUTRAL_H_DONOR : C_SUBTYPE_NEUTRAL_H_ACCEPT;
                                }
                                else
                                {
                                    return 0;
                                }
                            }
                        }
                        return 1; /* charge type, neutral */
                    }
                    if (nNumBonds == neutral_valence - 1)
                    {
                        /* possibly tautomeric acceptor =N-, =NH, =O or non-taut =P-, =PH */
                        if (nEndpointValence)
                        {
                            /* =N-,  =NH, =O  */
                            *cChargeSubtype = C_SUBTYPE_NEUTRAL_H_ACCEPT_p_ACCEPT;
                        }
                        else
                        {
                            /* =P-, =PH */
                            *cChargeSubtype = C_SUBTYPE_NEUTRAL_NON_TAUT;
                        }
                        return 1; /* charge type, (+) => neutral */
                    }
                }
            }
        }
    }

    return 0;
}


/****************************************************************************/
int GetChargeType( inp_ATOM *atom, int iat, S_CHAR *cChargeSubtype )
{
    int i, n;
    S_CHAR    nEndpointValence;
    inp_ATOM *at = atom + iat;

    *cChargeSubtype = 0;
    /* ignore ion pairs and charges != 1 */
    if (abs( at->charge ) == 1)
    {
        for (i = 0; i < at->valence; i++)
        {
            n = at->neighbor[i];
            /* allow negatively charged tautomeric neighbors 2004-02-26 */
            if (abs( atom[n].charge + at->charge ) < abs( atom[n].charge - at->charge ) && !atom[n].endpoint)
            {
                return -1; /* charges have different signs */
            }
        }
    }
    else
    {
        if (at->charge)
        {
            return -1; /* abs(charge) != 1 */
        }
    }

    /* find candidates */
    for (i = 0; i < NUM_C_TYPES; i++)
    {
        if (!strcmp( at->elname, CType[i].elname ) &&
            ( !CType[i].num_bonds || (CType[i].num_bonds == at->valence && at->nNumAtInRingSystem >= 5) )) /* djb-rwth: addressing LLVM warning */
        {
            nEndpointValence = (S_CHAR) get_endpoint_valence( at->el_number );
            if (bCanBeACPoint( at, CType[i].charge, CType[i].cChangeValence, CType[i].neutral_bonds_valence,
                               CType[i].neutral_valence, nEndpointValence, cChargeSubtype ))
            {
                return CType[i].cChargeType;
            }
        }
    }

    return -1;
}


/****************************************************************************/
int CmpCCandidates( const void *a1, const void *a2 )
{
    const C_CANDIDATE *c1 = (const C_CANDIDATE *) a1;
    const C_CANDIDATE *c2 = (const C_CANDIDATE *) a2;
    int ret;
    if ((ret = (int) c1->type - (int) c2->type)) /* djb-rwth: addressing LLVM warning */
    {
        return ret;
    }
    if ((ret = (int) c1->subtype - (int) c2->subtype)) /* djb-rwth: addressing LLVM warning */
    {
        return ret;
    }
    ret = (int) c1->atnumber - (int) c2->atnumber;

    return ret;
}


/****************************************************************************/
int RegisterCPoints( C_GROUP *c_group,
                     int *pnum_c,
                     int max_num_c,
                     T_GROUP_INFO *t_group_info,
                     int point1,
                     int point2,
                     int ctype,
                     inp_ATOM *at,
                     int num_atoms )
{
    int num_c = *pnum_c, i, i1, i2;
    AT_NUMB nGroupNumber = 0, nNewGroupNumber;

    if (at[point1].c_point == at[point2].c_point)
    {
        if (at[point1].c_point)
        {
            return 0;
        }
        memset( c_group + num_c, 0, sizeof( c_group[0] ) ); /* djb-rwth: memset_s C11/Annex K variant? */
        if (num_c < max_num_c)
        {
            c_group[num_c].num[0] = CHARGED_CPOINT( at, point1 ) + CHARGED_CPOINT( at, point2 );
            c_group[num_c].num_CPoints += 2;
            c_group[num_c].cGroupType = ctype;
            /* get next available c-group number */
            for (i = 0; i < num_c; i++)
            {
                if (nGroupNumber < c_group[i].nGroupNumber)
                {
                    nGroupNumber = c_group[i].nGroupNumber;
                }
            }
            nGroupNumber++;
            c_group[num_c].nGroupNumber =
                at[point1].c_point =
                at[point2].c_point = nGroupNumber;
            *pnum_c = num_c + 1;
            /* count protons */
            if (at[point1].num_H)
            {
                c_group[num_c].num[1] ++;
            }
            else
            {
                if (at[point2].num_H)
                {
                    c_group[num_c].num[1] ++;
                }
                else
                {
                    if (( at[point1].endpoint || at[point2].endpoint ) && t_group_info && t_group_info->t_group && t_group_info->num_t_groups)
                    {
                        /* !!! add later !!! */
                    }
                }
            }
            return 1;
        }

        return BNS_CPOINT_ERR; /* overflow */
    }

    if (at[point1].c_point > at[point2].c_point)
    {
        /* make sure at[point1].c_point < at[point2].c_point */
        i = point1;
        point1 = point2;
        point2 = i;
    }

    if (!at[point1].c_point)
    {
        /* add a new c-endpoint to an existing c-group */
        nGroupNumber = at[point2].c_point;
        for (i = 0; i < num_c; i++)
        {
            if (nGroupNumber == c_group[i].nGroupNumber)
            {
                at[point1].c_point = at[point2].c_point;
                c_group[i].num_CPoints++;
                c_group[i].num[0] += CHARGED_CPOINT( at, point1 );
                return 1;
            }
        }
        return BNS_CPOINT_ERR; /* program error: c-group not found */
    }
    else
    {
        /* merge two c-groups */
        nNewGroupNumber = at[point1].c_point;
        nGroupNumber = at[point2].c_point;
        for (i = 0, i1 = i2 = -1; i < num_c && ( i1 < 0 || i2 < 0 ); i++)
        {
            if (nNewGroupNumber == c_group[i].nGroupNumber)
            {
                i1 = i;
                continue;
            }
            if (nGroupNumber == c_group[i].nGroupNumber)
            {
                i2 = i;
                continue;
            }
        }
        if (i1 < 0 || i2 < 0)
        {
            return BNS_CPOINT_ERR; /* at least one not found */
        }

        c_group[i1].num[0] += c_group[i2].num[0];
        c_group[i1].num_CPoints += c_group[i2].num_CPoints;
        num_c--;
        if (num_c > i2)
        {
            memmove(c_group + i2, c_group + i2 + 1, ((long long)num_c - i2) * sizeof(c_group[0])); /* djb-rwth: cast operators added */
        }
        *pnum_c = num_c;
        /* renumber c-groups */
        for (i = 0; i < num_c; i++)
        {
            if (c_group[i].nGroupNumber > nGroupNumber)
            {
                c_group[i].nGroupNumber--;
            }
        }
        /* renumber c-points */
        for (i = 0; i < num_atoms; i++)
        {
            if (at[i].c_point > nGroupNumber)
            {
                at[i].c_point--;
            }
            else
            {
                if (at[i].c_point == nGroupNumber)
                {
                    at[i].c_point = nNewGroupNumber;
                }
            }
        }
        return 1;
    }

}


/****************************************************************************/
int MarkChargeGroups( struct tagCANON_GLOBALS *pCG,
                      inp_ATOM *at,
                      int num_atoms,
                      C_GROUP_INFO *c_group_info,
                      T_GROUP_INFO *t_group_info,
                      struct BalancedNetworkStructure *pBNS,
                      struct BalancedNetworkData *pBD )
{
    int nNumChanges = 0;

    if (c_group_info && c_group_info->c_candidate && c_group_info->max_num_candidates > 0)
    {
        int i, i1, i2, i3, j, num_tested;
        C_CANDIDATE *c_candidate = c_group_info->c_candidate;
        int          nMaxNumCandidates = c_group_info->max_num_candidates;
        int          nNumCandidates = c_group_info->num_candidates;
        S_CHAR       c_type, c_subtype;
        int          iat1, iat2, ret, nDelta;

        if (nNumCandidates == -1)
        {
            nNumCandidates = 0; /* 2004-02-26 they could appear after t-group discovery */
                                /*return 0;*/
        }
        if (nNumCandidates == 0)
        {
            for (i = 0, nNumCandidates = 0; i < num_atoms; i++)
            {
                if (0 <= ( c_type = GetChargeType( at, i, &c_subtype ) ))
                {
                    if (nNumCandidates >= nMaxNumCandidates)
                    {
                        return BNS_VERT_EDGE_OVFL;
                    }
                    c_candidate[nNumCandidates].atnumber = i;
                    c_candidate[nNumCandidates].type = c_type;
                    c_candidate[nNumCandidates].subtype = c_subtype;
                    nNumCandidates++;
                }
            }
            if (nNumCandidates <= 1)
            {
                c_group_info->num_candidates = -1; /* no candidate exists */
                return 0;
            }
        }
        /* sorting keys: (1) atom type (N,P); (2) uncharged=16/charged=0; (3) other;
        atom-charged-N .... i1
        ...
        atom-charged-N
        atom-neutral-N .... i2
        ...
        atom-neutral-N
        atom-charged-P .... i3 ... i1
        ...
        atom-charged-P
        atom-neutral-P ........... i2
        ...
        atom-neutral-P
        end.           ........... i3
        */

        qsort( c_candidate, nNumCandidates, sizeof( c_candidate[0] ), CmpCCandidates );

        i1 = 0;
        num_tested = 0;
        /* djb-rwth: removing redundant code */

        while (i1 < nNumCandidates)
        {
            /* the the first charged candidate of a new atom type */
            for (; i1 < nNumCandidates && ( c_candidate[i1].subtype & C_SUBTYPE_NEUTRAL ); i1++)
            {
                ;
            }
            if (i1 == nNumCandidates)
            {
                break; /* not found */
            }

            /* bypass other charged candidates of the same atom type */
            for (i2 = i1 + 1; i2 < nNumCandidates &&
                  c_candidate[i2].type == c_candidate[i1].type &&
                  !( c_candidate[i2].subtype & C_SUBTYPE_NEUTRAL );
                  i2++)
            {
                ;
            }
            if (i2 == nNumCandidates)
            {
                break; /* no neutral candidates */
            }

            /* find next to the last neutral candidate of the same atom type */
            for (i3 = i2;  i3 < nNumCandidates &&
                  c_candidate[i3].type == c_candidate[i1].type;
                  i3++)
            {
                ;
            }

            if (i3 == i2)
            {
                /* no neutral candidates found */
                if (i2 < nNumCandidates)
                {
                    i1 = i3;
                    continue;  /* move to the next atom type */
                }
                break; /* nothing more to do */
            }

            /* found charged candidates: i1...i2-1;  neutral candidates: i2...i3-1 */
            for (i = i1; i < i2; i++)
            {
                iat1 = c_candidate[i].atnumber;
                for (j = i2; j < i3; j++)
                {
                    /* check alt path at[iat1]=-=-...-at[iat2]; at[iat1] is charged, at[iat2] is neutral */
                    num_tested++;
                    iat2 = c_candidate[j].atnumber;
                    if (at[iat1].c_point && at[iat1].c_point == at[iat2].c_point)
                    {
                        continue;
                    }

                    ret = bExistsAltPath( pCG, pBNS, pBD, NULL, at, num_atoms, iat1, iat2, ALT_PATH_MODE_CHARGE );

                    if (IS_BNS_ERROR( ret ))
                    {
                        return ret;
                    }
                    if (ret & 1)
                    {
                        nDelta = ( ret & ~3 ) >> 2;
                        nNumChanges += ( ret & 2 );

                        ret = RegisterCPoints( c_group_info->c_group, &c_group_info->num_c_groups,
                                               c_group_info->max_num_c_groups, t_group_info,
                                               iat1, iat2, c_candidate[i1].type, at, num_atoms );

                        if (IS_BNS_ERROR( ret ))
                        {
                            return ret;
                        }
                        if (nDelta)
                        {
                            goto quick_exit;
                        }
                    }
                }
            }
            i1 = i3;
        }

    quick_exit:
        if (c_group_info->num_candidates == 0)
        {
            /* first time: initialize */
            c_group_info->num_candidates = num_tested ? nNumCandidates : -1; /* no candidate exists */
        }
    }

    return nNumChanges;
}


/****************************************************************************/
int GetSaltChargeType( inp_ATOM *at,
                       int at_no,
                       T_GROUP_INFO *t_group_info,
                       int *s_subtype )
{
    /*
    type (returned value):
    -1 => ignore
    0 => oxygen
    subtype:
    1 = SALT_DONOR_H   => has H
    2 = SALT_DONOR_Neg => has (-) charge
    4 = SALT_ACCEPTOR  => may be an acceptor of H or (-), but not necessarily

    O-atom should be:
    - a terminal atom
    - connected to unsaturated, uncharged, non-radical atom C that has chemical valence 4:
    H-donors:             =CH-OH, =C(-X)-OH
    possible H-acceptors: -CH=O, >C=O
    H-acceptors are true if O is tautomeric
    */

    int iC, tg, i, type;

    *s_subtype = 0; /* initialize the output */
                    /* check whether it is a candidate */
    if (at[at_no].valence != 1 ||
         (at[at_no].radical && at[at_no].radical != RADICAL_SINGLET) ||
         at[at_no].charge < -1 ||
         (at[at_no].charge > 0 && !at[at_no].c_point)) /* djb-rwth: addressing LLVM warnings */
    {
        return -1;
    }

    if (at[at_no].el_number == EL_NUMBER_O ||
         at[at_no].el_number == EL_NUMBER_S ||
         at[at_no].el_number == EL_NUMBER_SE ||
         at[at_no].el_number == EL_NUMBER_TE)
    {
        type = 0;  /* terminal oxygen atom, needs more to be checked... */
    }
    else
    {
        type = -1; /* ignore this atom */
    }

    if (type < 0 ||
         at[at_no].chem_bonds_valence + at[at_no].num_H !=
         get_el_valence( at[at_no].el_number, at[at_no].charge, 0 ))
    {
        return -1; /* non-standard valence or not an oxygen */
    }

    iC = at[at_no].neighbor[0];

#if ( SALT_WITH_PROTONS == 1 )
    if (at[iC].el_number != EL_NUMBER_C ||
         at[iC].chem_bonds_valence + at[iC].num_H != 4 || /* allow =C(H)-OH or -C(H)=O */
         at[iC].charge ||
         (at[iC].radical && at[iC].radical != RADICAL_SINGLET) ||
         at[iC].valence == at[iC].chem_bonds_valence) /* djb-rwth: addressing LLVM warning */
    {
        return -1; /* oxigen is connected to a wrong atom */
    }
#else
    if (at[iC].el_number != EL_NUMBER_C ||
         at[iC].num_H ||
         at[iC].chem_bonds_valence != 4 ||  /* allow only no H on C */
         at[iC].charge ||
         at[iC].radical && at[iC].radical != RADICAL_SINGLET ||
         at[iC].valence == at[iC].chem_bonds_valence)
    {
        return -1; /* oxigen is connected to a wrong atom */
    }
#endif
    if (( tg = at[at_no].endpoint ) && t_group_info && t_group_info->t_group)
    {
        /* O-atom is in a tautomeric group */
        for (i = 0; i < t_group_info->num_t_groups; i++)
        {
            if (tg == t_group_info->t_group[i].nGroupNumber)
            {
                /*
                t_group_info->t_group[i].num[0] = number of attached H-atoms and negative charges
                t_group_info->t_group[i].num[1] = number of attached negative charges
                */
                if (t_group_info->t_group[i].num[0] > t_group_info->t_group[i].num[1])
                {
                    *s_subtype |= SALT_DONOR_H; /* has H */
                }
                if (t_group_info->t_group[i].num[1])
                {
                    *s_subtype |= SALT_DONOR_Neg; /* has (-) */
                }
                *s_subtype |= SALT_ACCEPTOR; /* there is always an acceptor in a t-group */
                return type;
            }
        }
        return -1; /* error: t-group not found */
    }

    /* O is not not in a tautomeric group */
    /* assume valence(O-) < valence(O) < valence(O+) */
    if (at[at_no].charge == -1)
    {
        *s_subtype |= SALT_DONOR_Neg; /* has (-) */
    }
    if (at[at_no].charge <= 0 && at[at_no].num_H)
    {
        *s_subtype |= SALT_DONOR_H; /* has H */
    }
    if (at[at_no].charge == 0 && at[at_no].chem_bonds_valence == 2)
    {
        *s_subtype |= SALT_ACCEPTOR;
    }
    /* since O cannot be a charge point, the following cannot happen: */
    if (at[at_no].charge == 1 && at[at_no].c_point && at[at_no].chem_bonds_valence == 2 && at[at_no].num_H)
    {
        *s_subtype |= SALT_DONOR_H; /* has H */
    }

    return type;
}


/****************************************************************************/
int bDoNotMergeNonTautAtom( inp_ATOM *at, int at_no )
{
    if (at[at_no].el_number == EL_NUMBER_N)
    {
        return 1;
    }
    return 0;
}


/****************************************************************************/
int GetOtherSaltChargeType( inp_ATOM *at,
                            int at_no,
                            T_GROUP_INFO *t_group_info,
                            int *s_subtype,
                            int bAccept_O )
{
    /*
    type (returned value):
    -1 => ignore
    1 => not an oxygen
    subtype:
    1 = SALT_DONOR_H   => has H
    2 = SALT_DONOR_Neg => has (-) charge
    4 = SALT_ACCEPTOR  => may be an acceptor of H or (-), but not necessarily

    the atom should be:
    - a tautomeric endpoint atom
    - connected to possible centerpoint atom

    another description of the atom searched here:

    any possibly tautomeric atom adjacent to a possibly centerpoint
    that has at least one double bond (possibly if positively charged);
    if eif.cAcceptor then the bond between the atom and the centerpoint must be possibly double
    if eif.cAcceptor then the bond must be possibly single
    Donors that belong to a t-group are also acceptors
    */

    int tg, i, j, type, endpoint_valence, num_centerpoints, bond_type, centerpoint;
    ENDPOINT_INFO eif;

    *s_subtype = 0; /* initialize the output */
    if (!bAccept_O /* only N */ &&
        ( at[at_no].el_number == EL_NUMBER_O ||
          at[at_no].el_number == EL_NUMBER_S ||
          at[at_no].el_number == EL_NUMBER_SE ||
          at[at_no].el_number == EL_NUMBER_TE ))
    {
        return -1; /* we are not looking for oxygen here */
    }

    type = 1;
    if (!( endpoint_valence = nGetEndpointInfo( at, at_no, &eif ) )) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
    {
        return -1; /* not a possible endpoint */
    }
    else
    {
        /* at[at_no] is not not in a tautomeric group; use eif previously filled out by nGetEndpointInfo */
        /* check whether there is adjacent atom-candidate for a centerpoint */
        num_centerpoints = 0;
        for (j = 0; j < at[at_no].valence; j++)
        {
            bond_type = (int) at[at_no].bond_type[j] & BOND_TYPE_MASK;
            centerpoint = (int) at[at_no].neighbor[j];  /*  a centerpoint candidate */
            if (( (eif.cAcceptor && ( bond_type == BOND_DOUBLE ||
                                     bond_type == BOND_ALTERN || /* possibly double */
                                     bond_type == BOND_ALT12NS ||
                                     bond_type == BOND_TAUTOM )) ||
                  (eif.cDonor && ( bond_type == BOND_SINGLE ||
                                  bond_type == BOND_ALTERN || /* possibly single */
                                  bond_type == BOND_ALT12NS ||
                                  bond_type == BOND_TAUTOM )) ) &&
                                  ( at[centerpoint].chem_bonds_valence > at[centerpoint].valence ||
                                    /* check for possible endpoint added 2004-02-24 */
                                    (at[centerpoint].chem_bonds_valence == at[centerpoint].valence &&
                                    ( at[centerpoint].endpoint || at[centerpoint].c_point )) /* tautomerism or charge may increment at[centerpoint].chem_bonds_valence*/ ) &&
                 is_centerpoint_elem( at[centerpoint].el_number )) /* djb-rwth: addressing LLVM warnings */
            {
                num_centerpoints++;
                break; /* at least one possibly centerpoint neighbor has been found */
            }
        }
        if (!num_centerpoints)
        {
            return -1;
        }
        /* moved here from just after "type = 1;" line 2004-02-26 */
        if (( tg = at[at_no].endpoint ) && t_group_info && t_group_info->t_group)
        {
            /* atom is in a tautomeric group */
            for (i = 0; i < t_group_info->num_t_groups; i++)
            {
                if (tg == t_group_info->t_group[i].nGroupNumber)
                {
                    /*
                    t_group_info->t_group[i].num[0] = number of attached H-atoms and negative charges
                    t_group_info->t_group[i].num[1] = number of attached negative charges
                    */
                    if (t_group_info->t_group[i].num[0] > t_group_info->t_group[i].num[1])
                    {
                        *s_subtype |= SALT_DONOR_H; /* has H */
                    }
                    if (t_group_info->t_group[i].num[1])
                    {
                        *s_subtype |= SALT_DONOR_Neg; /* has (-) */
                    }
                    *s_subtype |= SALT_ACCEPTOR; /* there is always an acceptor in a t-group */
                    return type;
                }
            }
            return -1; /* error: t-group not found */
        }

        if (eif.cAcceptor)
        {
            *s_subtype |= SALT_ACCEPTOR;
        }
        if (eif.cDonor)
        {
            if (at[at_no].charge == -1)
            {
                *s_subtype |= SALT_DONOR_Neg; /* has (-) */
            }
            if (at[at_no].num_H)
            {
                *s_subtype |= SALT_DONOR_H; /* has H */
            }
        }
    }

    return type;
}


/****************************************************************************/
int GetOtherSaltType( inp_ATOM *at, int at_no, int *s_subtype )
{
    /*
    type (returned value):
    -1 => ignore
    2 => found:                           SH
    proton donor     -CH2-SH, >CH-SH, >C<    S(-)
    proton acceptor  -CH2-S(-), >CH-S(-), >C<
    subtype:
    1 = SALT_DONOR_H   => has H
    2 = SALT_DONOR_Neg => has (-) charge
    4 = SALT_ACCEPTOR  => may be an acceptor of H or (-), but not necessarily

    non-O-atom should be:
    - a tautomeric endpoint atom
    - connected to possible middle point atom
    */

    int type, endpoint_valence, centerpoint; /* djb-rwth: removing redundant variables */
    ENDPOINT_INFO eif;

    if (at[at_no].valence != 1 || at[at_no].chem_bonds_valence != 1 ||
         1 != ( at[at_no].num_H == 1 ) + ( at[at_no].charge == -1 ))
    {
        return -1;
    }

    *s_subtype = 0; /* initialize the output */
    if (!( at[at_no].el_number == EL_NUMBER_S ||
           at[at_no].el_number == EL_NUMBER_SE ||
           at[at_no].el_number == EL_NUMBER_TE ))
    {
        return -1; /* we are not looking for oxygen here */
    }

    type = 2; /* non-tautomeric p-donor or acceptor: C-SH, C-S(-) */

    if (!( endpoint_valence = nGetEndpointInfo( at, at_no, &eif ) ) ||
         (eif.cMoveableCharge && !at[at_no].c_point) || !eif.cDonor || eif.cAcceptor) /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
    {
        return -1; /* not a possible -SH or -S(-) */
    }
    else
    {
        /* at[at_no] is not not in a tautomeric group; use eif previously filled out by nGetEndpointInfo */
        /* check whether there is adjacent atom-candidate for a centerpoint */
        centerpoint = (int) at[at_no].neighbor[0];
        /* djb-rwth: removing redundant code */
        if (at[centerpoint].el_number != EL_NUMBER_C ||
             at[centerpoint].charge ||
             (at[centerpoint].radical && at[centerpoint].radical != RADICAL_SINGLET) ||
             at[centerpoint].valence != at[centerpoint].chem_bonds_valence) /* djb-rwth: addressing LLVM warning */
        {
            return -1; /* not a carbon with all single bonds */
        }
        if (at[at_no].num_H == 1)
        {
            *s_subtype |= SALT_p_DONOR;
        }
        else
        {
            if (at[at_no].charge == -1)
            {
                *s_subtype |= SALT_p_ACCEPTOR;
            }
            else
            {
                return -1;
            }
        }
    }

    return type;
}


/************************************************************************************/
/* new version: merge all, check alt paths, then unmerge unreachable O-atoms if any */
/* Check for oxygen negative charge-H tautomerism (Salts)
allowed long-range tautomerism; more than one H or (-) can be moved, for example:
HO-C=C-O(-)         O=C-C=O
/   \              /   \
R     R            R     R
|     |       =>   |     |
R'    R'           R'    R'
\   /              \   /
O=C-C=O           HO-C=C-O(-)

To check:

|             |
-add all possible HO-C=, O=C, (-)O-C= (including all containing O t-groups) into one t-group;
-temporarily disconnect one of previously not belonging to any t-group O-atoms from the one t-group;
-find whether there is an alt path allowing H or (-) to migrate
from the temp. disconnected O to any one left in the group.
If the alt path does not exist then the temp. disconnected atom does not
participate in the H/(-) migrartion and it will be unmarked/unmerged.
*/



/****************************************************************************/
int comp_candidates( const void *a1, const void *a2 )
{
    const S_CANDIDATE *s1 = (const S_CANDIDATE *) a1;
    const S_CANDIDATE *s2 = (const S_CANDIDATE *) a2;
    int ret;
    if (s1->type >= 0 /* enabled < */ && s2->type < 0 /* disabled */)
    {
        return -1; /* enabled goes first */
    }
    if (s1->type < 0 /* disabled > */ && s2->type >= 0 /* enabled */)
    {
        return 1;
    }
    if (s1->endpoint && !s2->endpoint)
    {
        return -1; /* tautomeric goes first; only tautomeric may be disabled */
    }
    if (!s1->endpoint && s2->endpoint)
    {
        return 1; /* tautomeric goes first; only tautomeric may be disabled */
    }
    if (s1->endpoint && s2->endpoint && ( ret = (int) s1->endpoint - (int) s2->endpoint ))
    {
        return ret;
    }

    return (int) s1->atnumber - (int) s2->atnumber;
}


/****************************************************************************/
int MarkSaltChargeGroups2( CANON_GLOBALS *pCG,
                           inp_ATOM *at,
                           int num_atoms,
                           S_GROUP_INFO *s_group_info,
                           T_GROUP_INFO *t_group_info,
                           C_GROUP_INFO *c_group_info,
                           struct BalancedNetworkStructure *pBNS,
                           struct BalancedNetworkData *pBD )
{
    /* BNS_EDGE_FORBIDDEN_TEMP */
#define ALT_PATH_FOUND    (MAX_ATOMS+1)
#define NO_ENDPOINT       (MAX_ATOMS+2)  /* the two defines must be different */
#define DISABLE_CANDIDATE 10
#define cPAIR(a,b) cPair[a+b*nNumLeftCandidates]
#define ACCEPTOR_PAIR 1
#define DONOR_PAIR    2

    int nNumOtherChanges = 0, nTotNumChanges = 0; /* djb-rwth: removing redundant variables */
    S_CHAR      *cPair = NULL;
    T_ENDPOINT  *EndPoint = NULL;
    if (s_group_info && s_group_info->s_candidate && s_group_info->max_num_candidates > 0)
    {
        int i, j, i1, j1;
        S_CANDIDATE *s_candidate = s_group_info->s_candidate;
        int          nMaxNumCandidates = s_group_info->max_num_candidates;
        int          nNumCandidates = s_group_info->num_candidates;
        int          nNumOtherCandidates; /* djb-rwth: removing redundant code */
        /* djb-rwth: removing redundant variables */
        int          nNumLeftCandidates = 0;
        int          nNumMarkedCandidates = 0;
        int          s_type, s_subtype;
        int          ret, nDelta;
        int          bHardAddedRemovedProtons = t_group_info && ( t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT );

        int          s_subtype_all = 0;
        int          nDonorPairs, nAcceptorPairs, nCurDonorPairs, nCurAcceptorPairs, bAlreadyTested;
        /*
        ENDPOINT_INFO    eif;
        */

#if ( IGNORE_TGROUP_WITHOUT_H == 1 )
        int          bTGroupHasNegativeChargesOnly = 1;
#endif
        /*return 0;*/ /* debug only */

        /* djb-rwth: removing redundant code */

        if (nNumCandidates <= -2 || !t_group_info || !t_group_info->t_group)
        {
            return 0;
        }

        /*************************************************************************/
        /* Find all candidates including those with differen s_type (other type) */
        /*************************************************************************/
        for (i = 0, nNumCandidates = nNumOtherCandidates = 0; i < num_atoms; i++) /* djb-rwth: removing redundant code */
        {
            if (0 == ( s_type = GetSaltChargeType( at, i, t_group_info, &s_subtype ) ) ||
                 /* -C=O or =C-OH, O = S, Se, Te */
                 1 == ( s_type = GetOtherSaltChargeType( at, i, t_group_info, &s_subtype, 1/* bAccept_O*/ ) ) ||
                 /* =Z-MH or -Z=M, Z = centerpoint, M = endpoint, other than above */
                 2 == ( s_type = GetOtherSaltType( at, i, &s_subtype ) ) ||
                 ( bHardAddedRemovedProtons && 4 == ( s_type = bIsHardRemHCandidate( at, i, &s_subtype ) ) )
                 /* >C-SH, >C-S(-); S=S,Se,Te */
                 )
            {

                if (nNumCandidates >= nMaxNumCandidates)
                {
                    return BNS_VERT_EDGE_OVFL;
                }
                s_candidate[nNumCandidates].atnumber = i;
                s_candidate[nNumCandidates].type = s_type;
                s_candidate[nNumCandidates].subtype = s_subtype;
                s_candidate[nNumCandidates].endpoint = at[i].endpoint;
                nNumCandidates++;
                nNumOtherCandidates += ( 1 == s_type );
                s_subtype_all |= s_subtype;
                /* djb-rwth: removing redundant code */
            }
        }

        if (nNumCandidates <= 1 ||  /* TG_FLAG_ALLOW_NO_NEGTV_O <=> CHARGED_SALTS_ONLY=0 */
             !( s_subtype_all & SALT_ACCEPTOR ) ||
             ( ( ( t_group_info->bTautFlags & TG_FLAG_ALLOW_NO_NEGTV_O ) ||
             ( t_group_info->bTautFlagsDone & TG_FLAG_FOUND_SALT_CHARGES_DONE ) ||
                 ( t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT ) ) ?
               !( s_subtype_all & ( SALT_DONOR ) ) :
               ( !( s_subtype_all & SALT_DONOR_Neg ) || nNumOtherCandidates == nNumCandidates ) )
             )
        {
            s_group_info->num_candidates = 0; /* no candidate exists */
            return 0;
        }

        if (!( s_subtype_all & ( SALT_DONOR_Neg ) ))
        {
            t_group_info->bTautFlagsDone |= TG_FLAG_ALLOW_NO_NEGTV_O_DONE;
        }

        /************************************************************************************/
        /* Mark redundant candidates so that only one candidate from one t-group is left in */
        /************************************************************************************/
        for (i = 0; i < nNumCandidates; i++)
        {
            /* djb-rwth: fixing oss-fuzz issue #45059 */
            if ((nNumCandidates < nMaxNumCandidates) && (2 == s_candidate[nNumCandidates].type))
            {
                s_candidate[i].type -= DISABLE_CANDIDATE; /* disable >C-SH candidates */
                nNumLeftCandidates++; /* count rejected */
                continue;
            }
            if (s_candidate[i].endpoint)
            {
                for (j = i - 1; 0 <= j; j--)
                {
                    if (s_candidate[i].endpoint == s_candidate[j].endpoint)
                    {
                        s_candidate[i].type -= DISABLE_CANDIDATE; /* disable subsequent redundant */
                        nNumLeftCandidates++; /* count rejected */
                        break;
                    }
                }
            }
        }

        nNumLeftCandidates = nNumCandidates - nNumLeftCandidates; /* subtract num. rejected from the total */
        s_group_info->num_candidates = 0; /* reinit next time */
                                          /*********************************************************************/
                                          /* reorder so that all disabled are at the end, tautomeric are first */
                                          /*********************************************************************/

        qsort( s_candidate, nNumCandidates, sizeof( s_candidate[0] ), comp_candidates );

        if (nNumLeftCandidates) /* djb-rwth: avoiding zero-length allocation */
            cPair = (S_CHAR *) inchi_calloc( (long long)nNumLeftCandidates * (long long)nNumLeftCandidates, sizeof( cPair[0] ) ); /* djb-rwth: cast operators added */
        if (!cPair)
        {
            /*printf("BNS_OUT_OF_RAM-6\n");*/
            nTotNumChanges = BNS_OUT_OF_RAM;
            goto quick_exit;
        }
        nDonorPairs = nAcceptorPairs = 0;

        /**********************************************************************/
        /* Find whether we have at least one donor pair and one acceptor pair */
        /**********************************************************************/
        for (i = 0; i < nNumLeftCandidates; i++)
        {
            nCurDonorPairs = nCurAcceptorPairs = 0;
            for (j = 0; j <= i; j++)
            {
                if (i == j && !s_candidate[i].endpoint)
                {
                    continue;  /* same non-taut atom. However, success for i==j means     *
                               * that the whole tautomeric group may donate or accept 2H */
                }
                /* check for acceptor pair */
                if (( s_candidate[i].subtype & SALT_ACCEPTOR ) && ( s_candidate[j].subtype & SALT_ACCEPTOR ) &&
                    ( ret = bExistsAltPath( pCG, pBNS, pBD, NULL, at, num_atoms, s_candidate[i].atnumber,
                                            s_candidate[j].atnumber, ALT_PATH_MODE_ADD2H_TST ) ))
                {
                    if (IS_BNS_ERROR( ret ))
                    {
                        nTotNumChanges = ret;
                        goto quick_exit;
                    }
                    if (ret & 1)
                    {
                        nDelta = ( ret & ~3 ) >> 2;
                        /*nNumChanges += (ret & 2);*/
                        if (nDelta)
                        {
                            /* alt path unleashed previously localized radicals and they annihilated */
                            /* djb-rwth: removing redundant code */
                            nTotNumChanges = BNS_RADICAL_ERR;
                            goto quick_exit;
                        }
                        cPAIR( i, j ) |= ACCEPTOR_PAIR; /* the result: mark the pair */
                                                        /*cPAIR(j,i) |= ACCEPTOR_PAIR;*/
                    }
                }
                /* Check for donor pair */
                if (( s_candidate[i].subtype & SALT_DONOR ) && ( s_candidate[j].subtype & SALT_DONOR ) &&
                    ( ret = bExistsAltPath( pCG, pBNS, pBD, NULL, at, num_atoms, s_candidate[i].atnumber,
                                            s_candidate[j].atnumber, ALT_PATH_MODE_REM2H_TST ) ))
                {
                    if (IS_BNS_ERROR( ret ))
                    {
                        nTotNumChanges = ret;
                        goto quick_exit;
                    }
                    if (ret & 1)
                    {
                        nDelta = ( ret & ~3 ) >> 2;
                        /*nNumChanges += (ret & 2);*/
                        if (nDelta)
                        {
                            /* Alt path unleashed previously localized radicals and they annihilated */
                            /* djb-rwth: removing redundant code */
                            nTotNumChanges = BNS_RADICAL_ERR;
                            goto quick_exit;
                        }
                        cPAIR( i, j ) |= DONOR_PAIR; /* the result: mark the pair */
                                                     /*cPAIR(j,i) |= ACCEPTOR_PAIR;*/
                    }
                }
                /* Since the results will be used later to change bonds, check only now */
                /* when both results for (i,j) have been obtained. */
                if (cPAIR( i, j ) & ACCEPTOR_PAIR)
                {
                    nCurAcceptorPairs++;
                    if (nDonorPairs)
                    {
                        /* Find donor pair (i1,j1) such that i!=i1, i!=j1, j!=i1, j!=j1 */
                        for (i1 = 0; i1 < i; i1++)
                        {
                            for (j1 = 0; j1 <= i1; j1++)
                            {
                                /* Here always j1 < i && i1 < i therefore we do not compare i to i1 or j1 */
                                if (j1 != j && i1 != j && ( cPAIR( i1, j1 ) & DONOR_PAIR ))
                                {
                                    /* Both the donor and the acceptor pairs have been found */
                                    goto bFound2Pairs;
                                }
                            }
                        }
                    }
                }
                if (cPAIR( i, j ) & DONOR_PAIR)
                {
                    nCurDonorPairs++;
                    if (nAcceptorPairs)
                    {
                        /* Find acceptor pair (i1,j1) such that i!=i1, i!=j1, j!=i1, j!=j1 */
                        for (i1 = 0; i1 < i; i1++)
                        {
                            for (j1 = 0; j1 <= i1; j1++)
                            {
                                /* Here always j1 < i && i1 < i therefore we do not compare i to i1 or j1 */
                                if (j1 != j && i1 != j && ( cPAIR( i1, j1 ) & ACCEPTOR_PAIR ))
                                {
                                    /* Both the donor and the acceptor pairs have been found */
                                    goto bFound2Pairs;
                                }
                            }
                        }
                    }
                }
            }
            nDonorPairs += nCurDonorPairs;
            nAcceptorPairs += nCurAcceptorPairs;
        }

        /* Nothing has been found */
        /* djb-rwth: removing redundant code */
        inchi_free( cPair );
        cPair = NULL;
        goto quick_exit;


        /* Both the donor and the acceptor pairs have been found */
    bFound2Pairs:
        /* first, try already found pairs */
        i1 = i;
        j1 = j;

        /* Find all possible donor and acceptor pairs */
        nNumMarkedCandidates = 0;
        for (i = 0; i < nNumLeftCandidates; i++)
        {
            nCurDonorPairs = nCurAcceptorPairs = 0;
            for (j = 0; j <= i; j++)
            {
                bAlreadyTested = ( i < i1 || (i == i1 && j <= j1 )); /* djb-rwth: addressing LLVM warning */
                if ((bAlreadyTested && ( cPAIR( i, j ) & ACCEPTOR_PAIR )) || !bAlreadyTested) /* djb-rwth: addressing LLVM warning */
                {
                    /* Checking for acceptor pair */
                    if (( s_candidate[i].subtype & SALT_ACCEPTOR ) && ( s_candidate[j].subtype & SALT_ACCEPTOR ) &&
                        ( ret = bExistsAltPath( pCG, pBNS, pBD, NULL, at, num_atoms, s_candidate[i].atnumber,
                                                s_candidate[j].atnumber, ALT_PATH_MODE_ADD2H_CHG ) ))
                    {
                        if (IS_BNS_ERROR( ret ))
                        {
                            nTotNumChanges = ret;
                            goto quick_exit;
                        }
                        if (ret & 1)
                        {
                            nDelta = ( ret & ~3 ) >> 2;
                            /* djb-rwth: removing redundant code */
                            if (nDelta)
                            {
                                /* Alt path unleashed previously localized radicals and they annihilated */
                                /* djb-rwth: removing redundant code */
                                nTotNumChanges = BNS_RADICAL_ERR;
                                goto quick_exit;
                            }
                            cPAIR( i, j ) |= ACCEPTOR_PAIR;
                            /*cPAIR(j,i) |= ACCEPTOR_PAIR;*/
                            nCurAcceptorPairs += !bAlreadyTested;
                            if (!( s_candidate[i].subtype & SALT_SELECTED ))
                            {
                                s_candidate[i].subtype |= SALT_SELECTED;
                                nNumMarkedCandidates++;
                                if (!s_candidate[i].endpoint && s_candidate[i].type)
                                {
                                    nNumOtherChanges++;
                                }
                                /* djb-rwth: removing redundant code */
                            }
                            if (!( s_candidate[j].subtype & SALT_SELECTED ))
                            {
                                s_candidate[j].subtype |= SALT_SELECTED;
                                nNumMarkedCandidates++;
                                if (!s_candidate[j].endpoint && s_candidate[j].type)
                                {
                                    nNumOtherChanges++;
                                }
                                /* djb-rwth: removing redundant code */
                            }
                        }
                    }
                }

                if ((bAlreadyTested && ( cPAIR( i, j ) & DONOR_PAIR )) || !bAlreadyTested) /* djb-rwth: addressing LLVM warning */
                {
                    /* Checking for donor pair */
                    if (( s_candidate[i].subtype & SALT_DONOR ) && ( s_candidate[j].subtype & SALT_DONOR ) &&
                        ( ret = bExistsAltPath( pCG, pBNS, pBD, NULL, at, num_atoms, s_candidate[i].atnumber,
                                                s_candidate[j].atnumber, ALT_PATH_MODE_REM2H_CHG ) ))
                    {
                        if (IS_BNS_ERROR( ret ))
                        {
                            nTotNumChanges = ret;
                            goto quick_exit;
                        }
                        if (ret & 1)
                        {
                            nDelta = ( ret & ~3 ) >> 2;
                            /* djb-rwth: removing redundant code */
                            if (nDelta)
                            {
                                /* Alt path unleashed previously localized radicals and they annihilated */
                                /* djb-rwth: removing redundant code */
                                nTotNumChanges = BNS_RADICAL_ERR;
                                goto quick_exit;
                            }
                            cPAIR( i, j ) |= DONOR_PAIR;
                            /*cPAIR(j,i) |= ACCEPTOR_PAIR;*/
                            nCurDonorPairs += !bAlreadyTested;
                            if (!( s_candidate[i].subtype & SALT_SELECTED ))
                            {
                                s_candidate[i].subtype |= SALT_SELECTED;
                                nNumMarkedCandidates++;
                                if (!s_candidate[i].endpoint && s_candidate[i].type)
                                {
                                    nNumOtherChanges++;
                                }
                                /* djb-rwth: removing redundant code */
                            }
                            if (!( s_candidate[j].subtype & SALT_SELECTED ))
                            {
                                s_candidate[j].subtype |= SALT_SELECTED;
                                nNumMarkedCandidates++;
                                if (!s_candidate[j].endpoint && s_candidate[j].type)
                                {
                                    nNumOtherChanges++;
                                }
                                /* djb-rwth: removing redundant code */
                            }
                        }
                    }
                }
            }
            nDonorPairs += nCurDonorPairs;
            nAcceptorPairs += nCurAcceptorPairs;
        }

        inchi_free( cPair );
        cPair = NULL;

        if (nNumMarkedCandidates)
        {
            EndPoint = (T_ENDPOINT *) inchi_calloc( nNumMarkedCandidates, sizeof( EndPoint[0] ) );
            if (!EndPoint)
            {
                /*printf("BNS_OUT_OF_RAM-7\n");*/
                nTotNumChanges = BNS_OUT_OF_RAM;
                goto quick_exit;
            }
            for (i = 0, j = 0; i < nNumLeftCandidates; i++)
            {
                if (s_candidate[i].subtype & SALT_SELECTED)
                {
                    s_candidate[i].subtype ^= SALT_SELECTED; /* remove the flag */
                    if (j < nNumMarkedCandidates)
                    {
                        i1 = s_candidate[i].atnumber; /* save a representative of the t-group to be created */
                        AddEndPoint( EndPoint + j, at, i1 );
                    }
                    j++;
                }
            }
            if (j != nNumMarkedCandidates)
            {
                nTotNumChanges = BNS_PROGRAM_ERR;
                goto quick_exit;
            }

            /* Merge all marked atoms and their t-groups into one t-group */
            ret = RegisterEndPoints( pCG, t_group_info, EndPoint,
                                     nNumMarkedCandidates, at,
                                     num_atoms, c_group_info, pBNS );

            if (ret == -1)
            {
                ret = BNS_PROGRAM_ERR;
            }
            if (ret < 0)
            {
                nTotNumChanges = ret;
                goto quick_exit;
            }
            nTotNumChanges += ( ret > 0 );
            inchi_free( EndPoint );
            EndPoint = NULL;

            if (nNumMarkedCandidates)
            {
                for (i = nNumLeftCandidates; i < nNumCandidates; i++)
                {
                    s_candidate[i].type += DISABLE_CANDIDATE;
                    j1 = s_candidate[i].atnumber;
                    if (at[j1].endpoint == at[i1].endpoint)
                    {
                        if (!s_candidate[i].endpoint && s_candidate[i].type)
                        {
                            nNumOtherChanges++;
                        }
                        /* djb-rwth: removing redundant code */
                    }
                }
            }
            else
            {
                for (i = nNumLeftCandidates; i < nNumCandidates; i++)
                {
                    s_candidate[i].type += DISABLE_CANDIDATE;
                }
            }

            /* Find whether the new t-group have any movable H */
            for (i = 0, bTGroupHasNegativeChargesOnly = 0; i < t_group_info->num_t_groups; i++)
            {
                if (t_group_info->t_group[i].nGroupNumber == at[i1].endpoint &&
                     t_group_info->t_group[i].num[0] == t_group_info->t_group[i].num[1])
                {
                    bTGroupHasNegativeChargesOnly = 1;
                    break;
                }
            }
        }
        nTotNumChanges = ( nTotNumChanges > 0 );

#if ( IGNORE_TGROUP_WITHOUT_H == 1 )
        if (nTotNumChanges && bTGroupHasNegativeChargesOnly)
        {
            nTotNumChanges = 2;  /* means no moveable H has been affected */
        }
#endif
    }

quick_exit:
    if (nNumOtherChanges && nTotNumChanges == 1)
    {
        nTotNumChanges = 5; /* not only acidic atoms merged */
    }
    if (cPair)
    {
        inchi_free( cPair );
        /*cPair = NULL;*/
    }
    if (EndPoint)
    {
        inchi_free( EndPoint );
        /*EndPoint = NULL;*/
    }

    return nTotNumChanges; /* 0=>no changes, 1=>new salt tautomerism found, 2=>only new charge tautomerism found */
#undef ALT_PATH_FOUND
#undef NO_ENDPOINT
}


/****************************************************************************/
/* regular one-path version: find alt paths then merge */
/* Check for oxygen negative charge-H tautomerism (Salts)
allowed long-range tautomerism; only one H or (-) can be moved, for example:
HO-C=X-Y=Z-...-C=O  => O=C-X=Y-Z=...=C-OH
*/

#if ( SALT_WITH_PROTONS == 1 )

#define MAX_LOCAL_TGNUM 0 /* was 32; disable since it has not been used */

#if ( MAX_LOCAL_TGNUM > 0 )
typedef struct tagTGroupData {
    S_SHORT nGroupNumber; /* t-group number from t_group_info->t_group->nGroupNumber */
    S_SHORT nGroupIndex;  /* TGroupData[nGroupNumber]nGroupIndex = index of t_group in t_group_info */
    S_SHORT nDonorM;      /* number of endpoint-donors that have negative charge (Minus) */
    S_SHORT nDonorH;      /* number of endpoint-donors that have only H */
    S_SHORT nAccepM;      /* number of endpoint-acceptors that have negative charge (Minus) */
    S_SHORT nAccepH;      /* number of endpoint-acceptors that have H and no negative charge */
    S_SHORT nAccep0;      /* number of endpoint-acceptors that have no H and no negative charge */
    S_SHORT nDonorA;      /* number of acidic endpoint-donors */
    S_SHORT nAccepS;      /* number of acidic endpoint-acceptors */
} TGroupData;
#endif


/****************************************************************************/
int MarkSaltChargeGroups( CANON_GLOBALS *pCG,
                          inp_ATOM *at,
                          int num_atoms,
                          S_GROUP_INFO *s_group_info,
                          T_GROUP_INFO *t_group_info,
                          C_GROUP_INFO *c_group_info,
                          struct BalancedNetworkStructure *pBNS,
                          struct BalancedNetworkData *pBD )
{

    int nNumChanges = 0, nTotNumChanges = 0;
    if (s_group_info && s_group_info->s_candidate && s_group_info->max_num_candidates > 0)
    {
        int i, i1, i2, j, j1, j2, jj, ii1, ii2, jj1, jj2, /*k,*/ num_tested;
        S_CANDIDATE *s_candidate = s_group_info->s_candidate;
        int          nMaxNumCandidates = s_group_info->max_num_candidates;
        int          nNumCandidates = s_group_info->num_candidates;
        int          nNumOtherCandidates; /* djb-rwth: removing redundant code */
        int          nNumPOnlyCandidates; /* djb-rwth: removing redundant code */
        int          s_type, s_subtype;
        int          ret, nDelta, /*nMobile,*/ err = 0;
        int          s_subtype_all = 0;
        int          nGroupNumber;
        T_ENDPOINT   EndPoint[2];
#if ( MAX_LOCAL_TGNUM > 0 )
        TGroupData   tgData[MAX_LOCAL_TGNUM];
        TGroupData   *ptgData = tgData;
#endif
        int cond1 = 0, cond2a = 0, cond2b = 0, cond2c = 0, cond2 = 0;

        if (nNumCandidates <= -1 || !t_group_info || !t_group_info->t_group)
        {
            return 0;
        }

        /* count t-groups */
        for (i = 0, nGroupNumber = 0; i < t_group_info->num_t_groups; i++)
        {
            if (nGroupNumber < t_group_info->t_group[i].nGroupNumber)
            {
                nGroupNumber = t_group_info->t_group[i].nGroupNumber; /* max. t-group number */
            }
        }
#if ( MAX_LOCAL_TGNUM > 0 )
        /* prepare memory */
        if (nGroupNumber >= MAX_LOCAL_TGNUM)
        {
            if (!( ptgData = (TGroupData*) inchi_calloc( nGroupNumber + 1, sizeof( TGroupData ) ) ))
            {
                err = BNS_OUT_OF_RAM;
                goto quick_exit;
            }
        }
        else
        {
            memset( ptgData, 0, sizeof( tgData ) );
        }
        ptgData[0].nGroupIndex = -1; /* data for non-tautomeric atoms */
        for (i = 0, nGroupNumber = 0; i < t_group_info->num_t_groups; i++)
        {
            if (nGroupNumber = t_group_info->t_group[i].nGroupNumber)
            {
                ptgData[nGroupNumber].nGroupIndex = i;
                ptgData[i].nGroupNumber = nGroupNumber;
            }
        }
#endif
        nNumCandidates = 0; /* always recalculate 2004-03-22 */
        num_tested = 0;

        if (nNumCandidates == 0)
        {
            for (i = 0, nNumCandidates = nNumOtherCandidates = nNumPOnlyCandidates = 0; i < num_atoms; i++)
            {
                if (0 == ( s_type = GetSaltChargeType( at, i, t_group_info, &s_subtype ) ) ||
                     /* -C=O or =C-OH, O = S, Se, Te */
#if ( INCL_NON_SALT_CANDIDATATES == 1 )
                     1 == ( s_type = GetOtherSaltChargeType( at, i, t_group_info, &s_subtype, 1 ) ) ||
                     /* =Z-MH or -Z=M, Z = centerpoint, M = endpoint, other than above */
#endif
                     2 == ( s_type = GetOtherSaltType( at, i, &s_subtype ) )
                     /* >C-SH, >C-S(-); S=S,Se,Te */
                     )
                {

                    if (nNumCandidates >= nMaxNumCandidates)
                    {
                        err = BNS_VERT_EDGE_OVFL;
                        goto quick_exit;
                    }
                    s_candidate[nNumCandidates].atnumber = i;
                    s_candidate[nNumCandidates].type = s_type;
                    s_candidate[nNumCandidates].subtype = s_subtype;
                    s_candidate[nNumCandidates].endpoint = at[i].endpoint;
                    nNumCandidates++;
                    nNumOtherCandidates += ( 1 == s_type );
                    nNumPOnlyCandidates += ( 2 == s_type );
                    s_subtype_all |= s_subtype;
                    /*i1 = i;*/ /* save a representative of a tautomeric group */
                }
            } /* for */

              /* changes: TG_FLAG_ALLOW_NO_NEGTV_O replaced CHARGED_SALTS_ONLY==0 */
            cond1 = s_subtype_all & SALT_ACCEPTOR;
            cond2a = t_group_info->bTautFlags & TG_FLAG_ALLOW_NO_NEGTV_O;
            cond2b = t_group_info->bTautFlagsDone & TG_FLAG_FOUND_SALT_CHARGES_DONE;
            cond2c = t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT;
            if (cond2a || cond2b || cond2c)
            {
                cond2 = !( s_subtype_all & ( SALT_DONOR_Neg | SALT_DONOR_H ) );
            }
            else
            {
                cond2 = !( s_subtype_all & SALT_DONOR_Neg ) || nNumOtherCandidates == nNumCandidates;
            }
            if (nNumCandidates <= 1 || !cond1 || cond2
                 /*(
                 ( cond2a || cond2b    || cond2c )
                 ?        !(s_subtype_all & (SALT_DONOR_Neg | SALT_DONOR_H))
                 :        ( !(s_subtype_all & SALT_DONOR_Neg) || nNumOtherCandidates==nNumCandidates) ) */
                 )
            {
                s_group_info->num_candidates = -1; /* no candidate exists */
                goto quick_exit;
            }
            if (!( s_subtype_all & ( SALT_DONOR_Neg ) ))
            {
                t_group_info->bTautFlagsDone |= TG_FLAG_ALLOW_NO_NEGTV_O_DONE;
            }
        }
        else
        {
            for (i = 0; i < nNumCandidates; i++)
            {
                i1 = s_candidate[i].atnumber;
                if (0 <= ( s_type = GetSaltChargeType( at, i1, t_group_info, &s_subtype ) )
#if ( INCL_NON_SALT_CANDIDATATES == 1 )
                     || 0 < ( s_type = GetOtherSaltChargeType( at, i1, t_group_info, &s_subtype, 1 /* bAccept_O*/ ) )
#endif
                     )
                {
                    s_candidate[nNumCandidates].type = s_type;
                    s_candidate[nNumCandidates].subtype = s_subtype;
                    s_candidate[nNumCandidates].endpoint = at[i1].endpoint;
                }
            }
        }

        /* Look for alt paths connecting:
        SALT_DONOR_Neg to SALT_ACCEPTOR  : long distance migration of negative charges
        SALT_DONOR_H   to SALT_ACCEPTOR  : long distance migration of H-atoms
        */
        do
        {
            nNumChanges = 0;
            for (i1 = 0; i1 < nNumCandidates; i1++)
            {
                j1 = s_candidate[i1].atnumber;
                for (i2 = i1 + 1; i2 < nNumCandidates; i2++)
                {
                    /* prev. approach: do not test if both candidates are not "salt-type". Disabled 2004-03-18
                    if ( s_candidate[i1].type && s_candidate[i2].type )
                    continue;
                    */
                    j2 = s_candidate[i2].atnumber;
                    if (at[j1].endpoint && at[j1].endpoint == at[j2].endpoint)
                    {
                        continue;
                    }
                    for (j = 0; j < 2; j++)
                    {
                        if (j)
                        {
                            ii1 = i2; /* candidate 1 (donor)    ordering number */
                            ii2 = i1; /* candidate 2 (acceptor) ordering number */
                            jj1 = j2; /* candidate 1 (donor)    atom number */
                            jj2 = j1; /* candidate 2 (acceptor) atom number */
                        }
                        else
                        {
                            /* transposition */
                            ii1 = i1; /* candidate 1 (donor)    ordering number */
                            ii2 = i2; /* candidate 2 (acceptor) ordering number */
                            jj1 = j1; /* candidate 1 (donor)    atom number     */
                            jj2 = j2; /* candidate 2 (acceptor) atom number     */
                        }

                        if (( s_candidate[ii1].subtype & ( SALT_DONOR_Neg | SALT_DONOR_H ) ) &&
                            ( s_candidate[ii2].subtype & SALT_ACCEPTOR ))
                        {
                            /****printf("\nChkpt @ %-s:%-d ", __FILE__,__LINE__); fflush(stdout);*/

                            ret = bExistsAltPath( pCG, pBNS, pBD, NULL, at, num_atoms, jj2, jj1, ALT_PATH_MODE_4_SALT );

                            num_tested++;
                            if (IS_BNS_ERROR( ret ))
                            {
                                err = ret;
                                goto quick_exit;
                            }

                            if (ret & 1)
                            {
                                nDelta = ( ret & ~3 ) >> 2;
                                nNumChanges += ( ret & 2 );
                                for (i = 0; i < 2; i++)
                                {
                                    jj = i ? jj2 : jj1;
                                    AddEndPoint( EndPoint + i, at, jj );
                                }

                                /* add/merge taut groups and reinit pBNS in the fly */
                                ret = RegisterEndPoints( pCG, t_group_info,
                                                         EndPoint, 2, at,
                                                         num_atoms, c_group_info,
                                                         pBNS );
                                if (ret == -1)
                                {
                                    ret = BNS_PROGRAM_ERR;
                                }
                                if (ret < 0)
                                {
                                    err = ret;
                                    goto quick_exit;
                                }
                                if (nDelta)
                                {
                                    err = BNS_RADICAL_ERR;
                                    goto quick_exit;
                                }
                                nNumChanges += ( ret > 0 );
                                break; /* avoid redundant repetition */
                            }
                        }
                    }
                }
            }
            nTotNumChanges += nNumChanges;
        } while (num_tested && nNumChanges);

    quick_exit:
        if (!err)
        {
            nTotNumChanges += nNumChanges; /* nNumChanges != 0 only in case of 'goto quick_exit' */
            if (s_group_info->num_candidates == 0)
            {
                /* first time: initialize */
                s_group_info->num_candidates = num_tested ? nNumCandidates : -1; /* no candidate exists */
            }
        }
        else
        {
            nTotNumChanges = err;
        }
#if ( MAX_LOCAL_TGNUM > 0 )
        if (ptgData != tgData)
        {
            inchi_free( ptgData );
        }
#endif
    }

    return nTotNumChanges;
}


#else


/********************************************************************************************************/
int MarkSaltChargeGroups( CANON_GLOBALS *pCG, inp_ATOM *at, int num_atoms, S_GROUP_INFO *s_group_info,
                          T_GROUP_INFO *t_group_info, C_GROUP_INFO *c_group_info,
                          struct BalancedNetworkStructure *pBNS, struct BalancedNetworkData *pBD )
{

    int nNumChanges = 0, nTotNumChanges = 0;
    if (s_group_info && s_group_info->s_candidate && s_group_info->max_num_candidates > 0)
    {
        int i, i1, i2, j, j1, j2, jj, ii1, ii2, jj1, jj2, k, num_tested;
        S_CANDIDATE *s_candidate = s_group_info->s_candidate;
        int          nMaxNumCandidates = s_group_info->max_num_candidates;
        int          nNumCandidates = s_group_info->num_candidates;
        int          nNumOtherCandidates = s_group_info->num_other_candidates;
        int          s_type, s_subtype;
        int          ret, nDelta, nMobile;
        int          s_subtype_all = 0;
        T_ENDPOINT   EndPoint[2];

        if (nNumCandidates <= -1 || !t_group_info || !t_group_info->t_group)
        {
            return 0;
        }
        else
            if (nNumCandidates == 0)
            {
                for (i = 0, nNumCandidates = nNumOtherCandidates = 0; i < num_atoms; i++)
                {
                    if (0 <= ( s_type = GetSaltChargeType( at, i, t_group_info, &s_subtype ) ))
                    {
                        if (nNumCandidates >= nMaxNumCandidates)
                        {
                            return BNS_VERT_EDGE_OVFL;
                        }
                        s_candidate[nNumCandidates].atnumber = i;
                        s_candidate[nNumCandidates].type = s_type;
                        s_candidate[nNumCandidates].subtype = s_subtype;
                        s_candidate[nNumCandidates].endpoint = at[i].endpoint;
                        nNumCandidates++;
                        s_subtype_all |= s_subtype;
                        /*i1 = i;*/ /* save a representative of a tautomeric group */
                    }
#if ( INCL_NON_SALT_CANDIDATATES == 1 )
                    else  /* new */
                        if (0 < ( s_type = GetOtherSaltChargeType( at, i, t_group_info, &s_subtype, 1 /* bAccept_O*/ ) ))
                        {
                            if (nNumCandidates >= nMaxNumCandidates)
                            {
                                return BNS_VERT_EDGE_OVFL;
                            }
                            s_candidate[nNumCandidates].atnumber = i;
                            s_candidate[nNumCandidates].type = s_type;
                            s_candidate[nNumCandidates].subtype = s_subtype;
                            s_candidate[nNumCandidates].endpoint = at[i].endpoint;
                            nNumCandidates++;
                            nNumOtherCandidates++;
                            s_subtype_all |= s_subtype;
                        }
#endif
                }

                /* changes: TG_FLAG_ALLOW_NO_NEGTV_O replaced CHARGED_SALTS_ONLY==0 */
                if (nNumCandidates <= 1 || nNumOtherCandidates == nNumCandidates ||
                    ( ( t_group_info->bTautFlags & TG_FLAG_ALLOW_NO_NEGTV_O ) ?
                      !( s_subtype_all & ( SALT_DONOR_Neg | SALT_DONOR_H ) ) :
                      !( s_subtype_all & SALT_DONOR_Neg ) ) ||
                     !( s_subtype_all & SALT_ACCEPTOR ))
                {
                    s_group_info->num_candidates = -1; /* no candidate exists */
                    return 0;
                }
                if (!( s_subtype_all & ( SALT_DONOR_Neg ) ))
                {
                    t_group_info->bTautFlagsDone |= TG_FLAG_ALLOW_NO_NEGTV_O_DONE;
                }
            }
            else
            {
                for (i = 0; i < nNumCandidates; i++)
                {
                    i1 = s_candidate[i].atnumber;
                    if (0 <= ( s_type = GetSaltChargeType( at, i1, t_group_info, &s_subtype ) )
#if ( INCL_NON_SALT_CANDIDATATES == 1 )
                         || 0 < ( s_type = GetOtherSaltChargeType( at, i1, t_group_info, &s_subtype, 1 /* bAccept_O*/ ) )
#endif
                         )
                    {
                        s_candidate[nNumCandidates].type = s_type;
                        s_candidate[nNumCandidates].subtype = s_subtype;
                        s_candidate[nNumCandidates].endpoint = at[i1].endpoint;
                    }
                }
            }
        /* Look for alt paths connecting:
        SALT_DONOR_Neg to SALT_ACCEPTOR  : long distance migration of negative charges
        SALT_DONOR_H   to SALT_ACCEPTOR  : long distance migration of H-atoms
        */
        num_tested = 0;
        do
        {
            nNumChanges = 0;
            for (i1 = 0; i1 < nNumCandidates; i1++)
            {
                j1 = s_candidate[i1].atnumber;
                for (i2 = i1 + 1; i2 < nNumCandidates; i2++)
                {
                    if (s_candidate[i1].type && s_candidate[i2].type)
                        continue; /* both candidates are not "salt-type" */
                    j2 = s_candidate[i2].atnumber;
                    if (at[j1].endpoint && at[j1].endpoint == at[j2].endpoint)
                    {
                        continue;
                    }
                    for (j = 0; j < 2; j++)
                    {
                        if (j)
                        {
                            ii1 = i2; /* candidate 1 (donor)    ordering number */
                            ii2 = i1; /* candidate 2 (acceptor) ordering number */
                            jj1 = j2; /* candidate 1 (donor)    atom number */
                            jj2 = j1; /* candidate 2 (acceptor) atom number */
                        }
                        else
                        {      /* transposition */
                            ii1 = i1; /* candidate 1 (donor)    ordering number */
                            ii2 = i2; /* candidate 2 (acceptor) ordering number */
                            jj1 = j1; /* candidate 1 (donor)    atom number     */
                            jj2 = j2; /* candidate 2 (acceptor) atom number     */
                        }

                        if (( s_candidate[ii1].subtype & ( SALT_DONOR_Neg | SALT_DONOR_H ) ) &&
                            ( s_candidate[ii2].subtype & SALT_ACCEPTOR ))
                        {
                            ret = bExistsAltPath( pCG, pBNS, pBD, NULL, at, num_atoms, jj2, jj1, ALT_PATH_MODE_4_SALT );
                            num_tested++;
                            if (IS_BNS_ERROR( ret ))
                            {
                                return ret;
                            }
                            if (ret & 1)
                            {
                                nDelta = ( ret & ~3 ) >> 2;
                                nNumChanges += ( ret & 2 );
                                for (i = 0; i < 2; i++)
                                {
                                    jj = i ? jj2 : jj1;
                                    EndPoint[i].nAtomNumber = jj;
                                    EndPoint[i].nEquNumber = 0;
                                    EndPoint[i].nGroupNumber = at[jj].endpoint;
                                    if (at[jj].endpoint)
                                    {
                                        memset( EndPoint[i].num, 0, sizeof( EndPoint[i].num ) );
                                    }
                                    else
                                    {
                                        AddAtom2num( EndPoint[i].num, at, jj, 2 ); /* fill out */
                                        AddAtom2DA( EndPoint[i].num_DA, at, jj, 2 );
                                        /*
                                        nMobile  = EndPoint[i].num[1] = (at[jj].charge == -1);
                                        nMobile  = EndPoint[i].num[0] = at[jj].num_H + nMobile;
                                        for ( k = 0; k < T_NUM_ISOTOPIC; k ++ ) {
                                        EndPoint[i].num[T_NUM_NO_ISOTOPIC+k] = at[jj].num_iso_H[NUM_H_ISOTOPES-k-1];
                                        }
                                        */
                                    }
                                }
                                /* add/merge taut groups and reinit pBNS */
                                ret = RegisterEndPoints( pCG, t_group_info,
                                                         EndPoint, 2, at, num_atoms, c_group_info, pBNS );
                                if (ret < 0)
                                {
                                    return ret;
                                }
                                nNumChanges += ( ret > 0 );
                                if (nDelta)
                                {
                                    goto quick_exit;
                                }
                                break; /* avoid redundant repetition */
                            }
                        }
                    }
                }
            }
            nTotNumChanges += nNumChanges;
        } while (num_tested && nNumChanges);

    quick_exit:
        nTotNumChanges += nNumChanges; /* nNumChanges != 0 only in case of 'goto quick_exit' */
        if (s_group_info->num_candidates == 0)
        {
            /* first time: initialize */
            s_group_info->num_candidates = num_tested ? nNumCandidates : -1; /* no candidate exists */
        }
    }
    return nTotNumChanges;
}
#endif


/****************************************************************************/
int MergeSaltTautGroups( CANON_GLOBALS *pCG,
                         inp_ATOM *at,
                         int num_atoms,
                         S_GROUP_INFO *s_group_info,
                         T_GROUP_INFO *t_group_info,
                         C_GROUP_INFO *c_group_info,
                         struct BalancedNetworkStructure *pBNS )
{
    /* Count candidates to be connected: exclude pure donors that do not belong to any t-group */
    AT_NUMB    nCurTGroupNumber;
    int        i, j, /*k,*/ ret, iat, /*nMobile,*/ nMinNumEndpoints;
    int        s_subtype_all, s_subtype_taut;
    int        nMaxNumCandidates, nNumCandidates; /* djb-rwth: removing redundant variables */
    T_ENDPOINT EndPointStackArray[MAX_STACK_ARRAY_LEN]; /* will be reallocated if too short */
    T_ENDPOINT  *EndPoint = EndPointStackArray;


    if (!s_group_info || !s_group_info->s_candidate || /*s_group_info->num_candidates <= 0 ||*/
         !t_group_info || !t_group_info->t_group || !c_group_info)
    {
        return 0;
    }
    nMinNumEndpoints = 0;
    nMaxNumCandidates = s_group_info->max_num_candidates;
    nCurTGroupNumber = MAX_ATOMS;  /* impossible t-group number */
    s_subtype_all = s_subtype_taut = 0;

    /* Collect tautomeric acidic O and previously non-tautomeric C-OH, C-SH, C-O(-), C-S(-)  */
    /* find whether previously found tautomeric atoms have both mobile H and (-) */
    if (1 || ( s_group_info->num_candidates < 0 ))
    {
        /* Can be only -O(-)  and -OH */
        int          s_type, s_subtype;
        S_CANDIDATE *s_candidate = s_group_info->s_candidate;
        for (i = 0, nNumCandidates = 0; i < num_atoms; i++) /* djb-rwth: removing redundant code */
        {
            s_subtype = 0;
            if (0 == ( s_type = GetSaltChargeType( at, i, t_group_info, &s_subtype ) ) ||
                 /* -C=O or =C-OH, O = S, Se, Te */

                 /*(t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT) &&*/
                 1 == ( s_type = GetOtherSaltChargeType( at, i, t_group_info, &s_subtype, 1/* bAccept_O*/ ) ) ||
                 /* =Z-MH or -Z=M, Z = centerpoint, M = endpoint, other than above. M may be N */

                 2 == ( s_type = GetOtherSaltType( at, i, &s_subtype ) ) ||
                 /* >C-SH, >C-S(-); S=S,Se,Te */

                 /* other proton donor or acceptor */
                bHasAcidicHydrogen(at, i) && ((s_type = 3), (s_subtype = SALT_p_DONOR)) || /* djb-rwth: ui_rr */
                bHasAcidicMinus(at, i) && ((s_type = 3), (s_subtype = SALT_p_ACCEPTOR)) /* djb-rwth: ui_rr */
                )
            {

                if (nNumCandidates >= nMaxNumCandidates)
                {
                    return BNS_VERT_EDGE_OVFL;
                }
                if (at[i].endpoint)
                {
                    s_subtype_taut |= s_subtype;
                }
                else
                {
                    if (bDoNotMergeNonTautAtom( at, i ))
                    {
                        continue; /* ignore non-tautomeric N */
                    }
                }
                if (!( s_subtype & SALT_DONOR_ALL ) ||
                    (( s_subtype & SALT_ACCEPTOR ) && !at[i].endpoint)) /* djb-rwth: addressing LLVM warning */
                {
                    continue;  /* do not include non-taut acceptors like -C=O */
                }
                s_candidate[nNumCandidates].atnumber = i;
                s_candidate[nNumCandidates].type = s_type;
                s_candidate[nNumCandidates].subtype = s_subtype;
                s_candidate[nNumCandidates].endpoint = at[i].endpoint;
                nNumCandidates++;
                s_subtype_all |= s_subtype;
            }
        }

        /*
        Forced merging occurs upon:
        ===========================
        (t_group_info->bTautFlags & TG_FLAG_ALLOW_NO_NEGTV_O) or
        (t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT)


        Allow forced merging in cases:
        {t-groups}  (H, (-)}  {H, (-), t-groups}


        Normal salt merging in cases:
        (H, (-)} {H, (-), t-groups},

        Cannot merge H into t-groups if no (-) is present
        */
        if (( t_group_info->bTautFlags & TG_FLAG_ALLOW_NO_NEGTV_O ) ||
            ( t_group_info->bTautFlagsDone & TG_FLAG_FOUND_SALT_CHARGES_DONE ) ||
             ( t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT ))
        {
            /* Force merge even though no negative charges are present */
            if (nNumCandidates <= 1 ||
                (( !( s_subtype_all & SALT_DONOR_Neg2 ) || !( s_subtype_all & SALT_DONOR_H2 ) ) &&
                 !t_group_info->num_t_groups)) /* djb-rwth: addressing LLVM warning */
            {
                s_group_info->num_candidates = -1; /* no candidate exists */
                return 0;
            }
        }
        else
        {
            /* normal salt mode: merge if both -XH and -X(-) are present */
            if (nNumCandidates <= 1 ||
                ( !( s_subtype_all & SALT_DONOR_Neg2 ) || !( s_subtype_all & SALT_DONOR_H2 ) ))
            {
                s_group_info->num_candidates = -1; /* no candidate exists */
                return 0;
            }
        }
        /* -- old code --
        if ( nNumCandidates <= 1 ||
        (((t_group_info->bTautFlags & TG_FLAG_ALLOW_NO_NEGTV_O) ||
        (t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT)) ?
        !(s_subtype_all & SALT_DONOR_ALL):
        !(s_subtype_all & SALT_DONOR_Neg2)
        )
        ) {
        s_group_info->num_candidates = -1;
        return 0;
        }
        */
        if (!( s_subtype_all & ( SALT_DONOR_Neg2 ) ))
        {
            t_group_info->bTautFlagsDone |= TG_FLAG_ALLOW_NO_NEGTV_O_DONE;
        }
        s_group_info->num_candidates = nNumCandidates;
    }

    for (i = 0; i < s_group_info->num_candidates; i++)
    {
        iat = s_group_info->s_candidate[i].atnumber;
        if (( s_group_info->s_candidate[i].subtype & SALT_ACCEPTOR ) && !at[iat].endpoint)
        {
            continue; /* should not happen */
        }
        s_subtype_all |= s_group_info->s_candidate[i].subtype;
        if (at[iat].endpoint != nCurTGroupNumber || !at[iat].endpoint)
        {
            nMinNumEndpoints++;
        }
        nCurTGroupNumber = (int) at[iat].endpoint;
    }
    if (nMinNumEndpoints <= 1)
    {
        return 0; /* too few endpoints */
    }

    /* Make sure we have enough memory */
    if (nMinNumEndpoints > MAX_STACK_ARRAY_LEN)
    {
        if (!( EndPoint = (T_ENDPOINT *) inchi_calloc( nMinNumEndpoints, sizeof( EndPoint[0] ) ) ))
        {
            /*printf("BNS_OUT_OF_RAM-8\n");*/
            return BNS_OUT_OF_RAM;
        }
    }

    nCurTGroupNumber = MAX_ATOMS;  /* impossible t-group number */
    for (i = j = 0; i < s_group_info->num_candidates; i++)
    {
        iat = s_group_info->s_candidate[i].atnumber;
        if (s_group_info->s_candidate[i].subtype == SALT_ACCEPTOR && !at[iat].endpoint)
        {
            continue;
        }
        if (at[iat].endpoint != nCurTGroupNumber || !at[iat].endpoint)
        {
            AddEndPoint( EndPoint + j, at, iat );
            j++;
        }
        nCurTGroupNumber = (int) at[iat].endpoint;
    }

    ret = RegisterEndPoints( pCG, t_group_info, EndPoint, j, at,
                             num_atoms, c_group_info, pBNS );

    if (ret == -1)
    {
        ret = BNS_PROGRAM_ERR;
    }

    if (EndPoint != EndPointStackArray)
    {
        inchi_free( EndPoint );
    }

    return ret;
}


/****************************************************************************/
int MakeIsotopicHGroup( inp_ATOM *at,
                        int num_atoms,
                        S_GROUP_INFO *s_group_info,
                        T_GROUP_INFO *t_group_info )
{
    /* All tautomeric atoms and all possible H+ donors and acceptors that have H */
    int        i, j, k, n, bHasH, tg, nError = 0;
    /* djb-rwth: removing redundant variables */
    int        nMaxNumCandidates, nNumCandidates, nNumNonTautCandidates;


    if (!s_group_info || !s_group_info->s_candidate || /*s_group_info->num_candidates <= 0 ||*/
         !t_group_info || !t_group_info->t_group)
    {
        return 0;
    }
    nMaxNumCandidates = s_group_info->max_num_candidates;
    /* djb-rwth: removing redundant code */
    memset( t_group_info->num_iso_H, 0, sizeof( t_group_info->num_iso_H ) ); /* djb-rwth: memset_s C11/Annex K variant? */
    if (1 || ( s_group_info->num_candidates < 0 ))
    {
        int s_type, s_subtype;
        S_CANDIDATE *s_candidate = s_group_info->s_candidate;
        for (i = 0, nNumCandidates = nNumNonTautCandidates = 0; i < num_atoms; i++)
        {
            s_subtype = 0;
            s_type = 0;
            if (at[i].endpoint)
            {
                if (( tg = t_group_info->tGroupNumber[at[i].endpoint] ) &&
                     at[i].endpoint == t_group_info->t_group[tg -= 1].nGroupNumber)
                {
                    bHasH = (int) t_group_info->t_group[tg].num[0] - (int) t_group_info->t_group[tg].num[1];
                }
                else
                {
                    nError = BNS_PROGRAM_ERR;
                    break;
                }
            }
            else
            {
                bHasH = (int) at[i].num_H;
            }
            if ((bHasH && at[i].endpoint) || /* tautomeric atoms */
                                           /* non-tautomeric heteroatoms that
                                           (a) have H and
                                           (b) may be donors of H
                                           therefore may exchange isotopic-non-isotopic H */
                 (bHasH &&
                 ( 0 == ( s_type = GetSaltChargeType( at, i, t_group_info, &s_subtype ) ) ||
                   /* -C=O or =C-OH, O = S, Se, Te */

                   /*(t_group_info->tni.bNormalizationFlags & FLAG_FORCE_SALT_TAUT) &&*/
                   1 == ( s_type = GetOtherSaltChargeType( at, i, t_group_info, &s_subtype, 1/* bAccept_O*/ ) ) ||
                   /* =Z-MH or -Z=M, Z = centerpoint, M = endpoint, other than above. M may be N */

                   2 == ( s_type = GetOtherSaltType( at, i, &s_subtype ) ) ||
                   /* >C-SH, >C-S(-); S=S,Se,Te */

                   /* other proton donor or acceptor */
                     bHasAcidicHydrogen(at, i) && ((s_type = 3), (s_subtype = SALT_p_DONOR)) || /* djb-rwth: ui_rr */
                     bHasAcidicMinus(at, i) && ((s_type = 3), (s_subtype = SALT_p_ACCEPTOR)) || /* djb-rwth: ui_rr */
                     bHasOtherExchangableH(at, i) && ((s_type = 3), (s_subtype = SALT_DONOR_H)))) /* djb-rwth: ui_rr */

                     )
            {
                if (nNumCandidates >= nMaxNumCandidates)
                {
                    return BNS_VERT_EDGE_OVFL;
                }
                s_candidate[nNumCandidates].atnumber = i;
                s_candidate[nNumCandidates].type = s_type;
                s_candidate[nNumCandidates].subtype = s_subtype;
                s_candidate[nNumCandidates].endpoint = at[i].endpoint;
                nNumCandidates++;
                nNumNonTautCandidates += !at[i].endpoint;
                /* djb-rwth: removing redundant code */
            }
        }

        if (nError)
        {
            return nError;
        }

        if (nNumCandidates > 0)
        {
            t_group_info->nIsotopicEndpointAtomNumber = (AT_NUMB *) inchi_calloc( (long long)nNumNonTautCandidates + 1, sizeof( t_group_info->nIsotopicEndpointAtomNumber[0] ) ); /* djb-rwth: cast operator added */
            if (t_group_info->nIsotopicEndpointAtomNumber) /* djb-rwth: fixing a NULL pointer dereference */
            {
                t_group_info->nIsotopicEndpointAtomNumber[0] = nNumNonTautCandidates;
                for (i = 0, n = 1; i < nNumCandidates; i++)
                {
                    k = s_candidate[i].atnumber;
                    if (!at[k].endpoint)
                    {
                        t_group_info->nIsotopicEndpointAtomNumber[n++] = k;
                    }
                    for (j = 0; j < NUM_H_ISOTOPES; j++)
                    {
                        t_group_info->num_iso_H[j] += at[k].num_iso_H[j];
                    }
                    at[k].cFlags |= AT_FLAG_ISO_H_POINT;
                }
                t_group_info->nNumIsotopicEndpoints = nNumNonTautCandidates + 1;
            }
        }
    }

    return nNumCandidates;
}


/*#else*/ /* } DISCONNECT_SALTS == 0 */


          /**********************************************************************************
          Charges and tautomeric endpoints (N only)
          **********************************************************************************

          H = number of possibly moveable hydrogen atoms
          C = possibly moveable positive charge

          - = single bond
          = = double bond
          # = triple bond

          +-----------------------------------------------------------------------------+
          |ca-| H    | edges to t-   | 1 bond         |  2 bonds       |  3 bonds    *) |
          |se | C    | and c-groups  | (valence)      |  (valence)     |  (valence)     |
          | # |      | (edges flow)  |                |                |                |
          +---|------+---------------+----------------+----------------+----------------|
          | 1 | H=0  | --  (1)       | =NH     (3)    |  =N-     (3)   |   >N-     (3)  |
          |   | C=0  | ==            |                |                |                |
          +---|------+---------------+----------------+----------------+----------------|
          | 2 | H=1  | ==  (2)       | -NH2    (3)    |  -NH-    (3)   |   none         |
          |   | C=0  | ==            |                |                |                |
          +---|------+---------------+----------------+----------------+----------------|
          | 3 | H=0  | --  (0)       | #NH(+)  (4)    |  =N(+)=  (4) +)|   >N(+)=  (4)  |
          |   | C=1  | --            | (prohibited    |                |                |
          |   |      |               | by edge cap)   |                |                |
          +---|------+---------------+----------------+----------------+----------------|
          | 4 | H=1  | ==  (1)       | =NH2(+) (4)  +)|  =NH(+)- (4) +)|   >NH(+)- (4)  |
          |   | C=1  | --            |                |                |                |
          +---+-------------------------------------------------------------------------+

          *) Cannot be a tautomeric endpoint

          +) The three charged types of atoms [=N(+)=, =NH(+)-, =NH2(+)] should be
          checked for possible H-tautomerism. Other types in the marked by *)
          column should not be checked as long as H(+) exchange is not considered
          tautomeric.

          Other possibilities:  -NH3(+)  >NH2(+)  >N(+)<  cannot be H-tautomeric endpoints.

          Case #1 (H=0, C=0) and #4 (H=1,C=0) is indistinguishable from the
          viewpoint of edges flow and capacities except for flow from N to (+) vertex.

          Without taking precautions H(+) can be transferred

          from =NH2(+)  to =NH,
          from =NH(+)-  to =N-,
          from >NH(+)-  to >N-

          or to any other appropriate atom that has a lone electron pair and bonds
          will not change. In this case no bond must be marked as tautomeric.

          For this reason before attempting to transfer H from one endpoint to
          another the charges on the two atoms should be set to zero by
          forcing zero flow from each of atoms to the (+)-vertices if the
          atoms belong to a c-group.

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


          /****************************************************************************
          Mark Tautomer Groups:
          do not identify positively charged N as endpoints for now
          ****************************************************************************/
int MarkTautomerGroups( CANON_GLOBALS *pCG,
                        inp_ATOM *at,
                        int num_atoms,
                        T_GROUP_INFO *t_group_info,
                        C_GROUP_INFO *c_group_info,
                        struct BalancedNetworkStructure *pBNS,
                        struct BalancedNetworkData *pBD )
{
    int i, j, k, m, endpoint_valence, centerpoint, endpoint, bond_type, nMobile, num_changes = 0, tot_changes = 0;
    T_ENDPOINT EndPoint[MAXVAL];
    T_BONDPOS  BondPos[MAXVAL];
    AT_NUMB    nGroupNumber;
    int        bDiffGroups;
    int  nNumEndPoints, nNumBondPos, nNumPossibleMobile;
    int  bNonTautBond, bAltBond; /* djb-rwth: removing redundant variables */
    int  nNumDonor, nNumAcceptor, bPossiblyEndpoint;
    int bIgnoreIsotopic; /* djb-rwth: removing redundant variables */
    ENDPOINT_INFO eif1, eif2;
    int nErr = 0;

#define ALLOWED_EDGE(PBNS, IAT,IBOND)  ( !(PBNS) || !(PBNS)->edge || !(PBNS)->vert || !(PBNS)->edge[(PBNS)->vert[IAT].iedge[IBOND]].forbidden)
#define ACTUAL_ORDER(PBNS, IAT,IBOND, BTYPE)  ( ((PBNS) && (PBNS)->edge && (PBNS)->vert &&\
    ((BTYPE)==BOND_ALT_123 || (BTYPE)==BOND_ALT_13 || (BTYPE)==BOND_ALT_23))? (PBNS)->edge[(PBNS)->vert[IAT].iedge[IBOND]].flow+BOND_TYPE_SINGLE:(BTYPE))


    if (!t_group_info || !( t_group_info->bTautFlags & TG_FLAG_TEST_TAUT__ATOMS ))
    {
        return 0;
    }

    /*  Initial t_group allocation */
    if (!t_group_info->t_group && !t_group_info->max_num_t_groups)
    {
        INCHI_MODE bTautFlags = t_group_info->bTautFlags;       /*  save initial setting */
        INCHI_MODE bTautFlagsDone = t_group_info->bTautFlagsDone;   /*  save previous findings, if any */
        TNI       tni = t_group_info->tni;
        AT_NUMB   *tGroupNumber = t_group_info->tGroupNumber;
        T_GROUP* tgi_tgr = NULL;  /* copied from below 2024-09-01 DT */

        bIgnoreIsotopic = t_group_info->bIgnoreIsotopic;
        memset( t_group_info, 0, sizeof( *t_group_info ) ); /* djb-rwth: memset_s C11/Annex K variant? */
        t_group_info->bIgnoreIsotopic = bIgnoreIsotopic; /*  restore initial setting */
        t_group_info->bTautFlags = bTautFlags;
        t_group_info->bTautFlagsDone = bTautFlagsDone;
        t_group_info->tni = tni;
        t_group_info->tGroupNumber = tGroupNumber;
        t_group_info->max_num_t_groups = num_atoms / 2 + 1; /*  upper limit */
        /* djb-rwth: fixing oss-fuzz issue #52978 */
        tgi_tgr = (T_GROUP*)inchi_calloc((long long)t_group_info->max_num_t_groups + 1, sizeof(t_group_info->t_group[0]));
        if (!tgi_tgr)
        {
            t_group_info->max_num_t_groups = -1;
            t_group_info->t_group = NULL;
            return (-1); /*  failed, out of RAM */
        }
        else
        {
            t_group_info->t_group = tgi_tgr;
        }
    }

    /*  Check if t_group_info exists */
    if (!t_group_info->t_group || !t_group_info->max_num_t_groups)
    {
        return 0;
    }

    if (0 > t_group_info->max_num_t_groups)
    {
        return t_group_info->max_num_t_groups;
    }

    /* djb-rwth: removing redundant code */

    /*  1-3 tautomers */
    for (i = 0; i < num_atoms; i++)
    {
        /*  Find possible endpoint Z = at[i] */
        if ((endpoint_valence = nGetEndpointInfo( at, i, &eif1 ))) /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
        {
            /*  1st endpoint candidate found. Find centerpoint candidate */
            for (j = 0; j < at[i].valence; j++)
            {
                bond_type = (int) at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                bond_type = ACTUAL_ORDER( pBNS, i, j, bond_type );
#endif
                centerpoint = (int) at[i].neighbor[j];  /*  a centerpoint candidate */
                if (( bond_type == BOND_DOUBLE ||
                      bond_type == BOND_ALTERN ||
                      bond_type == BOND_ALT12NS ||
                      bond_type == BOND_TAUTOM ) && is_centerpoint_elem( at[centerpoint].el_number )
                     && ALLOWED_EDGE( pBNS, i, j )
                     )
                {
                    /*  Test a centerpoint candidate. */
                    /*  find all endpoints including at[i] and store them into EndPoint[] */
                    nNumPossibleMobile = 0;
                    nGroupNumber = (AT_NUMB) num_atoms; /*  greater than any tautomeric group number */
                    bDiffGroups = -1;         /*  ignore the first difference */
                    nNumDonor = nNumAcceptor = 0;
                    for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++)
                    {
                        endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                        bond_type = (int) at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                        bond_type = ACTUAL_ORDER( pBNS, centerpoint, k, bond_type );
#endif
                        /* djb-rwth: removing redundant code */
                            bNonTautBond =
                            bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                            bPossiblyEndpoint = 0;
                        if (!ALLOWED_EDGE( pBNS, centerpoint, k ))
                        {
                            continue;
                        }
                        else
                        {
                            if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_TAUTOM)
                            {
                                /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                bAltBond = ( bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS ); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                            }
                            else
                            {
                                if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE)
                                {
                                    bNonTautBond = 1;
                                }
                                else
                                {
                                    continue;
                                }
                            }
                        }

                        if (!( endpoint_valence = nGetEndpointInfo( at, endpoint, &eif1 ) )) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
                            continue; /*  not an endpoint element or can't have mobile groups */
                                      /*  save information about the found possible tautomeric endpoint */
                                      /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                        nMobile =
                            AddAtom2num( EndPoint[nNumEndPoints].num, at, endpoint, 2 ); /* fill out */
                        AddAtom2DA( EndPoint[nNumEndPoints].num_DA, at, endpoint, 2 );
                        /* --- why is isitopic info missing ? -- see below
                        nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                        nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                        */
                        if (bNonTautBond)
                        {
                            m = ( bond_type == BOND_SINGLE && ( nMobile || at[endpoint].endpoint ) );
                            nNumDonor += m;
                            bPossiblyEndpoint += m;
                            m = ( bond_type == BOND_DOUBLE );
                            nNumAcceptor += m;
                            bPossiblyEndpoint += m;
                        }
                        else
                        {
                            /*  Tautomeric or alternating bond */
                            m = ( 0 != at[endpoint].endpoint || eif1.cDonor );
                            nNumDonor += m;
                            bPossiblyEndpoint += m;
                            m = ( at[endpoint].endpoint ||
                                  eif1.cNeutralBondsValence > at[endpoint].valence );
                            nNumAcceptor += m;
                            bPossiblyEndpoint += m;
                        }
                        if (!bPossiblyEndpoint)
                        {
                            continue;
                        }
                        EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                        EndPoint[nNumEndPoints].nEquNumber = 0;
                        EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB) endpoint;
                        if (nGroupNumber != at[endpoint].endpoint)
                        {
                            bDiffGroups++;
                            nGroupNumber = at[endpoint].endpoint;
                        }

                        /*  save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                        if (bNonTautBond || bAltBond)
                        {
#endif
                            BondPos[nNumBondPos].nAtomNumber = (AT_NUMB) centerpoint;
                            BondPos[nNumBondPos].neighbor_index = (AT_NUMB) k; /* bond ordering number; used to change bonds to tautomeric only  */
                            nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                        }
#endif
                        /*  mobile group is possible if (a) the endpoint has a mobile group or */
                        /*                              (b) the centerpoint is adjacent to another endpoint */
                        nNumPossibleMobile += ( nMobile > 0 || at[endpoint].endpoint );
                        nNumEndPoints++;
                        /*printf("Found %d %d %d %d\n", centerpoint+1, at[centerpoint].el_number, endpoint+1, at[endpoint].el_number);*/
                    }
                    if (nNumEndPoints > 1 && nNumPossibleMobile && nNumDonor && nNumAcceptor)
                    {
                        /*
                        * a tautomeric group has been found
                        *
                        * at this point:
                        * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                        * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                        * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                        * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                        */

                        nErr = FindAccessibleEndPoints( pCG, EndPoint,
                                                        &nNumEndPoints,
                                                        BondPos, &nNumBondPos,
                                                        pBNS, pBD, at,
                                                        num_atoms, c_group_info,
                                                        ALT_PATH_MODE_TAUTOM );

                        if (IS_BNS_ERROR( nErr ))
                        {
                            return nErr;
                        }
                        nErr = 0;
                        if (nNumEndPoints > 0) {
                            if (!nGroupNumber || bDiffGroups > 0) {

                                num_changes = RegisterEndPoints(pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS);
                                if (num_changes == -1) {
                                    nErr = CT_TAUCOUNT_ERR;
                                }
                                if (num_changes < 0) {
                                    nErr = num_changes;
                                }
                                if (nErr)
                                    goto exit_function;
                                tot_changes += (num_changes>0);
                            }
                            if (nNumBondPos > 0) {
                                /*  some of the bonds have not been marked as tautomeric yet */
                                num_changes = SetTautomericBonds(at, nNumBondPos, BondPos);
                                tot_changes += (num_changes>0);
                            }
                        }
                    }
                }
            }
        }
    }


#if ( TAUT_PT_22_00 == 1 ) /******* BEGIN PT_22_00 ********/
    if (t_group_info->bTautFlags & TG_FLAG_PT_22_00) {
        /*** [#1:1][CX4:2][NX2:3]=[CX3:4]>>[CX3:2]=[NX2:3][CX4:4][#1:1] ***/
        /*** Similar to the previous case of M=Q-ZH >> MH-Q=Z, with M,Z = "C" and Q = "N" ***/
        for (i = 0; i < num_atoms; i++) {
            /*  find possible endpoint Z = at[i] */
            if ((endpoint_valence = nGetEndpointInfo_PT_22_00(at, i, &eif1))) { /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
                /*  1st endpoint candidate found. Find centerpoint candidate */
                for (j = 0; j < at[i].valence; j++) {
                    bond_type = (int)at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                    bond_type = ACTUAL_ORDER(pBNS, i, j, bond_type);
#endif
                    centerpoint = (int)at[i].neighbor[j];  /*  a centerpoint candidate */
                    if ((bond_type == BOND_DOUBLE ||
                        bond_type == BOND_ALTERN ||
                        bond_type == BOND_ALT12NS ||
                        bond_type == BOND_TAUTOM) && at[centerpoint].el_number == EL_NUMBER_N
                        && ALLOWED_EDGE(pBNS, i, j)
                        ) {
                        /*  test a centerpoint candidate. */
                        /*  find all endpoints including at[i] and store them into EndPoint[] */
                        nNumPossibleMobile = 0;
                        nGroupNumber = (AT_NUMB)num_atoms; /*  greater than any tautomeric group number */
                        bDiffGroups = -1;         /*  ignore the first difference */
                        nNumDonor = nNumAcceptor = 0;
                        for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++) {
                            endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                            bond_type = (int)at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                            bond_type = ACTUAL_ORDER(pBNS, centerpoint, k, bond_type);
#endif
                            /* djb-rwth: removing redundant code */
                                bNonTautBond =
                                bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                                bPossiblyEndpoint = 0;
                            if (!ALLOWED_EDGE(pBNS, centerpoint, k)) {
                                continue;
                            }
                            else
                                if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_TAUTOM) {
                                    /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                    bAltBond = (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                                }
                                else
                                    if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE)
                                        bNonTautBond = 1;
                                    else
                                        continue;

                            if (!(endpoint_valence = nGetEndpointInfo_PT_22_00(at, endpoint, &eif1))) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
                                continue; /*  not an endpoint element or can't have mobile groups */
                                          /*  save information about the found possible tautomeric endpoint */
                                          /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                            nMobile =
                                AddAtom2num(EndPoint[nNumEndPoints].num, at, endpoint, 2); /* fill out */
                            AddAtom2DA(EndPoint[nNumEndPoints].num_DA, at, endpoint, 2);
                            /* --- why is isitopic info missing ? -- see below
                            nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                            nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                            */
                            if (bNonTautBond) {
                                m = (bond_type == BOND_SINGLE && (nMobile || at[endpoint].endpoint));
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (bond_type == BOND_DOUBLE);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            else {
                                /*  tautomeric or alternating bond */
                                m = (0 != at[endpoint].endpoint || eif1.cDonor);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (at[endpoint].endpoint ||
                                    eif1.cNeutralBondsValence > at[endpoint].valence);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            if (!bPossiblyEndpoint)
                                continue;
                            EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                            EndPoint[nNumEndPoints].nEquNumber = 0;
                            EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB)endpoint;
                            if (nGroupNumber != at[endpoint].endpoint) {
                                bDiffGroups++;
                                nGroupNumber = at[endpoint].endpoint;
                            }

                            /*  save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            if (bNonTautBond || bAltBond) {
#endif
                                BondPos[nNumBondPos].nAtomNumber = (AT_NUMB)centerpoint;
                                BondPos[nNumBondPos].neighbor_index = (AT_NUMB)k; /* bond ordering number; used to change bonds to tautomeric only  */
                                nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            }
#endif
                            /*  mobile group is possible if (a) the endpoint has a mobile group or */
                            /*                              (b) the centerpoint is adjacent to another endpoint */
                            nNumPossibleMobile += (nMobile>0 || at[endpoint].endpoint);
                            nNumEndPoints++;
                            /*printf("Found %d %d %d %d\n", centerpoint+1, at[centerpoint].el_number, endpoint+1, at[endpoint].el_number);*/
                        }
                        if (nNumEndPoints > 1 && nNumPossibleMobile && nNumDonor && nNumAcceptor) {
                            /*
                            * a tautomeric group has been found
                            *
                            * at this point:
                            * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                            * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                            * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                            * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                            */

                            nErr = FindAccessibleEndPoints(pCG,
                                EndPoint, &nNumEndPoints,
                                BondPos, &nNumBondPos,
                                pBNS, pBD, at,
                                num_atoms, c_group_info,
                                ALT_PATH_MODE_TAUTOM_PT_22_00);

                            if (IS_BNS_ERROR(nErr)) {
                                return nErr;
                            }
                            nErr = 0;

                            if (nNumEndPoints > 0) {
                                if (!nGroupNumber || bDiffGroups > 0) {

                                    num_changes = RegisterEndPoints(pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS);
                                    if (num_changes == -1) {
                                        nErr = CT_TAUCOUNT_ERR;
                                    }
                                    if (num_changes < 0) {
                                        nErr = num_changes;
                                    }

                                    if (nErr)
                                        goto exit_function;
                                    tot_changes += (num_changes>0);
                                }
                                if (nNumBondPos > 0) {
                                    /*  some of the bonds have not been marked as tautomeric yet */
                                    num_changes = SetTautomericBonds(at, nNumBondPos, BondPos);
                                    tot_changes += (num_changes>0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif /********** END PT_22_00 ************/

#if ( TAUT_PT_16_00 == 1 )    /******* BEGIN PT_16_00 ********/
    if (t_group_info->bTautFlags & TG_FLAG_PT_16_00) {
        /*** [#1:1][O;!R:2][N+0z1:3]=[CX3:4]>>[O;!R:2]=[N+0z1:3][CX4:4][#1:1] ***/
        /*** Similar to the previous case of M=Q-ZH >> MH-Q=Z, with M,Z = "C, O" and Q = "N" ***/
        for (i = 0; i < num_atoms; i++) {
            /*  find possible endpoint Z = at[i] */
            if ((endpoint_valence = nGetEndpointInfo_PT_16_00(at, i, &eif1))) { /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
                /*  1st endpoint candidate found. Find centerpoint candidate */
                for (j = 0; j < at[i].valence; j++) {
                    bond_type = (int)at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                    bond_type = ACTUAL_ORDER(pBNS, i, j, bond_type);
#endif
                    centerpoint = (int)at[i].neighbor[j];  /*  a centerpoint candidate */
                    if ((bond_type == BOND_DOUBLE ||
                        bond_type == BOND_ALTERN ||
                        bond_type == BOND_ALT12NS ||
                        bond_type == BOND_TAUTOM)
                        && at[centerpoint].el_number == EL_NUMBER_N
                        && at[centerpoint].valence == 2
                        && at[centerpoint].charge == 0
                        && ALLOWED_EDGE(pBNS, i, j)
                        ) {
                        /*  test a centerpoint candidate. */
                        /*  find all endpoints including at[i] and store them into EndPoint[] */
                        nNumPossibleMobile = 0;
                        nGroupNumber = (AT_NUMB)num_atoms; /*  greater than any tautomeric group number */
                        bDiffGroups = -1;         /*  ignore the first difference */
                        nNumDonor = nNumAcceptor = 0;
                        for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++) {
                            endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                            bond_type = (int)at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                            bond_type = ACTUAL_ORDER(pBNS, centerpoint, k, bond_type);
#endif
                            /* djb-rwth: removing redundant code */
                                bNonTautBond =
                                bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                                bPossiblyEndpoint = 0;
                            if (!ALLOWED_EDGE(pBNS, centerpoint, k)) {
                                continue;
                            }
                            else
                                if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_TAUTOM) {
                                    /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                    bAltBond = (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                                }
                                else
                                    if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE)
                                        bNonTautBond = 1;
                                    else
                                        continue;

                            if (!(endpoint_valence = nGetEndpointInfo_PT_16_00(at, endpoint, &eif1))) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
                                continue; /*  not an endpoint element or can't have mobile groups */
                            if (at[endpoint].el_number == EL_NUMBER_O &&
                                at[endpoint].nNumAtInRingSystem != 1)
                                continue;
                            if (endpoint != i && at[endpoint].el_number == at[i].el_number)
                                continue;
                            /*  save information about the found possible tautomeric endpoint */
                            /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                            nMobile =
                                AddAtom2num(EndPoint[nNumEndPoints].num, at, endpoint, 2); /* fill out */
                            AddAtom2DA(EndPoint[nNumEndPoints].num_DA, at, endpoint, 2);
                            /* --- why is isitopic info missing ? -- see below
                            nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                            nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                            */
                            if (bNonTautBond) {
                                m = (bond_type == BOND_SINGLE && (nMobile || at[endpoint].endpoint));
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (bond_type == BOND_DOUBLE);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            else {
                                /*  tautomeric or alternating bond */
                                m = (0 != at[endpoint].endpoint || eif1.cDonor);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (at[endpoint].endpoint ||
                                    eif1.cNeutralBondsValence > at[endpoint].valence);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            if (!bPossiblyEndpoint)
                                continue;

                            EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                            EndPoint[nNumEndPoints].nEquNumber = 0;
                            EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB)endpoint;
                            if (nGroupNumber != at[endpoint].endpoint) {
                                bDiffGroups++;
                                nGroupNumber = at[endpoint].endpoint;
                            }

                            /*  save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            if (bNonTautBond || bAltBond) {
#endif
                                BondPos[nNumBondPos].nAtomNumber = (AT_NUMB)centerpoint;
                                BondPos[nNumBondPos].neighbor_index = (AT_NUMB)k; /* bond ordering number; used to change bonds to tautomeric only  */
                                nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            }
#endif
                            /*  mobile group is possible if (a) the endpoint has a mobile group or */
                            /*                              (b) the centerpoint is adjacent to another endpoint */
                            nNumPossibleMobile += (nMobile>0 || at[endpoint].endpoint);
                            nNumEndPoints++;
                            /*printf("Found %d %d %d %d\n", centerpoint+1, at[centerpoint].el_number, endpoint+1, at[endpoint].el_number);*/
                        }
                        if (nNumEndPoints > 1 && nNumPossibleMobile && nNumDonor && nNumAcceptor) {
                            /*
                            * a tautomeric group has been found
                            *
                            * at this point:
                            * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                            * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                            * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                            * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                            */
                            nErr = FindAccessibleEndPoints(pCG,
                                EndPoint, &nNumEndPoints,
                                BondPos, &nNumBondPos,
                                pBNS, pBD, at,
                                num_atoms, c_group_info,
                                ALT_PATH_MODE_TAUTOM_PT_16_00);

                            if (IS_BNS_ERROR(nErr)) {
                                return nErr;
                            }
                            nErr = 0;

                            if (nNumEndPoints > 0) {
                                if (!nGroupNumber || bDiffGroups > 0) {

                                    num_changes = RegisterEndPoints(pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS);
                                    if (num_changes == -1) {
                                        nErr = CT_TAUCOUNT_ERR;
                                    }
                                    if (num_changes < 0) {
                                        nErr = num_changes;
                                    }

                                    if (nErr)
                                        goto exit_function;
                                    tot_changes += (num_changes>0);
                                }
                                if (nNumBondPos > 0) {
                                    /*  some of the bonds have not been marked as tautomeric yet */
                                    num_changes = SetTautomericBonds(at, nNumBondPos, BondPos);
                                    tot_changes += (num_changes>0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif /********** END PT_16_00 ************/     

#if ( TAUT_PT_06_00 == 1 ) /******* BEGIN PT_06_00 ********/
    if (t_group_info->bTautFlags & TG_FLAG_PT_06_00) {
        /*** [CX{2-3}z{0-1},N,n,S,s,O,o,Se,Te:1]=[NX2,nX2,CX3,c,P,p:2][N,n,S,O,Se,Te:3][#1:4] >> [#1:4][CX4z{0-1},N,n,S,O,Se,Te:1][NX2,nX2,CX3z{0-1},c,P,p:2]=[N,n,S,s,O,o,Se,Te:3] ***/
        /*** Similar to the previous case of M=Q-ZH >> MH-Q=Z, with M = "C,N,S,O,Se,Te", Q = "N,C,P", and Z = "N,S,O,Se,Te" ***/
        for (i = 0; i < num_atoms; i++) {
            /*  find possible endpoint Z = at[i] */
            if ((endpoint_valence = nGetEndpointInfo_PT_06_00(at, i, &eif1))) { /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
                /*  1st endpoint candidate found. Find centerpoint candidate */
                for (j = 0; j < at[i].valence; j++) {
                    bond_type = (int)at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                    bond_type = ACTUAL_ORDER(pBNS, i, j, bond_type);
#endif
                    centerpoint = (int)at[i].neighbor[j];  /*  a centerpoint candidate */
                    if ((bond_type == BOND_DOUBLE ||
                        bond_type == BOND_ALTERN ||
                        bond_type == BOND_ALT12NS ||
                        bond_type == BOND_TAUTOM) &&
                        (at[centerpoint].el_number == EL_NUMBER_N ||
                            at[centerpoint].el_number == EL_NUMBER_C ||
                            at[centerpoint].el_number == EL_NUMBER_P)
                        && ALLOWED_EDGE(pBNS, i, j)
                        ) {
                        /*  test a centerpoint candidate. */
                        /*  find all endpoints including at[i] and store them into EndPoint[] */
                        nNumPossibleMobile = 0;
                        nGroupNumber = (AT_NUMB)num_atoms; /*  greater than any tautomeric group number */
                        bDiffGroups = -1;         /*  ignore the first difference */
                        nNumDonor = nNumAcceptor = 0;
                        for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++) {
                            endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                            bond_type = (int)at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                            bond_type = ACTUAL_ORDER(pBNS, centerpoint, k, bond_type);
#endif
                            /* djb-rwth: removing redundant code */
                                bNonTautBond =
                                bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                                bPossiblyEndpoint = 0;
                            if (!ALLOWED_EDGE(pBNS, centerpoint, k)) {
                                continue;
                            }
                            else
                                if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_TAUTOM) {
                                    /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                    bAltBond = (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                                }
                                else
                                    if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE)
                                        bNonTautBond = 1;
                                    else
                                        continue;
                            if (!(endpoint_valence = nGetEndpointInfo_PT_06_00(at, endpoint, &eif1))) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
                                continue; /*  not an endpoint element or can't have mobile groups */
                            if (i != endpoint &&
                                at[endpoint].el_number == EL_NUMBER_C &&
                                at[i].el_number == EL_NUMBER_C)
                                continue;
                            /*  save information about the found possible tautomeric endpoint */
                            /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                            nMobile =
                                AddAtom2num(EndPoint[nNumEndPoints].num, at, endpoint, 2); /* fill out */
                            AddAtom2DA(EndPoint[nNumEndPoints].num_DA, at, endpoint, 2);
                            /* --- why is isitopic info missing ? -- see below
                            nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                            nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                            */
                            if (bNonTautBond) {
                                m = (bond_type == BOND_SINGLE && (nMobile || at[endpoint].endpoint));
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (bond_type == BOND_DOUBLE);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            else {
                                /*  tautomeric or alternating bond */
                                m = (0 != at[endpoint].endpoint || eif1.cDonor);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (at[endpoint].endpoint ||
                                    eif1.cNeutralBondsValence > at[endpoint].valence);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            if (!bPossiblyEndpoint)
                                continue;
                            EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                            EndPoint[nNumEndPoints].nEquNumber = 0;
                            EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB)endpoint;
                            if (nGroupNumber != at[endpoint].endpoint) {
                                bDiffGroups++;
                                nGroupNumber = at[endpoint].endpoint;
                            }

                            /*  save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            if (bNonTautBond || bAltBond) {
#endif
                                BondPos[nNumBondPos].nAtomNumber = (AT_NUMB)centerpoint;
                                BondPos[nNumBondPos].neighbor_index = (AT_NUMB)k; /* bond ordering number; used to change bonds to tautomeric only  */
                                nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            }
#endif
                            /*  mobile group is possible if (a) the endpoint has a mobile group or */
                            /*                              (b) the centerpoint is adjacent to another endpoint */
                            nNumPossibleMobile += (nMobile>0 || at[endpoint].endpoint);
                            nNumEndPoints++;
                            /*printf("Found %d %d %d %d\n", centerpoint+1, at[centerpoint].el_number, endpoint+1, at[endpoint].el_number);*/
                        }
                        if (nNumEndPoints > 1 && nNumPossibleMobile && nNumDonor && nNumAcceptor) {
                            /*printf("Real %d\n", nNumEndPoints);*/
                            /*
                            * a tautomeric group has been found
                            *
                            * at this point:
                            * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                            * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                            * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                            * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                            */

                            nErr = FindAccessibleEndPoints(pCG,
                                EndPoint, &nNumEndPoints,
                                BondPos, &nNumBondPos,
                                pBNS, pBD, at,
                                num_atoms, c_group_info,
                                ALT_PATH_MODE_TAUTOM_PT_06_00);

                            if (IS_BNS_ERROR(nErr)) {
                                return nErr;
                            }
                            nErr = 0;

                            if (nNumEndPoints > 0) {
                                if (!nGroupNumber || bDiffGroups > 0) {

                                    num_changes = RegisterEndPoints(pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS);
                                    if (num_changes == -1) {
                                        nErr = CT_TAUCOUNT_ERR;
                                    }
                                    if (num_changes < 0) {
                                        nErr = num_changes;
                                    }

                                    if (nErr)
                                        goto exit_function;
                                    tot_changes += (num_changes>0);
                                }
                                if (nNumBondPos > 0) {
                                    /*  some of the bonds have not been marked as tautomeric yet */
                                    num_changes = SetTautomericBonds(at, nNumBondPos, BondPos);
                                    tot_changes += (num_changes>0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif /********** END PT_06_00 ************/

#if ( TAUT_PT_39_00 == 1 )    /******* BEGIN PT_39_00 ********/
    if (t_group_info->bTautFlags & TG_FLAG_PT_39_00) {
        /*** [CX3,NX2:1]=[NX3+:2]([O-:3])[CX4:4][#1:5]>>[#1:5][CX4,NX3:1][NX3+:2]([O-:3])=[CX3:4] ***/
        /*** Similar to the previous case of M=Q-ZH >> MH-Q=Z, with M,Z = "C, N" and Q = "N+" ***/
        for (i = 0; i < num_atoms; i++) {
            /*  find possible endpoint Z = at[i] */
            if ((endpoint_valence = nGetEndpointInfo_PT_39_00(at, i, &eif1))) { /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
                /*  1st endpoint candidate found. Find centerpoint candidate */
                for (j = 0; j < at[i].valence; j++) {
                    bond_type = (int)at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                    bond_type = ACTUAL_ORDER(pBNS, i, j, bond_type);
#endif
                    centerpoint = (int)at[i].neighbor[j];  /*  a centerpoint candidate */
                    if ((bond_type == BOND_DOUBLE ||
                        bond_type == BOND_ALTERN ||
                        bond_type == BOND_ALT12NS ||
                        bond_type == BOND_TAUTOM)
                        && at[centerpoint].el_number == EL_NUMBER_N
                        && at[centerpoint].valence == 3
                        && ALLOWED_EDGE(pBNS, i, j)
                        ) {
                        int num_O = 0;
                        int num_N = 0;
                        /*  test a centerpoint candidate. */
                        /*  find all endpoints including at[i] and store them into EndPoint[] */
                        nNumPossibleMobile = 0;
                        nGroupNumber = (AT_NUMB)num_atoms; /*  greater than any tautomeric group number */
                        bDiffGroups = -1;         /*  ignore the first difference */
                        nNumDonor = nNumAcceptor = 0;
                        for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++) {
                            endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                            num_O += (at[endpoint].el_number == EL_NUMBER_O);
                            num_N += (at[endpoint].el_number == EL_NUMBER_N);
                            bond_type = (int)at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                            bond_type = ACTUAL_ORDER(pBNS, centerpoint, k, bond_type);
#endif
                            /* djb-rwth: removing redundant code */
                                bNonTautBond =
                                bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                                bPossiblyEndpoint = 0;
                            if (!ALLOWED_EDGE(pBNS, centerpoint, k)) {
                                continue;
                            }
                            else
                                if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_TAUTOM) {
                                    /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                    bAltBond = (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                                }
                                else
                                    if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE)
                                        bNonTautBond = 1;
                                    else
                                        continue;

                            if (!(endpoint_valence = nGetEndpointInfo_PT_39_00(at, endpoint, &eif1))) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
                                continue; /*  not an endpoint element or can't have mobile groups */


                                          /*  save information about the found possible tautomeric endpoint */
                                          /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                            nMobile =
                                AddAtom2num(EndPoint[nNumEndPoints].num, at, endpoint, 2); /* fill out */
                            AddAtom2DA(EndPoint[nNumEndPoints].num_DA, at, endpoint, 2);
                            /* --- why is isitopic info missing ? -- see below
                            nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                            nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                            */
                            if (bNonTautBond) {
                                m = (bond_type == BOND_SINGLE && (nMobile || at[endpoint].endpoint));
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (bond_type == BOND_DOUBLE);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            else {
                                /*  tautomeric or alternating bond */
                                m = (0 != at[endpoint].endpoint || eif1.cDonor);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (at[endpoint].endpoint ||
                                    eif1.cNeutralBondsValence > at[endpoint].valence);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            if (!bPossiblyEndpoint)
                                continue;

                            EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                            EndPoint[nNumEndPoints].nEquNumber = 0;
                            EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB)endpoint;
                            if (nGroupNumber != at[endpoint].endpoint) {
                                bDiffGroups++;
                                nGroupNumber = at[endpoint].endpoint;
                            }

                            /*  save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            if (bNonTautBond || bAltBond) {
#endif
                                BondPos[nNumBondPos].nAtomNumber = (AT_NUMB)centerpoint;
                                BondPos[nNumBondPos].neighbor_index = (AT_NUMB)k; /* bond ordering number; used to change bonds to tautomeric only  */
                                nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            }
#endif
                            /*  mobile group is possible if (a) the endpoint has a mobile group or */
                            /*                              (b) the centerpoint is adjacent to another endpoint */
                            nNumPossibleMobile += (nMobile>0 || at[endpoint].endpoint);
                            nNumEndPoints++;
                            /*printf("Found %d %d %d %d\n", centerpoint+1, at[centerpoint].el_number, endpoint+1, at[endpoint].el_number);*/
                        }
                        if (nNumEndPoints > 1 && nNumPossibleMobile && nNumDonor && nNumAcceptor && num_O == 1 && num_N < 2) {
                            /*
                            * a tautomeric group has been found
                            *
                            * at this point:
                            * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                            * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                            * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                            * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                            */
                            nErr = FindAccessibleEndPoints(pCG,
                                EndPoint, &nNumEndPoints,
                                BondPos, &nNumBondPos,
                                pBNS, pBD, at,
                                num_atoms, c_group_info,
                                ALT_PATH_MODE_TAUTOM_PT_39_00);

                            if (IS_BNS_ERROR(nErr)) {
                                return nErr;
                            }
                            nErr = 0;

                            if (nNumEndPoints > 0) {
                                if (!nGroupNumber || bDiffGroups > 0) {

                                    num_changes = RegisterEndPoints(pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS);
                                    if (num_changes == -1) {
                                        nErr = CT_TAUCOUNT_ERR;
                                    }
                                    if (num_changes < 0) {
                                        nErr = num_changes;
                                    }

                                    if (nErr)
                                        goto exit_function;
                                    tot_changes += (num_changes>0);
                                }
                                if (nNumBondPos > 0) {
                                    /*  some of the bonds have not been marked as tautomeric yet */
                                    num_changes = SetTautomericBonds(at, nNumBondPos, BondPos);
                                    tot_changes += (num_changes>0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif /********** END PT_39_00 ************/

#if ( TAUT_PT_13_00 == 1 ) /******* BEGIN PT_13_00 ********/
    if (t_group_info->bTautFlags & TG_FLAG_PT_13_00) {
        /*** [O,S,Se,Te;X1:1]=[C:2]=[C:3][#1:4]>>[#1:4][O,S,Se,Te;X2:1][C:2]#[C:3] ***/
        /*** Similar to the previous case of M=Q-ZH >> MH-Q=Z, with M = "S,O,Se,Te", Q = "C", and Z = "C" ***/
        for (i = 0; i < num_atoms; i++) {
            /*  find possible endpoint Z = at[i] */
            if ((endpoint_valence = nGetEndpointInfo_PT_13_00(at, i, &eif1))) { /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
                /*  1st endpoint candidate found. Find centerpoint candidate */
                for (j = 0; j < at[i].valence; j++) {
                    bond_type = (int)at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                    bond_type = ACTUAL_ORDER(pBNS, i, j, bond_type);
#endif
                    centerpoint = (int)at[i].neighbor[j];  /*  a centerpoint candidate */
                    if ((bond_type == BOND_DOUBLE ||
                        bond_type == BOND_SINGLE ||
                        bond_type == BOND_ALTERN ||
                        bond_type == BOND_ALT12NS ||
                        bond_type == BOND_ALT_13 ||
                        bond_type == BOND_TAUTOM) &&
                        at[centerpoint].el_number == EL_NUMBER_C &&
                        at[centerpoint].valence == 2 &&
                        ALLOWED_EDGE(pBNS, i, j)
                        ) {
                        /*  test a centerpoint candidate. */
                        /*  find all endpoints including at[i] and store them into EndPoint[] */
                        int num_O = 0;
                        int num_C = 0;
                        nNumPossibleMobile = 0;
                        nGroupNumber = (AT_NUMB)num_atoms; /*  greater than any tautomeric group number */
                        bDiffGroups = -1;         /*  ignore the first difference */
                        nNumDonor = nNumAcceptor = 0;
                        for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++) {
                            endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                            bond_type = (int)at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                            bond_type = ACTUAL_ORDER(pBNS, centerpoint, k, bond_type);
#endif		
                            /* djb-rwth: removing redundant code */
                                bNonTautBond =
                                bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                                bPossiblyEndpoint = 0;
                            if (!ALLOWED_EDGE(pBNS, centerpoint, k)) {
                                continue;
                            }
                            else
                                if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_ALT_13 || bond_type == BOND_TAUTOM) {
                                    /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                    bAltBond = (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_ALT_13); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                                }
                                else
                                    if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE || bond_type == BOND_TRIPLE)
                                        bNonTautBond = 1;
                                    else
                                        continue;
                            if (!(endpoint_valence = nGetEndpointInfo_PT_13_00(at, endpoint, &eif1)))
                                continue; /*  not an endpoint element or can't have mobile groups */

                                          /*  save information about the found possible tautomeric endpoint */
                                          /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                            nMobile =
                                AddAtom2num(EndPoint[nNumEndPoints].num, at, endpoint, 2); /* fill out */
                            AddAtom2DA(EndPoint[nNumEndPoints].num_DA, at, endpoint, 2);
                            /* --- why is isitopic info missing ? -- see below
                            nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                            nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                            */
                            if (bNonTautBond) {
                                m = (nMobile || at[endpoint].endpoint);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (nMobile == 0);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            else {
                                /*  tautomeric or alternating bond */
                                m = (eif1.cDonor != 0);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (eif1.cNeutralBondsValence > at[endpoint].valence);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }


                            if (!bPossiblyEndpoint)
                                continue;

                            num_O += (endpoint_valence == 2);
                            num_C += (endpoint_valence == 4);
                            EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                            EndPoint[nNumEndPoints].nEquNumber = 0;
                            EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB)endpoint;
                            if (nGroupNumber != at[endpoint].endpoint) {
                                bDiffGroups++;
                                nGroupNumber = at[endpoint].endpoint;
                            }

                            /*  save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            if (bNonTautBond || bAltBond) {
#endif
                                BondPos[nNumBondPos].nAtomNumber = (AT_NUMB)centerpoint;
                                BondPos[nNumBondPos].neighbor_index = (AT_NUMB)k; /* bond ordering number; used to change bonds to tautomeric only  */
                                nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            }
#endif
                            /*  mobile group is possible if (a) the endpoint has a mobile group or */
                            /*                              (b) the centerpoint is adjacent to another endpoint */
                            nNumPossibleMobile += (nMobile>0);
                            nNumEndPoints++;
                            /*printf("Found %d %d %d %d\n", centerpoint+1, at[centerpoint].el_number, endpoint+1, at[endpoint].el_number);*/
                        }
                        if (nNumEndPoints > 1 && nNumPossibleMobile && nNumDonor && nNumAcceptor && num_O == 1 && num_C == 1) {
                            /*printf("Real %d\n", nNumEndPoints);*/
                            /*
                            * a tautomeric group has been found
                            *
                            * at this point:
                            * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                            * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                            * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                            * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                            */

                            nErr = FindAccessibleEndPoints(pCG,
                                EndPoint, &nNumEndPoints,
                                BondPos, &nNumBondPos,
                                pBNS, pBD, at,
                                num_atoms, c_group_info,
                                ALT_PATH_MODE_TAUTOM_PT_13_00);

                            if (IS_BNS_ERROR(nErr)) {
                                return nErr;
                            }
                            nErr = 0;

                            if (nNumEndPoints > 0) {
                                if (!nGroupNumber || bDiffGroups > 0) {

                                    num_changes = RegisterEndPoints(pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS);
                                    if (num_changes == -1) {
                                        nErr = CT_TAUCOUNT_ERR;
                                    }
                                    if (num_changes < 0) {
                                        nErr = num_changes;
                                    }

                                    if (nErr)
                                        goto exit_function;
                                    tot_changes += (num_changes>0);
                                }
                                if (nNumBondPos > 0) {
                                    /*  some of the bonds have not been marked as tautomeric yet */
                                    num_changes = SetTautomericBonds(at, nNumBondPos, BondPos);
                                    tot_changes += (num_changes>0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif /********** END PT_13_00 ************/

#if ( TAUT_PT_18_00 == 1 ) /******* BEGIN PT_18_00 ********/
    if (t_group_info->bTautFlags & TG_FLAG_PT_18_00) {
        /*** [#1:1][O:2][C:3]#[N:4]>>[O:2]=[C:3]=[N:4][#1:1] ***/
        /*** Similar to the previous case of M=Q-ZH >> MH-Q=Z, with M = "O", Q = "C", and Z = "N" ***/
        for (i = 0; i < num_atoms; i++) {
            /*  find possible endpoint Z = at[i] */
            if ((endpoint_valence = nGetEndpointInfo_PT_18_00(at, i, &eif1))) { /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
                /*  1st endpoint candidate found. Find centerpoint candidate */
                for (j = 0; j < at[i].valence; j++) {
                    bond_type = (int)at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                    bond_type = ACTUAL_ORDER(pBNS, i, j, bond_type);
#endif
                    centerpoint = (int)at[i].neighbor[j];  /*  a centerpoint candidate */
                                                           /*printf("Centerpoint: %d\n", centerpoint+1);*/
                    if ((bond_type == BOND_DOUBLE ||
                        bond_type == BOND_SINGLE ||
                        bond_type == BOND_ALTERN ||
                        bond_type == BOND_ALT12NS ||
                        bond_type == BOND_ALT_13 ||
                        bond_type == BOND_TAUTOM) &&
                        at[centerpoint].el_number == EL_NUMBER_C &&
                        at[centerpoint].valence == 2 &&
                        ALLOWED_EDGE(pBNS, i, j)
                        ) {
                        /*  test a centerpoint candidate. */
                        /*  find all endpoints including at[i] and store them into EndPoint[] */
                        int num_O = 0;
                        int num_N = 0;
                        nNumPossibleMobile = 0;
                        nGroupNumber = (AT_NUMB)num_atoms; /*  greater than any tautomeric group number */
                        bDiffGroups = -1;         /*  ignore the first difference */
                        nNumDonor = nNumAcceptor = 0;
                        for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++) {
                            endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                                                                    /*printf("Endpoint: %d\n", endpoint+1);*/
                            bond_type = (int)at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                            bond_type = ACTUAL_ORDER(pBNS, centerpoint, k, bond_type);
#endif

                            /* djb-rwth: removing redundant code */
                                bNonTautBond =
                                bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                                bPossiblyEndpoint = 0;
                            if (!ALLOWED_EDGE(pBNS, centerpoint, k)) {
                                continue;
                            }
                            else
                                if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_ALT_13 || bond_type == BOND_TAUTOM) {
                                    /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                    bAltBond = (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_ALT_13); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                                }
                                else
                                    if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE || bond_type == BOND_TRIPLE)
                                        bNonTautBond = 1;
                                    else
                                        continue;
                            if (!(endpoint_valence = nGetEndpointInfo_PT_18_00(at, endpoint, &eif1)))
                                continue; /*  not an endpoint element or can't have mobile groups */

                                          /*  save information about the found possible tautomeric endpoint */
                                          /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                            nMobile = eif1.cMobile;
                            AddAtom2num(EndPoint[nNumEndPoints].num, at, endpoint, 2); /* fill out */
                            AddAtom2DA(EndPoint[nNumEndPoints].num_DA, at, endpoint, 2);
                            /* --- why is isitopic info missing ? -- see below
                            nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                            nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                            */

                            if (bond_type == BOND_DOUBLE && endpoint_valence == 3 && nMobile == 0)
                                continue;

                            if (bNonTautBond) {
                                /*printf("AAA %d\n", nMobile);*/
                                m = (nMobile || at[endpoint].endpoint);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (nMobile == 0);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            else {
                                /*  tautomeric or alternating bond */
                                /*printf("BBB %d %d %d\n", eif1.cDonor, eif1.cNeutralBondsValence, at[endpoint].valence);*/
                                m = (eif1.cDonor != 0);
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = (eif1.cAcceptor != 0);
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }


                            if (!bPossiblyEndpoint)
                                continue;

                            num_O += (endpoint_valence == 2);
                            num_N += (endpoint_valence == 3);
                            EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                            EndPoint[nNumEndPoints].nEquNumber = 0;
                            EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB)endpoint;
                            if (nGroupNumber != at[endpoint].endpoint) {
                                bDiffGroups++;
                                nGroupNumber = at[endpoint].endpoint;
                            }

                            /*  save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            if (bNonTautBond || bAltBond) {
#endif
                                BondPos[nNumBondPos].nAtomNumber = (AT_NUMB)centerpoint;
                                BondPos[nNumBondPos].neighbor_index = (AT_NUMB)k; /* bond ordering number; used to change bonds to tautomeric only  */
                                nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            }
#endif
                            /*  mobile group is possible if (a) the endpoint has a mobile group or */
                            /*                              (b) the centerpoint is adjacent to another endpoint */
                            nNumPossibleMobile += (nMobile>0);
                            nNumEndPoints++;
                            /*printf("Found %d %d %d %d\n", centerpoint+1, at[centerpoint].el_number, endpoint+1, at[endpoint].el_number);*/
                        }
                        if (nNumEndPoints > 1 && nNumPossibleMobile && nNumDonor && nNumAcceptor && num_O == 1 && num_N == 1) {
                            /*printf("Real %d\n", nNumEndPoints);*/
                            /*
                            * a tautomeric group has been found
                            *
                            * at this point:
                            * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                            * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                            * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                            * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                            */

                            nErr = FindAccessibleEndPoints(pCG,
                                EndPoint, &nNumEndPoints,
                                BondPos, &nNumBondPos,
                                pBNS, pBD, at,
                                num_atoms, c_group_info,
                                ALT_PATH_MODE_TAUTOM_PT_18_00);

                            if (IS_BNS_ERROR(nErr)) {
                                return nErr;
                            }
                            nErr = 0;

                            if (nNumEndPoints > 0) {
                                if (!nGroupNumber || bDiffGroups > 0) {

                                    num_changes = RegisterEndPoints(pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS);
                                    if (num_changes == -1) {
                                        nErr = CT_TAUCOUNT_ERR;
                                    }
                                    if (num_changes < 0) {
                                        nErr = num_changes;
                                    }

                                    if (nErr)
                                        goto exit_function;
                                    tot_changes += (num_changes>0);
                                }
                                if (nNumBondPos > 0) {
                                    /*  some of the bonds have not been marked as tautomeric yet */
                                    num_changes = SetTautomericBonds(at, nNumBondPos, BondPos);
                                    tot_changes += (num_changes>0);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif /********** END PT_18_00 ************/    


#if ( KETO_ENOL_TAUT == 1 )  /***** post v.1 feature *****/
    if (t_group_info->bTautFlags & TG_FLAG_KETO_ENOL_TAUT)
    {
        /* 1,3 keto-enol tautomerism */
        for (i = 0; i < num_atoms; i++)
        {
            /*  Find possible endpoint Z = at[i] */
            if ((endpoint_valence = nGetEndpointInfo_KET( at, i, &eif1 ))) /* djb-rwth: addressing LLVM warning; ignoring LLVM warning: variable used to store function return value */
            {
                /*  1st endpoint candidate found. Find centerpoint candidate */
                for (j = 0; j < at[i].valence; j++)
                {
                    bond_type = (int) at[i].bond_type[j] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                    bond_type = ACTUAL_ORDER( pBNS, i, j, bond_type );
#endif
                    centerpoint = (int) at[i].neighbor[j];  /*  a centerpoint candidate */
                    if (( bond_type == BOND_DOUBLE ||
                          bond_type == BOND_ALTERN ||
                          bond_type == BOND_ALT12NS ||
                          bond_type == BOND_TAUTOM ) &&
                         is_centerpoint_elem_KET( at[centerpoint].el_number ) &&
                         !at[centerpoint].charge && !at[centerpoint].radical &&
                         /* only normal carbon is allowed */
                         4 == at[centerpoint].chem_bonds_valence + at[centerpoint].num_H
                         && ALLOWED_EDGE( pBNS, i, j ))
                    {
                        int num_O = 0;
                        int num_C = 0;
                        /*  Test a centerpoint candidate. */
                        /*  find all endpoints including at[i] and store them into EndPoint[] */
                        nNumPossibleMobile = 0;
                        nGroupNumber = (AT_NUMB) num_atoms; /*  greater than any tautomeric group number */
                        bDiffGroups = -1;         /*  ignore the first difference */
                        nNumDonor = nNumAcceptor = 0;
                        for (k = 0, nNumEndPoints = 0, nNumBondPos = 0; k < at[centerpoint].valence; k++)
                        {
                            endpoint = at[centerpoint].neighbor[k]; /*  endpoint candidate */
                            bond_type = (int) at[centerpoint].bond_type[k] & ~BOND_MARK_ALL;
#if ( FIX_BOND23_IN_TAUT == 1 )
                            bond_type = ACTUAL_ORDER( pBNS, centerpoint, k, bond_type );
#endif
                            /* djb-rwth: removing redundant code */
                                bNonTautBond =
                                bAltBond = /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
                                bPossiblyEndpoint = 0;
                            if (!ALLOWED_EDGE( pBNS, centerpoint, k ))
                            {
                                continue;
                            }
                            else
                            {
                                if (bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS || bond_type == BOND_TAUTOM)
                                {
                                    /* djb-rwth: removing redundant code */
#if ( REPLACE_ALT_WITH_TAUT == 1 )
                                    bAltBond = ( bond_type == BOND_ALTERN || bond_type == BOND_ALT12NS ); /* djb-rwth: ignoring LLVM warning: possible presence of global variables */
#endif
                                }
                                else
                                {
                                    if (bond_type == BOND_SINGLE || bond_type == BOND_DOUBLE)
                                    {
                                        bNonTautBond = 1;
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                            }

                            if (!( endpoint_valence = nGetEndpointInfo_KET( at, endpoint, &eif2 ) ))
                            {
                                continue;
                            }
                            /*
                            if ( 3 != eif1.cKetoEnolCode + eif2.cKetoEnolCode && endpoint != i )
                            continue;
                            */
                            /*  save information about the found possible tautomeric endpoint */
                            /*  2 = T_NUM_NO_ISOTOPIC non-isotopic values */
                            nMobile = AddAtom2num( EndPoint[nNumEndPoints].num, at, endpoint, 2 ); /* fill out */
                            AddAtom2DA( EndPoint[nNumEndPoints].num_DA, at, endpoint, 2 );
                            /* --- why is isitopic info missing ? -- see below
                            nMobile  = EndPoint[nNumEndPoints].num[1] = (at[endpoint].charge == -1);
                            nMobile  = EndPoint[nNumEndPoints].num[0] = at[endpoint].num_H + nMobile;
                            */
                            if (bNonTautBond)
                            {
                                m = ( bond_type == BOND_SINGLE && ( nMobile || at[endpoint].endpoint ) );
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = ( bond_type == BOND_DOUBLE );
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            else
                            {
                                /*  Tautomeric or alternating bond */
                                m = ( 0 != at[endpoint].endpoint || eif1.cDonor );
                                nNumDonor += m;
                                bPossiblyEndpoint += m;
                                m = ( at[endpoint].endpoint ||
                                      eif1.cNeutralBondsValence > at[endpoint].valence );
                                nNumAcceptor += m;
                                bPossiblyEndpoint += m;
                            }
                            if (!bPossiblyEndpoint)
                            {
                                continue;
                            }

                            num_O += ( endpoint_valence == 2 );
                            num_C += ( endpoint_valence == 4 );

                            EndPoint[nNumEndPoints].nGroupNumber = at[endpoint].endpoint; /* =0 if it is an endpoint for the 1st time */
                            EndPoint[nNumEndPoints].nEquNumber = 0;
                            EndPoint[nNumEndPoints].nAtomNumber = (AT_NUMB) endpoint;
                            if (nGroupNumber != at[endpoint].endpoint)
                            {
                                bDiffGroups++;
                                nGroupNumber = at[endpoint].endpoint;
                            }

                            /*  Save positions of all, not only possibly tautomeric bonds */
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            if (bNonTautBond || bAltBond)
                            {
#endif
                                BondPos[nNumBondPos].nAtomNumber = (AT_NUMB) centerpoint;
                                BondPos[nNumBondPos].neighbor_index = (AT_NUMB) k; /* bond ordering number; used to change bonds to tautomeric only  */
                                nNumBondPos++;
#if ( REPLACE_ALT_WITH_TAUT != 1 )
                            }
#endif
                            /*  Mobile group is possible if (a) the endpoint has a mobile group or */
                            /*                              (b) the centerpoint is adjacent to another endpoint */
                            nNumPossibleMobile += ( nMobile > 0 || at[endpoint].endpoint );
                            nNumEndPoints++;
                        }
                        if (nNumEndPoints > 1 && nNumPossibleMobile &&
                             nNumDonor && nNumAcceptor &&
                             num_O == 1 && num_C)
                        {
                            /*
                            * A tautomeric group has been found
                            *
                            * at this point:
                            * nGroupNumber = 0 if all endpoints belong to a newly discovered tautomeric group
                            * bDiffGroups  > 0 if at least 2 tautomeric groups are to be merged (one of them can be new)
                            * case (nGroupNumber != 0 && bDiffGroups = 0 ) ignored because all endpoints belong to the same known t-group
                            * case (nGroupNumber != 0 && bDiffGroups < 0 ) cannot happen
                            */

                            nErr = FindAccessibleEndPoints( pCG, EndPoint, &nNumEndPoints, BondPos, &nNumBondPos,
                                                            pBNS, pBD, at, num_atoms, c_group_info, ALT_PATH_MODE_TAUTOM_KET );

                            if (IS_BNS_ERROR( nErr ))
                            {
                                return nErr;
                            }
                            nErr = 0;

                            if (nNumEndPoints > 0)
                            {
                                if (!nGroupNumber || bDiffGroups > 0)
                                {

                                    num_changes = RegisterEndPoints( pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS );
                                    if (num_changes == -1)
                                    {
                                        nErr = CT_TAUCOUNT_ERR;
                                    }
                                    if (num_changes < 0)
                                    {
                                        nErr = num_changes;
                                    }
                                    if (nErr)
                                    {
                                        goto exit_function;
                                    }
                                    tot_changes += ( num_changes > 0 );
                                }
                                if (nNumBondPos > 0)
                                {
                                    /*  Some of the bonds have not been marked as tautomeric yet */
                                    num_changes = SetTautomericBonds( at, nNumBondPos, BondPos );
                                    tot_changes += ( num_changes > 0 );
                                }
                            }
                        }
                    }
                }
            }
        }
    }
#endif  /* KETO_ENOL_TAUT */

#if ( TAUT_OTHER == 1 ) /* { */
    if (!tot_changes)
    {
#define MAX_ALT_PATH_LEN 8
        int nMaxLenDfsPath = MAX_ALT_PATH_LEN;
        int i1, i2;
        AT_RANK *nDfsPathPos = (AT_RANK  *) inchi_calloc( num_atoms, sizeof( nDfsPathPos[0] ) );
        DFS_PATH DfsPath[MAX_ALT_PATH_LEN];
        int      ret;

        if (!nDfsPathPos) /* djb-rwth: removing redundant code as address of DfsPath will always evaluate to true */
        {
            tot_changes = CT_OUT_OF_RAM;  /*   <BRKPT> */
            goto free_memory;
        }

#if ( TAUT_15_NON_RING      == 1 ) /***** post v.1 feature *****/
        if (t_group_info->bTautFlags & TG_FLAG_1_5_TAUT)
        {
            /*  1,5 tautomerism; one of the endpoints should no be on a ring  */
            /*
            O                OH                 O
            ||               |                  ||
            A--pos-          A--pos-            A--pos-
            /   sib-        //   sib-     ?     /   sib-
            C    ly          C    ly            CH   ly
            \\   a     <-->   \   a       <-->   \   a
            B--ring          B--ring            B--ring
            |                ||                 ||
            NH               N                  N

            Note: few recent modifications now allow the terminal N be in a ring, too
            */
            for (i1 = 0; i1 < num_atoms; i1++)
            {
                /*  Find possible endpoint Z = at[i1] */
                if (!( endpoint_valence = nGetEndpointInfo( at, i1, &eif1 ) ) /*||
                                                                              at[i1].nNumAtInRingSystem > 1*/) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
                {
                    continue; /*  not a possibly endpoint */
                }

                if (1)
                {
                    nNumEndPoints = 0;
                    nNumBondPos = 0;

                    ret = nGet15TautInAltPath( pCG, at, i1, nDfsPathPos,
                                               DfsPath, nMaxLenDfsPath,
                                               EndPoint, sizeof( EndPoint ) / sizeof( EndPoint[0] ),
                                               BondPos, sizeof( BondPos ) / sizeof( BondPos[0] ),
                                               &nNumEndPoints, &nNumBondPos,
                                               pBNS, pBD, num_atoms );

                    if (ret > 0)
                    {
                        if (nNumEndPoints)
                        {
                            num_changes = RegisterEndPoints( pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS );
                            if (num_changes == -1)
                            {
                                nErr = CT_TAUCOUNT_ERR;
                            }
                            if (num_changes < 0)
                            {
                                nErr = num_changes;
                            }
                            if (nErr)
                            {
                                goto free_memory;
                            }
                            tot_changes += ( num_changes > 0 );
                        }
                        if (nNumBondPos)
                        {
                            tot_changes += ( 0 < SetTautomericBonds( at, nNumBondPos, BondPos ) );
                        }
                    }
                    else
                    {
                        if (IS_BNS_ERROR( ret ))
                        {
                            nErr = ret;
                            goto free_memory;
                        }
                    }
                }
            }
        }
#endif

#if ( TAUT_4PYRIDINOL_RINGS == 1 )
        /*  6-member rings */
        /*
        O              OH             OH
        ||             |              |
        /  \          //  \           /  \\
        ||   ||  <-->  |    ||  <-->  ||   |
        \  /          \\  /           \  //
        NH             N              N
        */
        for (i1 = 0; i1 < num_atoms; i1++)
        {
            /*  Find possible endpoint Z = at[i1] */
            if (3 != ( endpoint_valence = nGetEndpointInfo( at, i1, &eif1 ) ) ||
                 2 != at[i1].valence) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
            {
                continue; /*  not a nitrogen atom or a wrong valence */
            }

            if (at[i1].nNumAtInRingSystem >= 6)
            {
                nNumEndPoints = 0;
                nNumBondPos = 0;

                ret = nGet15TautIn6MembAltRing( pCG, at, i1, nDfsPathPos,
                                                DfsPath, nMaxLenDfsPath, EndPoint,
                                                sizeof( EndPoint ) / sizeof( EndPoint[0] ),
                                                BondPos,
                                                sizeof( BondPos ) / sizeof( BondPos[0] ),
                                                &nNumEndPoints, &nNumBondPos,
                                                pBNS, pBD, num_atoms );
                if (ret > 0)
                {
                    if (nNumEndPoints)
                    {
                        num_changes = RegisterEndPoints( pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS );
                        if (num_changes == -1)
                        {
                            nErr = CT_TAUCOUNT_ERR;
                        }
                        if (num_changes < 0)
                        {
                            nErr = num_changes;
                        }
                        if (nErr)
                        {
                            goto free_memory;
                        }
                        tot_changes += ( num_changes > 0 );
                    }
                    if (nNumBondPos)
                    {
                        tot_changes += ( 0 < SetTautomericBonds( at, nNumBondPos, BondPos ) );
                    }
                }
                else
                {
                    if (IS_BNS_ERROR( ret ))
                    {
                        nErr = ret;
                        goto free_memory;
                    }
                }
            }
        }
#endif /* TAUT_4PYRIDINOL_RINGS */

#if ( TAUT_PYRAZOLE_RINGS == 1 )
        /* 5-member rings:

        Z               Z
        /  \\           //  \
        X     Y  <-->   X     Y
        \\   /          \    //
        N--NH           HN--N

        ^             ^
        search for these NH
        */
        /*  5-member rings (pyrazole derivatives): look for the neighboring N */
        for (i1 = 0; i1 < num_atoms; i1++)
        {
            if (2 == at[i1].valence &&
                 at[i1].nNumAtInRingSystem >= 5 &&
                 3 == ( endpoint_valence = nGetEndpointInfo( at, i1, &eif1 ) )
                 ) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
            {
                nMobile = at[i1].num_H + ( at[i1].charge == -1 );
                for (j = 0; j < at[i1].valence; j++)
                {
                    int nMobile2, endpoint_valence2;
                    i2 = at[i1].neighbor[j];

                    /*  may be important */
                    if (i2 >= i1)
                    {
                        continue; /*  do not try same pair 2 times */
                    }

                    if (at[i2].nRingSystem != at[i1].nRingSystem)
                    {
                        continue;
                    }

                    bond_type = ( at[i1].bond_type[j] & ~BOND_MARK_ALL );
                    if ((bond_type != BOND_SINGLE &&
                         bond_type != BOND_TAUTOM &&
                         bond_type != BOND_ALT12NS &&
                         bond_type != BOND_ALTERN) ||  /* added 1-15-2002 */
                         2 != at[i2].valence ||
                         3 != ( endpoint_valence2 = nGetEndpointInfo( at, i2, &eif2 ) )) /* djb-rwth: addressing LLVM warning; variable used to store function return value */
                    {
                        continue; /*  not a nitrogen atom or a wrong valence or not a single bond */
                    }
                    nMobile2 = at[i2].num_H + ( at[i2].charge == -1 );  /*  number of mobile groups */
#if ( TAUT_IGNORE_EQL_ENDPOINTS == 1 )
                    if (at[i1].endpoint && at[i1].endpoint == at[i2].endpoint)
                    {
                        continue; /* atoms already belong to the same t-group */
                    }
#endif
                    if (!at[i1].endpoint && !at[i2].endpoint && 1 != nMobile + nMobile2)
                    {
                        continue;
                    }

                    ret = nGet12TautIn5MembAltRing( pCG, at, i1, j, nDfsPathPos,
                                                    DfsPath, nMaxLenDfsPath,
                                                    EndPoint,
                                                    sizeof( EndPoint ) / sizeof( EndPoint[0] ),
                                                    BondPos,
                                                    sizeof( BondPos ) / sizeof( BondPos[0] ),
                                                    &nNumEndPoints, &nNumBondPos,
                                                    pBNS, pBD, num_atoms );
                    if (ret > 0)
                    {
                        if (nNumEndPoints)
                        {
                            num_changes = RegisterEndPoints( pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS );
                            if (num_changes == -1)
                            {
                                nErr = CT_TAUCOUNT_ERR;
                            }
                            if (num_changes < 0)
                            {
                                nErr = num_changes;
                            }
                            if (nErr)
                                goto free_memory;
                            tot_changes += ( num_changes > 0 );
                        }
                        if (nNumBondPos)
                        {
                            tot_changes += ( 0 < SetTautomericBonds( at, nNumBondPos, BondPos ) );
                        }
                    }
                    else
                    {
                        if (IS_BNS_ERROR( ret ))
                        {
                            nErr = ret;
                            goto free_memory;
                        }
                    }
                }
            }
        }
#endif /* TAUT_PYRAZOLE_RINGS */

#if ( TAUT_TROPOLONE_7 == 1 || TAUT_TROPOLONE_5 == 1 ) /* { */
        /********************************************************
        *                                         A  B
        *                                         | ||
        * 7-member rings (tropolones): look for M=Q--R--ZH,
        *                                       ^ ^  ^  ^
        *                               endpoint1 i1 i2 endpoint2
        * where A-Q-R=B belong to a 7-member alt. (except Q-R bond) ring: ..=A-(Q-R)=B-..
        * Bond Q-R should be single or tautomeric or alternating
        * M=Q and R-ZH should be chain (non-ring) bonds
        * Same for 5-member rings
        */
        for (i1 = 0; i1 < num_atoms; i1++)
        {
            if (at[i1].nNumAtInRingSystem >=
#if ( TAUT_TROPOLONE_5 == 1 )
                 5
#else
                 7
#endif
                 &&
                 bIsCenterPointStrict( at, i1 ) &&
#if ( TAUT_RINGS_ATTACH_CHAIN == 1 )
                 at[i1].bCutVertex &&
#endif
                 at[i1].valence == 3 && !at[i1].endpoint)
            {
                int nMobile1, endpoint1, endpoint1_valence, bond_type1;
                int nMobile2, endpoint2, endpoint2_valence, bond_type2;
                for (j = 0; j < at[i1].valence; j++)
                {
                    i2 = at[i1].neighbor[j];
                    /* may be important
                    if ( i2 > i1 )
                    continue;
                    do not try same pair 2 times
                    */
                    if (at[i2].nRingSystem != at[i1].nRingSystem ||
                         !bIsCenterPointStrict( at, i2 ) ||
#if ( TAUT_RINGS_ATTACH_CHAIN == 1 )
                         !at[i2].bCutVertex ||
#endif
                         at[i2].valence != 3 || at[i2].endpoint)
                    {
                        continue;
                    }
                    bond_type = ( at[i1].bond_type[j] & ~BOND_MARK_ALL );
                    if (bond_type != BOND_SINGLE &&
                         bond_type != BOND_TAUTOM &&
                         bond_type != BOND_ALT12NS &&
                         bond_type != BOND_ALTERN)
                    {
                        continue; /*  not a single bond between Q-R */
                    }

                    /*  Find endpoints */
                    for (k = 0; k < at[i1].valence; k++)
                    {
                        endpoint1 = at[i1].neighbor[k];
                        if (endpoint1 == i2)
                        {
                            continue; /*  j == k */
                        }
                        if (!( endpoint1_valence = nGetEndpointInfo( at, endpoint1, &eif1 ) ))
                        {
                            continue; /*  not an endpoint1 element or can't have mobile groups */
                        }
#if ( TAUT_RINGS_ATTACH_CHAIN == 1 )
                        if (at[endpoint1].nRingSystem == at[i1].nRingSystem)
                        {
                            continue;
                        }
#endif
                        nMobile1 = at[endpoint1].num_H + ( at[endpoint1].charge == -1 );  /*  number of mobile groups */
                        if (nMobile1 + at[endpoint1].chem_bonds_valence != endpoint1_valence)
                            continue; /*  abnormal endpoint1 valence; ignore. */
                        bond_type1 = ( at[i1].bond_type[k] & ~BOND_MARK_ALL );

                        if (bond_type1 != BOND_SINGLE &&
                             bond_type1 != BOND_DOUBLE &&
                             bond_type1 != BOND_TAUTOM &&
                             bond_type1 != BOND_ALT12NS &&
                             bond_type1 != BOND_ALTERN)
                        {
                            continue;
                        }
                        for (m = 0; m < at[i2].valence; m++)
                        {
                            endpoint2 = at[i2].neighbor[m];
                            if (endpoint2 == i1)
                            {
                                continue;
                            }
                            if (!( endpoint2_valence = nGetEndpointInfo( at, endpoint2, &eif2 ) )) /* djb-rwth: ignoring LLVM warning: variable used to store function return value */
                            {
                                continue; /*  not an endpoint2 element or can't have mobile groups */
                            }
#if ( TAUT_RINGS_ATTACH_CHAIN == 1 )
                            if (at[endpoint2].nRingSystem == at[i2].nRingSystem)
                            {
                                continue;
                            }
#endif
                            nMobile2 = at[endpoint2].num_H + ( at[endpoint2].charge == -1 );  /*  number of mobile groups */
                            bond_type2 = ( at[i2].bond_type[m] & ~BOND_MARK_ALL );

                            if (bond_type2 != BOND_SINGLE &&
                                 bond_type2 != BOND_DOUBLE &&
                                 bond_type2 != BOND_TAUTOM &&
                                 bond_type2 != BOND_ALT12NS &&
                                 bond_type2 != BOND_ALTERN)
                            {
                                continue;
                            }

                            /*  Final test for possible tautomerism */
                            nMobile = 0;

                            if (ALLOWED_EDGE( pBNS, i1, k ) && ALLOWED_EDGE( pBNS, i2, m ))
                            {
                                /*  Can mobile group move from 1 to 2? */
                                nMobile += ( at[endpoint1].endpoint || nMobile1 ) &&  /*  from endpoint1 */
                                    ( bond_type1 != BOND_DOUBLE ) &&

                                    ( at[endpoint2].endpoint ||          /*  to endpoint2 */
                                      eif2.cNeutralBondsValence > at[endpoint2].valence ) &&
                                      ( bond_type2 != BOND_SINGLE );

                                /*  Can mobile group move from 2 to 1? */
                                nMobile += ( at[endpoint2].endpoint || nMobile2 ) &&  /*  from endpoint2 */
                                    ( bond_type2 != BOND_DOUBLE ) && /*changed from BOND_SINGLE 2004-02-26 */

                                    ( at[endpoint1].endpoint ||          /*  to endpoint1 */
                                      eif1.cNeutralBondsValence > at[endpoint1].valence ) &&
                                      ( bond_type1 != BOND_SINGLE );
                            }
                            if (!nMobile)
                            {
                                continue;
                            }

                            if (bond_type1 == bond_type2 &&
                                ( bond_type1 == BOND_SINGLE || bond_type1 == BOND_DOUBLE ))
                            {
                                continue;
                            }

                            /* -- old --
                            if ( !at[endpoint1].endpoint && !at[endpoint2].endpoint && 1 != nMobile1 + nMobile2 )
                            continue;
                            */
                            /* -- new --

                            if ( !at[endpoint1].endpoint && !at[endpoint2].endpoint ) {
                            if ( !(bond_type1 == BOND_SINGLE || bond_type1 == BOND_DOUBLE) ||
                            !(bond_type2 == BOND_SINGLE || bond_type2 == BOND_DOUBLE) ) {
                            // at this point bond_type1 != bond_type2
                            continue;
                            }
                            if ( bond_type1 == BOND_SINGLE && !nMobile1 ||
                            bond_type2 == BOND_SINGLE && !nMobile2 ||
                            0 == nMobile1 + nMobile2 ) {
                            continue;
                            }
                            }
                            */

#if ( TAUT_TROPOLONE_7 == 1 )
                            if (at[i1].nNumAtInRingSystem >= 7)
                            {

                                ret = nGet14TautIn7MembAltRing( pCG, at, i1,
                                                                j, k, m, nDfsPathPos,
                                                                DfsPath, nMaxLenDfsPath,
                                                                EndPoint,
                                                                sizeof( EndPoint ) / sizeof( EndPoint[0] ),
                                                                BondPos,
                                                                sizeof( BondPos ) / sizeof( BondPos[0] ),
                                                                &nNumEndPoints, &nNumBondPos,
                                                                pBNS, pBD, num_atoms );
                                if (ret > 0)
                                {
                                    if (nNumEndPoints)
                                    {
                                        num_changes = RegisterEndPoints( pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS );
                                        if (num_changes == -1)
                                        {
                                            nErr = CT_TAUCOUNT_ERR;
                                        }
                                        if (num_changes < 0)
                                        {
                                            nErr = num_changes;
                                        }
                                        if (nErr)
                                        {
                                            goto free_memory;
                                        }
                                        tot_changes += ( num_changes > 0 );
                                    }
                                    if (nNumBondPos)
                                    {
                                        tot_changes += ( 0 < SetTautomericBonds( at, nNumBondPos, BondPos ) );
                                    }
                                }
                                else
                                {
                                    if (IS_BNS_ERROR( ret ))
                                    {
                                        nErr = ret;
                                        goto free_memory;
                                    }
                                }
                            }
#endif

#if ( TAUT_TROPOLONE_5 == 1 )
                            if (at[i1].nNumAtInRingSystem >= 5)
                            {

                                ret = nGet14TautIn5MembAltRing( pCG, at, i1, j, k, m, nDfsPathPos,
                                                                DfsPath, nMaxLenDfsPath,
                                                                EndPoint, sizeof( EndPoint ) / sizeof( EndPoint[0] ),
                                                                BondPos, sizeof( BondPos ) / sizeof( BondPos[0] ),
                                                                &nNumEndPoints, &nNumBondPos,
                                                                pBNS, pBD, num_atoms );
                                if (ret > 0)
                                {
                                    if (nNumEndPoints)
                                    {
                                        num_changes = RegisterEndPoints( pCG, t_group_info, EndPoint, nNumEndPoints, at, num_atoms, c_group_info, pBNS );
                                        if (num_changes == -1)
                                        {
                                            nErr = CT_TAUCOUNT_ERR;
                                        }
                                        if (num_changes < 0)
                                        {
                                            nErr = num_changes;
                                        }
                                        if (nErr)
                                        {
                                            goto free_memory;
                                        }
                                        tot_changes += ( num_changes > 0 );
                                    }
                                    if (nNumBondPos)
                                    {
                                        tot_changes += ( 0 < SetTautomericBonds( at, nNumBondPos, BondPos ) );
                                    }
                                }
                                else
                                {
                                    if (IS_BNS_ERROR( ret ))
                                    {
                                        nErr = ret;
                                        goto free_memory;
                                    }
                                }
                            }
#endif
                        }
                    }
                }
            }
        }
#endif /* } TAUT_TROPOLONE */

    free_memory:
        if (nDfsPathPos)
        {
            inchi_free( nDfsPathPos );
        }
#undef MAX_ALT_PATH_LEN
    }
#endif  /* } FIND_RING_SYSTEMS */

exit_function:

    return nErr < 0 ? nErr : tot_changes;
}


/****************************************************************************/
int free_t_group_info( T_GROUP_INFO *t_group_info )
{
    if (t_group_info)
    {
        if (t_group_info->t_group)
        {
            inchi_free( t_group_info->t_group ); /* djb-rwth: false positive oss-fuzz issue #42534728/68286? */
        }
        if (t_group_info->nEndpointAtomNumber)
        {
            inchi_free( t_group_info->nEndpointAtomNumber );
        }
        if (t_group_info->tGroupNumber)
        {
            inchi_free( t_group_info->tGroupNumber );
        }
        if (t_group_info->nIsotopicEndpointAtomNumber)
        {
            inchi_free( t_group_info->nIsotopicEndpointAtomNumber );
        }
        memset( t_group_info, 0, sizeof( *t_group_info ) ); /* djb-rwth: memset_s C11/Annex K variant? */
    }

    return 0;
}


/****************************************************************************/
int make_a_copy_of_t_group_info( T_GROUP_INFO *t_group_info,
                                 T_GROUP_INFO *t_group_info_orig )
{
    int err = 0, len;
    free_t_group_info( t_group_info );
    if (t_group_info_orig && t_group_info)
    {
        if (( len = t_group_info_orig->max_num_t_groups ) > 0)
        {
            T_GROUP* tgi_tg = NULL;  /* Copied from below 2024-09-01 DT */
            /* djb-rwth: fixing oss-fuzz issue #52978 */
            tgi_tg = (T_GROUP*)inchi_malloc(((long long)len+1) * sizeof(t_group_info->t_group[0])); /* djb-rwth: cast operator added */
            if (tgi_tg && t_group_info_orig->t_group)
            {
                t_group_info->t_group = tgi_tg;
                memcpy(tgi_tg,
                    t_group_info_orig->t_group,
                    len * sizeof(t_group_info->t_group[0])); /* djb-rwth: cast operator added */
            }
            else
            {
                err++;
            }
        }
        if (( len = t_group_info_orig->nNumEndpoints ) > 0)
        {
            if ((t_group_info->nEndpointAtomNumber =
                (AT_NUMB*) inchi_malloc( len * sizeof( t_group_info->nEndpointAtomNumber[0] ) ))) /* djb-rwth: addressing LLVM warning */
            {
                memcpy(t_group_info->nEndpointAtomNumber,
                    t_group_info_orig->nEndpointAtomNumber,
                    len * sizeof(t_group_info->nEndpointAtomNumber[0]));
            }
            else
            {
                err++;
            }
        }
        if (( len = t_group_info_orig->num_t_groups ) > 0)
        {
            if ((t_group_info->tGroupNumber =
                (AT_NUMB*) inchi_malloc( (long long)len * TGSO_TOTAL_LEN * sizeof( t_group_info->tGroupNumber[0] ) ))) /* djb-rwth: cast operator added; djb-rwth: addressing LLVM warning */
            {
                memcpy(t_group_info->tGroupNumber,
                    t_group_info_orig->tGroupNumber,
                    (long long)len * TGSO_TOTAL_LEN * sizeof(t_group_info->tGroupNumber[0])); /* djb-rwth: cast operator added */
            }
            else
            {
                err++;
            }
        }
        if (( len = t_group_info_orig->nNumIsotopicEndpoints ) > 0)
        {
            /* djb-rwth: fixing oss-fuzz issue #53519 */
            AT_NUMB* tgi_niean = (AT_NUMB*)inchi_malloc(len * sizeof(t_group_info->nIsotopicEndpointAtomNumber[0]));
            AT_NUMB* tgior_niean = (AT_NUMB*)inchi_realloc(t_group_info_orig->nIsotopicEndpointAtomNumber, len * sizeof(t_group_info_orig->nIsotopicEndpointAtomNumber[0]));
            if (tgi_niean && tgior_niean) /* djb-rwth: addressing LLVM warning */
            {
                t_group_info->nIsotopicEndpointAtomNumber = tgi_niean;
                t_group_info_orig->nIsotopicEndpointAtomNumber = tgior_niean;
                memcpy(tgi_niean,
                    tgior_niean,
                    len * sizeof(t_group_info->nIsotopicEndpointAtomNumber[0]));
            }
            else
            {
                err++;
            }
        }
        if (!err)
        {
            t_group_info->nNumEndpoints = t_group_info_orig->nNumEndpoints;
            t_group_info->num_t_groups = t_group_info_orig->num_t_groups;
            t_group_info->max_num_t_groups = t_group_info_orig->max_num_t_groups;
            t_group_info->bIgnoreIsotopic = t_group_info_orig->bIgnoreIsotopic;
            t_group_info->nNumIsotopicEndpoints = t_group_info_orig->nNumIsotopicEndpoints;
            t_group_info->tni = t_group_info_orig->tni;
            /*
            t_group_info->nNumRemovedExplicitH       = t_group_info_orig->nNumRemovedExplicitH;
            t_group_info->nNumRemovedProtons         = t_group_info_orig->nNumRemovedProtons;
            t_group_info->bNormalizationFlags        = t_group_info_orig->bNormalizationFlags;
            */
            /*
            t_group_info->bHardAddedRemovedProtons   = t_group_info_orig->bHardAddedRemovedProtons;
            t_group_info->bSimpleAddedRemovedProtons = t_group_info_orig->bSimpleAddedRemovedProtons;
            t_group_info->nNumCanceledCharges        = t_group_info_orig->nNumCanceledCharges;
            */
        }
        t_group_info->bTautFlags = t_group_info_orig->bTautFlags;
        t_group_info->bTautFlagsDone = t_group_info_orig->bTautFlagsDone;
    }

    return err;
}


/****************************************************************************
Set tautomer group isotopic sort keys
****************************************************************************/
int set_tautomer_iso_sort_keys( T_GROUP_INFO *t_group_info )
{
    T_GROUP       *t_group;
    T_GROUP_ISOWT Mult = 1;
    int     i, j, num_t_groups, num_iso_t_groups = 0;

    if (!t_group_info || !( t_group = t_group_info->t_group ) ||
         0 >= ( num_t_groups = t_group_info->num_t_groups ) || t_group_info->nNumIsotopicEndpoints)
    {
        return 0;
    }

    for (i = 0; i < num_t_groups; i++)
    {
        t_group[i].iWeight = 0;
        j = T_NUM_ISOTOPIC - 1;
        Mult = 1;
        do
        {
            t_group[i].iWeight += Mult * (T_GROUP_ISOWT) t_group[i].num[T_NUM_NO_ISOTOPIC + j];
        } while (--j >= 0 && ( Mult *= T_GROUP_ISOWT_MULT ));

        num_iso_t_groups += ( t_group[i].iWeight != 0 );
    }

    return num_iso_t_groups;
}


/****************************************************************************
Fill t_group_info with information necessary to fill out tautomer part
of the linear connection table record.

Note: on input, t_group_info should contain information created by MarkTautomerGroups()
No previous t_group_info adjustment due to throwing out disconnected parts of
the chemical structure is needed.

Note2: throws out t_groups containing negative charges only (IGNORE_TGROUP_WITHOUT_H==1)
(leave their tautomeric bonds unchanged)
Note3: removes negative charges from other tautomeric groups
and adjust counts of mobile atoms if permitted         (REMOVE_TGROUP_CHARGE==1)
****************************************************************************/
int CountTautomerGroups( sp_ATOM *at,
                         int num_atoms,
                         T_GROUP_INFO *t_group_info )
{
    int i, j, ret = 0, nNumEndpoints, max_t_group, num_groups_noH;

    AT_NUMB    nGroupNumber, nNewGroupNumber, *nCurrEndpointAtNoPos = NULL;

    T_GROUP   *t_group;
    int        num_t;
    /* int bIgnoreIsotopic, max_num_t; */
    AT_NUMB   *nTautomerGroupNumber = NULL;
    AT_NUMB   *nEndpointAtomNumber = NULL;
    AT_NUMB   *tGroupNumber = NULL;

    if (!t_group_info || !t_group_info->t_group || 0 >= t_group_info->max_num_t_groups)
    {
        return 0; /* empty t-groups */
    }
    num_t = t_group_info->num_t_groups;
    t_group = t_group_info->t_group;
    /*
    max_num_t       =  t_group_info->max_num_t_groups;
    bIgnoreIsotopic =  t_group_info->bIgnoreIsotopic;
    */
    num_groups_noH = 0;

    /* the following 2 arrays are to be rebuilt here */
    /* djb-rwth: fixing oss-fuzz issue #70006 */
    if (t_group_info->nEndpointAtomNumber)
    {
        /* free( t_group_info->nEndpointAtomNumber ); */ 
        t_group_info->nEndpointAtomNumber = NULL;
    }
    if (t_group_info->tGroupNumber)
    {
        /* inchi_free( t_group_info->tGroupNumber ); */
        t_group_info->tGroupNumber = NULL;
    }

    /*  Find max_t_group */
    for (i = 0, max_t_group = 0; i < t_group_info->num_t_groups; i++)
    {
        if (max_t_group < t_group[i].nGroupNumber)
            max_t_group = t_group[i].nGroupNumber;
    }
    /*  Allocate memory for temp storage of numbers of endpoints  */
    if (max_t_group &&
         !( nTautomerGroupNumber = (AT_NUMB*) inchi_calloc( (long long)max_t_group + 1, sizeof( nTautomerGroupNumber[0] ) ) /*temp*/ )) /* djb-rwth: cast operator added */
    {
        goto err_exit_function; /*  program error: out of RAM */ /*   <BRKPT> */
    }

    /*  Count endpoints for each tautomer group */
    for (i = 0, nNumEndpoints = 0; i < num_atoms; i++)
    {
        if (( j = at[i].endpoint ) == 0)
        {
            continue;
        }
        if (j > max_t_group) /*  debug only */
        {
            goto err_exit_function; /*  program error */ /*   <BRKPT> */
        }
        if (nTautomerGroupNumber) /* djb-rwth: fixing a NULL pointer dereference */
            nTautomerGroupNumber[j] ++;
        nNumEndpoints++;
    }

    if (!nNumEndpoints)
    {
        goto exit_function; /*  not a tautomer */
    }

    /*  allocate temporary array */
    if (!( nEndpointAtomNumber = (AT_NUMB*) inchi_calloc( nNumEndpoints, sizeof( nEndpointAtomNumber[0] ) ) ) ||
         !( nCurrEndpointAtNoPos = (AT_NUMB*) inchi_calloc( num_t, sizeof( nCurrEndpointAtNoPos[0] ) ) /*temp*/ ))
    {
        goto err_exit_function; /*   program error: out of RAM */ /*   <BRKPT> */
    }

    /*
    * Remove missing endpoints from t_group. Since only one
    * disconnected part is processed, some endpoints groups may have disappeared.
    * Mark t_groups containing charges only for subsequent removal
    */
    for (i = 0, nNewGroupNumber = 0; i < num_t; /*i ++*/)
    {
        int bNoH = 0, nNumH;
        nGroupNumber = t_group[i].nGroupNumber;
        for (j = 1, nNumH = t_group[i].num[0]; j < T_NUM_NO_ISOTOPIC; j++)
        {
            nNumH -= (int) t_group[i].num[j];
        }
        if (nTautomerGroupNumber) /* djb-rwth: fixing a NULL pointer dereference */
        {
            if (t_group[i].nNumEndpoints != nTautomerGroupNumber[(int)nGroupNumber]
#if ( IGNORE_TGROUP_WITHOUT_H == 1 )
                || (bNoH = (t_group[i].num[0] == t_group[i].num[1]))  /* only for (H,-) t-groups; (+) t-groups are not removed */
#endif
                ) /* djb-rwth: fixing a NULL pointer dereference */
            {
                if (!nTautomerGroupNumber[(int)nGroupNumber] || bNoH)
                {
                    /*  The group belongs to another disconnected part of the structure or has only charges */
                    /*  Remove the group */
                    num_t--;
                    if (i < num_t)
                        memmove(t_group + i, t_group + i + 1, ((long long)num_t - (long long)i) * sizeof(t_group[0])); /* djb-rwth: cast operators added */
                    if (bNoH)
                    {
                        /*  Group contains no mobile hydrogen atoms, only charges. Prepare to remove it. */
                        nTautomerGroupNumber[(int)nGroupNumber] = 0;
                        num_groups_noH++;
                    }
                    /*i --;*/
                }
                else
                {
                    /*  Different number of endpoints */
                    goto err_exit_function; /*  program error */ /*   <BRKPT> */
                }
            }
            else
            {
                /*  Renumber t_group and prepare to renumber at[i].endpoint */
                nTautomerGroupNumber[(int)nGroupNumber] =
                    t_group[i].nGroupNumber = ++nNewGroupNumber; /*  = i+1 */
                /*  get first group atom orig. number position in the nEndpointAtomNumber[] */
                /*  and in the tautomer endpoint canon numbers part of the connection table */
                t_group[i].nFirstEndpointAtNoPos = nCurrEndpointAtNoPos[i] =
                    i ? (t_group[i - 1].nFirstEndpointAtNoPos + t_group[i - 1].nNumEndpoints) : 0;
                t_group[i].num[0] = nNumH;
#if ( REMOVE_TGROUP_CHARGE == 1 )
                t_group[i].num[1] = 0;  /* remove only (-) charges */
#endif
                /* -- wrong condition. Disabled.
                if ( t_group[i].nGroupNumber != i + 1 ) { // for debug only
                goto err_exit_function; // program error
                }
                */
                i++;
            }
        }
    }
    if (num_t != nNewGroupNumber)
    {
        /*  for debug only */
        goto err_exit_function; /*  program error */ /*   <BRKPT> */
    }

    /*  Check if any tautomer group was left */
    if (!nNewGroupNumber)
    {
        if (!num_groups_noH)
        {
            goto err_exit_function; /*  program error: not a tautomer */ /*   <BRKPT> */
        }
        else
        {
            goto exit_function;
        }
    }

    /*
    * An array for tautomer group sorting later, at the time of storing Connection Table
    * Later the sorting consists out of 2 steps:
    * 1) Sort t_group[i].nNumEndpoints endpoint atom ranks within each endpoint group
    *    starting from t_group[i].nFirstEndpointAtNoPos; i = 0..t_group_info->num_t_groups-1
    * 2) Sort the groups indexes t_group_info->tGroupNumber[]
    */
    if (!( tGroupNumber =
        (AT_NUMB*) inchi_calloc( (long long)nNewGroupNumber * (long long)TGSO_TOTAL_LEN, sizeof( tGroupNumber[0] ) ) )) /* djb-rwth: cast operator added */
    {
        goto err_exit_function; /*  out of RAM */
    }
    for (i = 0; i < nNewGroupNumber; i++)
    {
        tGroupNumber[i] = (AT_NUMB) i; /*  initialization: original t_group number = (at[i]->endpoint-1) */
    }
    /*
    * Renumber endpoint atoms and save their orig. atom
    * numbers for filling out the tautomer part of the LinearCT.
    * nCurrEndpointAtNoPos[j] is an index of the atom number in the nEndpointAtomNumber[]
    */
    for (i = 0; i < num_atoms; i++)
    {
        if ((j = (int) at[i].endpoint)) /* djb-rwth: addressing LLVM warning */
        {
            j = (int) ( at[i].endpoint = nTautomerGroupNumber[j] ) - 1; /*  new t_group number */
            if (j >= 0)
            {
                /*  j=-1 in case of no mobile hydrogen atoms (charges only), group being removed */
                if (nCurrEndpointAtNoPos[j] >=   /*  debug only */ /* djb-rwth: ui_rr */
                     t_group[j].nFirstEndpointAtNoPos + t_group[j].nNumEndpoints)
                {
                    goto err_exit_function; /*  program error */ /*   <BRKPT> */
                }
                nEndpointAtomNumber[(int) nCurrEndpointAtNoPos[j] ++] = (AT_NUMB) i;
            }
            else
            {
                nNumEndpoints--; /*  endpoint has been removed */
            }
        }
    }
    t_group_info->num_t_groups = nNewGroupNumber;
    t_group_info->nNumEndpoints = nNumEndpoints;
    t_group_info->nEndpointAtomNumber = nEndpointAtomNumber;
    t_group_info->tGroupNumber = tGroupNumber; /* only the 1st segment filled */
    inchi_free( nTautomerGroupNumber );
    inchi_free( nCurrEndpointAtNoPos );
    return nNumEndpoints + T_GROUP_HDR_LEN * nNewGroupNumber + 1; /*  nLenLinearCTTautomer */

err_exit_function:
    ret = CT_TAUCOUNT_ERR;

exit_function:

    /*  release allocated memory; set "no tautomeric group" */
    if (nEndpointAtomNumber)
    {
        inchi_free( nEndpointAtomNumber );
    }
    if (nTautomerGroupNumber)
    {
        inchi_free( nTautomerGroupNumber );
    }
    if (tGroupNumber)
    {
        inchi_free( tGroupNumber );
    }
    if (nCurrEndpointAtNoPos)
    {
        inchi_free( nCurrEndpointAtNoPos );
    }
    t_group_info->nNumEndpoints = 0;
    t_group_info->num_t_groups = 0;
    if (!ret && ( ( t_group_info->tni.bNormalizationFlags & FLAG_NORM_CONSIDER_TAUT ) ||
                  (t_group_info->nNumIsotopicEndpoints > 1 && ( t_group_info->bTautFlagsDone & ( TG_FLAG_FOUND_ISOTOPIC_H_DONE | TG_FLAG_FOUND_ISOTOPIC_ATOM_DONE ) )) )) /* djb-rwth: addressing LLVM warning */
    {
        ret = 1; /* only protons have been (re)moved or neitralization happened */
    }

    return ret;
}


#if ( READ_INCHI_STRING == 1 )
#if ( INCLUDE_NORMALIZATION_ENTRY_POINT == 1 )


/****************************************************************************/
int CountTautomerGroupsInpAt( inp_ATOM *at,
                              int num_atoms,
                              T_GROUP_INFO *t_group_info )
{
    int i, j, ret = 0, nNumEndpoints, max_t_group, num_groups_noH;

    AT_NUMB    nGroupNumber, nNewGroupNumber, *nCurrEndpointAtNoPos = NULL;

    T_GROUP   *t_group;
    int        num_t;
    /* int bIgnoreIsotopic, max_num_t; */
    AT_NUMB   *nTautomerGroupNumber = NULL;
    AT_NUMB   *nEndpointAtomNumber = NULL;
    AT_NUMB   *tGroupNumber = NULL;

    if (!t_group_info || !t_group_info->t_group || 0 >= t_group_info->max_num_t_groups)
    {
        return 0; /* empty t-groups */
    }
    num_t = t_group_info->num_t_groups;
    t_group = t_group_info->t_group;
    /*
    max_num_t       =  t_group_info->max_num_t_groups;
    bIgnoreIsotopic =  t_group_info->bIgnoreIsotopic;
    */
    num_groups_noH = 0;

    /* the following 2 arrays are to be rebuilt here */
    if (t_group_info->nEndpointAtomNumber)
    {
        inchi_free( t_group_info->nEndpointAtomNumber );
        t_group_info->nEndpointAtomNumber = NULL;
    }
    if (t_group_info->tGroupNumber)
    {
        inchi_free( t_group_info->tGroupNumber );
        t_group_info->tGroupNumber = NULL;
    }
    /*  find max_t_group */
    for (i = 0, max_t_group = 0; i < t_group_info->num_t_groups; i++)
    {
        if (max_t_group < t_group[i].nGroupNumber)
            max_t_group = t_group[i].nGroupNumber;
    }
    /*  allocate memory for temp storage of numbers of endpoints  */
    if (max_t_group &&
         !( nTautomerGroupNumber = (AT_NUMB*) inchi_calloc( max_t_group + 1, sizeof( nTautomerGroupNumber[0] ) ) /*temp*/ ))
    {
        goto err_exit_function; /*  program error: out of RAM */ /*   <BRKPT> */
    }

    /*  count endpoints for each tautomer group */
    for (i = 0, nNumEndpoints = 0; i < num_atoms; i++)
    {
        if (( j = at[i].endpoint ) == 0)
            continue;
        if (j > max_t_group) /*  debug only */
            goto err_exit_function; /*  program error */ /*   <BRKPT> */
        nTautomerGroupNumber[j] ++;
        nNumEndpoints++;
    }

    if (!nNumEndpoints)
    {
        goto exit_function; /*  not a tautomer */
    }

    /*  allocate temporary array */
    if (!( nEndpointAtomNumber = (AT_NUMB*) inchi_calloc( nNumEndpoints, sizeof( nEndpointAtomNumber[0] ) ) ) ||
         !( nCurrEndpointAtNoPos = (AT_NUMB*) inchi_calloc( num_t, sizeof( nCurrEndpointAtNoPos[0] ) ) /*temp*/ ))
    {
        goto err_exit_function; /*   program error: out of RAM */ /*   <BRKPT> */
    }
    /*
    * Remove missing endpoints from t_group. Since only one
    * disconnected part is processed, some endpoints groups may have disappeared.
    * Mark t_groups containing charges only for subsequent removal
    */
    for (i = 0, nNewGroupNumber = 0; i < num_t; /*i ++*/)
    {
        int bNoH = 0, nNumH;
        nGroupNumber = t_group[i].nGroupNumber;
        for (j = 1, nNumH = t_group[i].num[0]; j < T_NUM_NO_ISOTOPIC; j++)
        {
            nNumH -= (int) t_group[i].num[j];
        }
        if (t_group[i].nNumEndpoints != nTautomerGroupNumber[(int) nGroupNumber]
#if ( IGNORE_TGROUP_WITHOUT_H == 1 )
             || ( bNoH = ( t_group[i].num[0] == t_group[i].num[1] ) )  /* only for (H,-) t-groups; (+) t-groups are not removed */
#endif
             )
        {
            if (!nTautomerGroupNumber[(int) nGroupNumber] || bNoH)
            {
                /*  the group belongs to another disconnected part of the structure or has only charges */
                /*  Remove the group */
                num_t--;
                if (i < num_t)
                    memmove( t_group + i, t_group + i + 1, ( num_t - i ) * sizeof( t_group[0] ) );
                if (bNoH)
                {
                    /*  group contains no mobile hydrogen atoms, only charges. Prepare to remove it. */
                    nTautomerGroupNumber[(int) nGroupNumber] = 0;
                    num_groups_noH++;
                }
                /*i --;*/
            }
            else
            {
                /*  different number of endpoints */
                goto err_exit_function; /*  program error */ /*   <BRKPT> */
            }
        }
        else
        {
            /*  renumber t_group and prepare to renumber at[i].endpoint */
            nTautomerGroupNumber[(int) nGroupNumber] =
                t_group[i].nGroupNumber = ++nNewGroupNumber; /*  = i+1 */
                                                             /*  get first group atom orig. number position in the nEndpointAtomNumber[] */
                                                             /*  and in the tautomer endpoint canon numbers part of the connection table */
            t_group[i].nFirstEndpointAtNoPos = nCurrEndpointAtNoPos[i] =
                i ? ( t_group[i - 1].nFirstEndpointAtNoPos + t_group[i - 1].nNumEndpoints ) : 0;
            t_group[i].num[0] = nNumH;
#if ( REMOVE_TGROUP_CHARGE == 1 )
            t_group[i].num[1] = 0;  /* remove only (-) charges */
#endif
                                    /* -- wrong condition. Disabled.
                                    if ( t_group[i].nGroupNumber != i + 1 ) { // for debug only
                                    goto err_exit_function; // program error
                                    }
                                    */
            i++;
        }
    }
    if (num_t != nNewGroupNumber)
    { /*  for debug only */
        goto err_exit_function; /*  program error */ /*   <BRKPT> */
    }

    /*  check if any tautomer group was left */
    if (!nNewGroupNumber)
    {
        if (!num_groups_noH)
            goto err_exit_function; /*  program error: not a tautomer */ /*   <BRKPT> */
        else
            goto exit_function;
    }
    /*
    * an array for tautomer group sorting later, at the time of storing Connection Table
    * Later the sorting consists out of 2 steps:
    * 1) Sort t_group[i].nNumEndpoints endpoint atom ranks within each endpoint group
    *    starting from t_group[i].nFirstEndpointAtNoPos; i = 0..t_group_info->num_t_groups-1
    * 2) Sort the groups indexes t_group_info->tGroupNumber[]
    */
    if (!( tGroupNumber =
        (AT_NUMB*) inchi_calloc( nNewGroupNumber*TGSO_TOTAL_LEN, sizeof( tGroupNumber[0] ) ) ))
    {
        goto err_exit_function; /*  out of RAM */
    }
    for (i = 0; i < nNewGroupNumber; i++)
    {
        tGroupNumber[i] = (AT_NUMB) i; /*  initialization: original t_group number = (at[i]->endpoint-1) */
    }
    /*
    * renumber endpoint atoms and save their orig. atom
    * numbers for filling out the tautomer part of the LinearCT.
    * nCurrEndpointAtNoPos[j] is an index of the atom number in the nEndpointAtomNumber[]
    */
    for (i = 0; i < num_atoms; i++)
    {
        if (j = (int) at[i].endpoint)
        {
            j = (int) ( at[i].endpoint = nTautomerGroupNumber[j] ) - 1; /*  new t_group number */
            if (j >= 0)
            { /*  j=-1 in case of no mobile hydrogen atoms (charges only), group being removed */
                if (nCurrEndpointAtNoPos[j] >=   /*  debug only */
                     t_group[j].nFirstEndpointAtNoPos + t_group[j].nNumEndpoints)
                {
                    goto err_exit_function; /*  program error */ /*   <BRKPT> */
                }
                nEndpointAtomNumber[(int) nCurrEndpointAtNoPos[j] ++] = (AT_NUMB) i;
            }
            else
            {
                nNumEndpoints--; /*  endpoint has been removed */
            }
        }
    }
    t_group_info->num_t_groups = nNewGroupNumber;
    t_group_info->nNumEndpoints = nNumEndpoints;
    t_group_info->nEndpointAtomNumber = nEndpointAtomNumber;
    t_group_info->tGroupNumber = tGroupNumber; /* only the 1st segment filled */
    inchi_free( nTautomerGroupNumber );
    inchi_free( nCurrEndpointAtNoPos );
    return nNumEndpoints + T_GROUP_HDR_LEN * nNewGroupNumber + 1; /*  nLenLinearCTTautomer */

err_exit_function:
    ret = CT_TAUCOUNT_ERR;
exit_function:
    /*  release allocated memory; set "no tautomeric group" */
    if (nEndpointAtomNumber)
    {
        inchi_free( nEndpointAtomNumber );
    }
    if (nTautomerGroupNumber)
    {
        inchi_free( nTautomerGroupNumber );
    }
    if (tGroupNumber)
    {
        inchi_free( tGroupNumber );
    }
    if (nCurrEndpointAtNoPos)
    {
        inchi_free( nCurrEndpointAtNoPos );
    }
    t_group_info->nNumEndpoints = 0;
    t_group_info->num_t_groups = 0;
    if (!ret && ( ( t_group_info->tni.bNormalizationFlags & FLAG_NORM_CONSIDER_TAUT ) ||
                  t_group_info->nNumIsotopicEndpoints > 1 && ( t_group_info->bTautFlagsDone & ( TG_FLAG_FOUND_ISOTOPIC_H_DONE | TG_FLAG_FOUND_ISOTOPIC_ATOM_DONE ) ) ))
    {
        ret = 1; /* only protons have been (re)moved or neitralization happened */
    }

    return ret;
}
#endif
#endif


/****************************************************************************
Tautomers: Compare for sorting

Compare for sorting Ranks only
Globals: pn_tRankForSort
****************************************************************************/
int CompRankTautomer( const void* a1, const void* a2, void *p )
{
    AT_RANK *pn_tRankForSort = (AT_RANK *) p;
    int ret = (int) pn_tRankForSort[(int) ( *(const AT_RANK*) a1 )] -
        (int) pn_tRankForSort[(int) ( *(const AT_RANK*) a2 )];

    return ret;
}


/****************************************************************************/
int SortTautomerGroupsAndEndpoints( CANON_GLOBALS *pCG,
                                    T_GROUP_INFO *t_group_info,
                                    int num_atoms,
                                    int num_at_tg,
                                    AT_RANK *nRank )
{
    int i, nFirstEndpointAtNoPos, nNumEndpoints;
    AT_RANK *pn_tRankForSort;
    AT_NUMB  *nEndpointAtomNumber;
    int       num_t_groups = num_at_tg - num_atoms;
    T_GROUP   *t_group = NULL;

    /*  Check if sorting is required */
    if (num_t_groups <= 0 || t_group_info->nNumEndpoints < 2)
    {
        return 0; /*  no tautomer data */
    }

    t_group = t_group_info->t_group;

    /*  Sort endpoints within the groups */
    for (i = 0; i < num_t_groups; i++)
    {
        if (t_group[i].nNumEndpoints < 2)
        {
            continue;  /*  program error; should not happen */ /*   <BRKPT> */
        }

        /*  Set globals for sorting */
        nFirstEndpointAtNoPos = t_group[i].nFirstEndpointAtNoPos;
        nNumEndpoints = t_group[i].nNumEndpoints;
        if (nNumEndpoints + nFirstEndpointAtNoPos > t_group_info->nNumEndpoints)
        {
            /*  for debug only */
            return CT_TAUCOUNT_ERR; /*  program error */ /*   <BRKPT> */
        }
        nEndpointAtomNumber = t_group_info->nEndpointAtomNumber + (int) nFirstEndpointAtNoPos;
        pn_tRankForSort = nRank;

        insertions_sort( pn_tRankForSort, nEndpointAtomNumber, nNumEndpoints,
                         sizeof( nEndpointAtomNumber[0] ), CompRankTautomer );
    }

    /*  Sort the tautomeric groups according to their ranks only
    (that is, ignoring the isotopic composition of the mobile groups and ranks of the endpoints) */
    if (t_group_info->num_t_groups > 1)
    {
        /*  Set globals for sorting */
        /*  a hack: the ranks of all tautomeric groups are */
        /*  located at nRank[num_atoms..num_at_tg-1] */
        pn_tRankForSort = nRank + num_atoms;
        /*  Sort */
        /*  ordering numbers to sort : t_group_info->tGroupNumber; */

        insertions_sort( pn_tRankForSort, t_group_info->tGroupNumber,
                         num_t_groups, sizeof( t_group_info->tGroupNumber[0] ),
                         CompRankTautomer );
    }

    return t_group_info->num_t_groups;
}
