content: /*
   * Copyright (c) 1997 - 2016
   * Actelion Pharmaceuticals Ltd.
   * Gewerbestrasse 16
   * CH-4123 Allschwil, Switzerland
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice, this
   *    list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   * 3. Neither the name of the the copyright holder nor the
   *    names of its contributors may be used to endorse or promote products
   *    derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * @author Thomas Sander
   */
  
  package com.actelion.research.chem;
  
  import com.actelion.research.util.IntArrayComparator;
  
  import java.util.ArrayDeque;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.TreeSet;
  
  public class TautomerHelper {
  	private static final int MAX_TAUTOMERS = 100000;
  	private static int sMaxTautomers = MAX_TAUTOMERS;
  	private static boolean sSuppressWarning = false;
  
  	private StereoMolecule mOriginalMol;
  	private boolean[] mIsTautomerBond;
  	private boolean[] mHasFreeValence;
  	private int[] mRegionPiCount;
  	private int[] mRegionDCount;
  	private int[] mRegionTCount;
  	private int[] mAtomRegionNo;
  	private int[] mAtomDCount;
  	private int[] mAtomTCount;
  	private int mRegionCount;
  
  	private Iterator<BondOrders> mBondOrderIterator;
  	private TreeSet<BondOrders> mBondOrderSet;
  	private ArrayDeque<BondOrders> mBondOrderDeque;
  
  	public static void setMaxTautomers(int maxTautomers) {
  		sMaxTautomers = maxTautomers;
  		}
  
  	public static void setSuppressWarning(boolean suppressWarning) {
  		sSuppressWarning = suppressWarning;
  		}
  
  	/**
  	 * @param mol
  	 */
  	public TautomerHelper(StereoMolecule mol) {
  		mOriginalMol = mol.getCompactCopy();
  		moveDeuteriumAndTritiumToTableEnd();
  		mOriginalMol.ensureHelperArrays(Molecule.cHelperRings);
  
  		mIsTautomerBond = new boolean[mOriginalMol.getBonds()];
  		mHasFreeValence = new boolean[mOriginalMol.getAtoms()];
  		for (int i=0; i<mOriginalMol.getAtoms(); i++) {
  			int valence = Molecule.getAllowedValences(mOriginalMol.getAtomicNo(i))[0];
  			mHasFreeValence[i] = (mOriginalMol.getNonHydrogenNeighbourCount(i) < valence);
  			}
  
  		createAllTautomers();
  
  		assignRegionNumbers();
  		countAndRemoveDAndT();
  		compileRegionCounts();
  		}
  
  	/**
  	 *
  	 * @param tautomer null or molecule container that received the new tautomer
  	 * @return
  	 */
  	public StereoMolecule getNextTautomer(StereoMolecule tautomer) {
  		if (mBondOrderIterator == null)
  			mBondOrderIterator = mBondOrderSet.iterator();
  
  		if (!mBondOrderIterator.hasNext())
  			return null;
  
  		if (tautomer != null) {
  			tautomer.clear();
  			mOriginalMol.copyMolecule(tautomer);
  			}
  		else {
  			tautomer = mOriginalMol.getCompactCopy();
  			}
  
  		mBondOrderIterator.next().copyToTautomer(tautomer);
  
  		// Now we add originally removed D and T at arbitrary positions
  		if (mAtomDCount != null || mAtomTCount != null) {
  			tautomer.ensureHelperArrays(Molecule.cHelperNeighbours);
  			int[] freeValence = new int[tautomer.getAtoms()];
  			for (int atom=0; atom<tautomer.getAtoms(); atom++)
  				freeValence[atom] = tautomer.getFreeValence(atom);
  
  			// Add D and T in two steps. First try to attach then to the atoms, where they have been originally.
  			// If limited free valence prevents that (because now there is a double bond, where it was single),
  			// then add remaining D and to other atoms in same region, which gained a free valence.
  			if (mAtomDCount != null) {
  				int[] regionDCount = mRegionDCount.clone();
  				for (int atom=0; atom<tautomer.getAtoms(); atom++) {
  					int dCount = Math.min(freeValence[atom], mAtomDCount[atom]);
  					for (int i=0; i<dCount; i++) {
  						addHydrogen(tautomer, atom, 2);
  						freeValence[atom]--;
  						regionDCount[mAtomRegionNo[atom]-1]--;
  						}
  					}
  				for (int atom=0; atom<tautomer.getAtoms(); atom++) {
  					if (mAtomRegionNo[atom] != 0 && regionDCount[mAtomRegionNo[atom]-1] > 0
  					 && tautomer.getAtomPi(atom) < mOriginalMol.getAtomPi(atom)
  					 && freeValence[atom] != 0) {
  						addHydrogen(tautomer, atom, 2);
  						freeValence[atom]--;
  						regionDCount[mAtomRegionNo[atom]-1]--;
  						}
  					}
  				}
  
  			if (mAtomTCount != null) {
  				int[] regionTCount = mRegionTCount.clone();
  				for (int atom=0; atom<tautomer.getAtoms(); atom++) {
  					int tCount = Math.min(freeValence[atom], mAtomTCount[atom]);
  					for (int i=0; i<tCount; i++) {
  						addHydrogen(tautomer, atom, 3);
  						freeValence[atom]--;
  						regionTCount[mAtomRegionNo[atom]-1]--;
  						}
  					}
  				for (int atom=0; atom<tautomer.getAtoms(); atom++) {
  					if (mAtomRegionNo[atom] != 0 && regionTCount[mAtomRegionNo[atom]-1] > 0
  					 && tautomer.getAtomPi(atom) < mOriginalMol.getAtomPi(atom)
  					 && freeValence[atom] != 0) {
  						addHydrogen(tautomer, atom, 3);
  						freeValence[atom]--;
  						regionTCount[mAtomRegionNo[atom]-1]--;
  						}
  					}
  				}
  			}
  
  		// Double bonds, which are single bonds in other tautomers, and which carry two different
  		// substituents on both double bond atoms, may have two configurations (E and Z),
  		// unless the double bond is in a small ring.
  		// In these cases we use a cross-bond instead of creating both configurations,
  		// to avoid another dimension of tautomer count explosion.
  		// Here we need to convert erroneous cross-bond assignments back to double, if the bond isn't a stereo bond.
  		tautomer.ensureHelperArrays(Molecule.cHelperParities);
  		for (int bond=0; bond<tautomer.getBonds(); bond++)
  			if (tautomer.getBondType(bond) == Molecule.cBondTypeCross
  			 && tautomer.getBondParity(bond) == Molecule.cBondParityNone)
  				tautomer.setBondType(bond, Molecule.cBondTypeDouble);
  
  		return tautomer;
  		}
  
  	private void moveDeuteriumAndTritiumToTableEnd() {
  		mOriginalMol.ensureHelperArrays(Molecule.cHelperNeighbours);
  
  		int lastNonHAtom = mOriginalMol.getAtoms();
  		do lastNonHAtom--;
  		while ((lastNonHAtom >= 0) && mOriginalMol.getAtomicNo(lastNonHAtom) == 1);
  
  		for (int atom=0; atom<lastNonHAtom; atom++) {
  			if (mOriginalMol.getAtomicNo(atom) == 1) {
  				mOriginalMol.swapAtoms(atom, lastNonHAtom);
  
  				do lastNonHAtom--;
  				while (mOriginalMol.getAtomicNo(lastNonHAtom) == 1);
  				}
  			}
  
  		if (lastNonHAtom == mOriginalMol.getAtoms()-1)
  			return;
  
  		boolean isHydrogenBond[] = new boolean[mOriginalMol.getBonds()];
  		for (int bond=0; bond<mOriginalMol.getBonds(); bond++) {	// mark all bonds to hydrogen
  			int atom1 = mOriginalMol.getBondAtom(0, bond);
  			int atom2 = mOriginalMol.getBondAtom(1, bond);
  			if (mOriginalMol.getAtomicNo(atom1) == 1
  			 || mOriginalMol.getAtomicNo(atom2) == 1)
  				isHydrogenBond[bond] = true;
  			}
  
  		int lastNonHBond = mOriginalMol.getBonds();
  		do lastNonHBond--; while ((lastNonHBond >= 0) && isHydrogenBond[lastNonHBond]);
  
  		for (int bond=0; bond<lastNonHBond; bond++) {
  			if (isHydrogenBond[bond]) {
  				mOriginalMol.swapBonds(bond, lastNonHBond);
  				isHydrogenBond[bond] = false;
  				do lastNonHBond--;
  				while (isHydrogenBond[lastNonHBond]);
  				}
  			}
  		}
  
  	private void addHydrogen(StereoMolecule mol, int atom, int mass) {
  		int hydrogen = mol.addAtom(1);
  		mol.setAtomMass(hydrogen, mass);
  		mol.addBond(atom, hydrogen, Molecule.cBondTypeSingle);
  		}
  
  	public int getTautomerCount() {
  		return mBondOrderSet.size();
  		}
  
  	private void countAndRemoveDAndT() {
  		for (int bond=0; bond<mOriginalMol.getAllBonds(); bond++) {
  			for (int i=0; i<2; i++) {
  				int atom1 = mOriginalMol.getBondAtom(i, bond);
  				int atom2 = mOriginalMol.getBondAtom(1-i, bond);
  				if (mOriginalMol.getAtomicNo(atom1) == 1
  				 && mOriginalMol.getAtomMass(atom1) > 1
  				 && mOriginalMol.getAtomicNo(atom2) > 1
  				 && mAtomRegionNo[atom2] != 0) {
  					if (mOriginalMol.getAtomMass(atom1) == 2) {
  						if (mAtomDCount == null)
  							mAtomDCount = new int[mOriginalMol.getAllAtoms()];
  						mAtomDCount[atom2]++;
  						}
  					else {
  						if (mAtomTCount == null)
  							mAtomTCount = new int[mOriginalMol.getAllAtoms()];
  						mAtomTCount[atom2]++;
  						}
  					mOriginalMol.markAtomForDeletion(atom1);
  					}
  				}
  			}
  		if (mAtomDCount != null || mAtomTCount != null)
  			mOriginalMol.deleteMarkedAtomsAndBonds();
  		}
  
  	/**
  	 * @return number of tautomeric regions, i.e. atoms being connected by tautomeric bonds
  	 */
  	public int getAtomRegionCount() {
  		return mRegionCount;
  		}
  
  	/**
  	 * Returns region numbers for all atoms, i.e. numbers identifying the respective connected
  	 * tautomeric regions the atom belongs to. Atoms sharing the same region share the same number.<br>
  	 * 0: not member of a tautomer region; 1 and above: region number
  	 * @return region count
  	 */
  	public int[] getAtomRegionNumbers() {
  		return mAtomRegionNo;
  		}
  
  	/**
  	 * Considers connected tautomer bonds to belong to a tautomer region.
  	 * All independent tautomer regions are located and member atoms assigned to them.
  	 * mAtomRegionNo[] is set accordingly.
  	 * 0: not member of a tautomer region; 1 and above: region number
  	 */
  	private void assignRegionNumbers() {
  		mAtomRegionNo = new int[mOriginalMol.getAtoms()];
  		int[] graphAtom = new int[mOriginalMol.getAtoms()];
  		boolean[] bondWasSeen = new boolean[mOriginalMol.getBonds()];
  		int region = 0;
  
  		for (int bond=0; bond<mOriginalMol.getBonds(); bond++) {
  			if (!bondWasSeen[bond] && mIsTautomerBond[bond]) {
  				region++;
  				mAtomRegionNo[mOriginalMol.getBondAtom(0, bond)] = region;
  				mAtomRegionNo[mOriginalMol.getBondAtom(1, bond)] = region;
  				bondWasSeen[bond] = true;
  				for (int i=0; i<2; i++) {
  					int atom = mOriginalMol.getBondAtom(i, bond);
  					mAtomRegionNo[atom] = region;
  					int current = 0;
  					int highest = 0;
  					graphAtom[0] = atom;
  					while (current <= highest) {
  						for (int j=0; j<mOriginalMol.getConnAtoms(graphAtom[current]); j++) {
  							int connBond = mOriginalMol.getConnBond(graphAtom[current], j);
  							if (!bondWasSeen[connBond] && mIsTautomerBond[connBond]) {
  								bondWasSeen[connBond] = true;
  								int connAtom = mOriginalMol.getConnAtom(graphAtom[current], j);
  								if (mAtomRegionNo[connAtom] == 0) {
  									mAtomRegionNo[connAtom] = region;
  									graphAtom[++highest] = connAtom;
  									}
  								}
  							}
  						current++;
  						}
  					}
  				}
  			}
  
  		mRegionCount = region;
  		}
  
  	/**
  	 * Counts for every region: pi-electrons, deuterium atoms, tritium atoms.
  	 * Must be called after assignRegionNumbers().
  	 */
  	private void compileRegionCounts() {
  		mRegionPiCount = new int[mRegionCount];
  		mRegionDCount = new int[mRegionCount];
  		mRegionTCount = new int[mRegionCount];
  
  		for (int atom=0; atom<mOriginalMol.getAtoms(); atom++) {
  			if (mAtomRegionNo[atom] != 0) {
  				int regionIndex = mAtomRegionNo[atom]-1;
  				if (mAtomDCount != null)
  					mRegionDCount[regionIndex] += mAtomDCount[atom];
  				if (mAtomTCount != null)
  					mRegionTCount[regionIndex] += mAtomTCount[atom];
  				}
  			}
  		for (int bond=0; bond<mOriginalMol.getBonds(); bond++) {
  			if (mIsTautomerBond[bond]
  			 && mOriginalMol.getBondOrder(bond) == 2) {
  				mRegionPiCount[mAtomRegionNo[mOriginalMol.getBondAtom(0, bond)]-1] += 2;
  				}
  			}
  		}
  
  	private boolean hasAcidicHydrogen(StereoMolecule mol, int atom) {
  		if (mol.getAllHydrogens(atom) <= 0)
  			return false;
  
  		if (mol.isElectronegative(atom))
  			return true;
  
  		if (mol.getAtomPi(atom) != 0)
  			return false;
  
  		return true;
  		}
  
  	/**
  	 * If no tautomers can be formed then a copy of the original molecule is returned.
  	 * Otherwise a copy of the original molecule is normalized to create a
  	 * generic tautomer structure. The original molecule is not touched.
  	 * Different tautomers of the same molecule should always result in the same
  	 * generic tautomer structure. A generic tautomer contains one or more regions
  	 * indicated by bond query features cBondQFSingle & cBondQFDouble. Bond types
  	 * of all bonds of any tautomer region are cBondTypeSingle.
  	 * The highest ranking atom in every region carries a label defining the number
  	 * of pi-electrons and D and T atoms. The returned molecule has the fragment bit set.
  	 * Canonicalizing the returned molecule with Canonizer mode ENCODE_ATOM_CUSTOM_LABELS
  	 * produces the same idcode from any tautomer.
  	 * If keepStereoCenters is true, then stereo centers with parity 1 or 2, if they are
  	 * absolute or if they are part of an AND/OR group with more than one member,
  	 * are considered stable (non racemising) and, thus, their proton is not considered
  	 * being able to take part in a tautomeric transition.
  	 * @return generic tautomer with normalized tautomer regions and custom label to encode pi,D,T counts
  	 */
  	public StereoMolecule createGenericTautomer() {
  		if (mBondOrderSet.size() == 1)
  			return mOriginalMol;
  
  		StereoMolecule mol = mOriginalMol.getCompactCopy();
  		mol.setFragment(true);
  
  		mol.ensureHelperArrays(Molecule.cHelperRings);
  
  		for (int bond=0; bond<mol.getBonds(); bond++) {
  			if (mIsTautomerBond[bond]) {
  				mol.setBondType(bond, Molecule.cBondTypeSingle);
  				mol.setBondQueryFeature(bond, Molecule.cBondTypeSingle | Molecule.cBondTypeDouble, true);
  				}
  			}
  
  		// remove stereo information from bonds that indicate a stereo center at one of the tautomer region atoms
  		for (int atom=0; atom<mol.getAtoms(); atom++) {
  			if (mAtomRegionNo[atom] != 0
  			 && mOriginalMol.getNonHydrogenNeighbourCount(atom) < 4) {   // keep terternary S or P
  				mol.convertStereoBondsToSingleBonds(atom);
  				mol.setAtomConfigurationUnknown(atom,false);
  				mol.setAtomESR(atom, Molecule.cESRTypeAbs, -1);
  				}
  			}
  
  		// find highest ranking atom in every region
  		int[] maxAtom = new int[mRegionCount];
  		int[] maxRank = new int[mRegionCount];
  		int[] atomRank = new Canonizer(mol).getFinalRank();
  		for (int atom=0; atom<mol.getAtoms(); atom++) {
  			if (mAtomRegionNo[atom] != 0) {
  				int regionIndex = mAtomRegionNo[atom]-1;
  				if (maxRank[regionIndex] < atomRank[atom]) {
  					maxRank[regionIndex] = atomRank[atom];
  					maxAtom[regionIndex] = atom;
  					}
  				}
  			}
  
  		// attach label with region counts to highest ranking atoms
  		for (int i=0; i<mRegionCount; i++) {
  			String label = ""+mRegionPiCount[i]+"|"+mRegionDCount[i]+"|"+mRegionTCount[i];
  			mol.setAtomCustomLabel(maxAtom[i], label);
  			}
  
  		return mol;
  		}
  
  	private void createAllTautomers() {
  		mBondOrderSet = new TreeSet<>();
  		mBondOrderDeque = new ArrayDeque<>();
  
  		addTautomerIfNew(new BondOrders(mOriginalMol));
  
  		// This serves as recycled molecule container
  		StereoMolecule tautomer = mOriginalMol.getCompactCopy();
  
  		while (!mBondOrderDeque.isEmpty()) {
  			mBondOrderDeque.poll().copyToTautomer(tautomer);
  			addAllTautomers(tautomer);
  
  			if (mBondOrderSet.size() >= sMaxTautomers) {
  				if (!sSuppressWarning)
  					System.out.println("Tautomer count exceeds maximum: "+new Canonizer(mOriginalMol).getIDCode());
  				break;
  				}
  			}
  
  		// TODO racemize stereo centers
  		}
  
  	/**
  	 * Find all HX-Y=Z / X=Y-ZH type 3-atom sequences and recursively vinylogous sequences.
  	 */
  	private void addAllTautomers(StereoMolecule mol) {
  		ArrayList<Integer> bondList = new ArrayList<>();
  		mol.ensureHelperArrays(Molecule.cHelperNeighbours);
  		boolean [] isUsedAtom = new boolean[mol.getAtoms()];   // atom use buffer for recursive methods
  
  		for (int atom1=0; atom1<mol.getAtoms(); atom1++) {
  			if (isValidHeteroAtom(atom1)) {
  				isUsedAtom[atom1] = true;
  				for (int i=0; i<mol.getConnAtoms(atom1); i++) {
  					int atom2 = mol.getConnAtom(atom1, i);
  					int bond12 = mol.getConnBond(atom1, i);
  					int order12 = mol.getConnBondOrder(atom1, i);
  					if (mol.getAtomPi(atom2) != 0
  					 && mol.getAtomPi(atom1) < order12) {   // make sure atom1 has no other double bond
  						isUsedAtom[atom2] = true;
  						bondList.add(bond12);
  						for (int j=0; j<mol.getConnAtoms(atom2); j++) {
  							int atom3 = mol.getConnAtom(atom2, j);
  							if (!isUsedAtom[atom3]) {
  								isUsedAtom[atom3] = true;
  								int bond23 = mol.getConnBond(atom2, j);
  								int order23 = mol.getConnBondOrder(atom2, j);
  								if (mol.getAtomPi(atom2) + 2 == order12 + order23) {
  									bondList.add(bond23);
  
  									// if atom3 has other double bond we check for vinylogous donor atoms
  									if (order12 >= order23) {
  										if (mol.getAtomPi(atom3) < order23) {   // if atom3 has no other double bond
  											if (hasAcidicHydrogen(mol, atom3))
  												addVinylogousTautomers(mol, atom3, true, false, isUsedAtom, bondList);
  											}
  										else {
  											addVinylogousTautomers(mol, atom3, true, true, isUsedAtom, bondList);
  											}
  										}
  
  									if (order23 >= order12 && hasAcidicHydrogen(mol, atom1)) {
  										addVinylogousTautomers(mol, atom3, false, false, isUsedAtom, bondList);
  										}
  
  									if (isValidDonorAtom(atom3)
  									 && mol.getAtomPi(atom3) < order23) {   // make sure atom3 has no other double bond
  										if (order12<=2 && order23>=2 && hasAcidicHydrogen(mol, atom1)) {
  											addDirectTautomer(mol, bond12, bond23);
  											}
  
  										if (order12>=2 && order23<=2 && hasAcidicHydrogen(mol, atom3)) {
  											addDirectTautomer(mol, bond23, bond12);
  											}
  										}
  
  									bondList.remove(bondList.size()-1);
  									}
  								isUsedAtom[atom3] = false;
  								}
  							}
  						bondList.remove(bondList.size()-1);
  						isUsedAtom[atom2] = false;
  						}
  					}
  				isUsedAtom[atom1] = false;
  				}
  			}
  		}
  
  	private boolean isValidDonorAtom(int atom) {
  		return mHasFreeValence[atom]
  			&& (mOriginalMol.getAtomicNo(atom) == 5
  			 || mOriginalMol.getAtomicNo(atom) == 6
  			 || mOriginalMol.getAtomicNo(atom) == 7
  			 || mOriginalMol.getAtomicNo(atom) == 8
  			 || mOriginalMol.getAtomicNo(atom) == 16
  			 || mOriginalMol.getAtomicNo(atom) == 34
  			 || mOriginalMol.getAtomicNo(atom) == 52);
  		}
  
  	private boolean isValidHeteroAtom(int atom) {
  		return mHasFreeValence[atom]
  			&& (mOriginalMol.getAtomicNo(atom) == 7
  			 || mOriginalMol.getAtomicNo(atom) == 8
  			 || mOriginalMol.getAtomicNo(atom) == 16
  			 || mOriginalMol.getAtomicNo(atom) == 34
  			 || mOriginalMol.getAtomicNo(atom) == 52);
  		}
  
  	private void addDirectTautomer(StereoMolecule mol, int bondSToD, int bondDToS) {
  		BondOrders bondOrders = new BondOrders(mol);
  		bondOrders.setBond(bondSToD, mol.getBondOrder(bondSToD) == 1 ? 2 : 3);
  		bondOrders.setBond(bondDToS, mol.getBondOrder(bondDToS) == 2 ? 1 : 2);
  		mIsTautomerBond[bondSToD] = true;
  		mIsTautomerBond[bondDToS] = true;
  		addTautomerIfNew(bondOrders);
  		}
  
  	private boolean addVinylogousTautomers(StereoMolecule mol, int atom1, boolean firstBondIsDouble, boolean thirdBondIsDouble, boolean[] isUsedAtom, ArrayList<Integer> bondList) {
  		for (int i=0; i<mol.getConnAtoms(atom1); i++) {
  			int atom2 = mol.getConnAtom(atom1, i);
  			if (!isUsedAtom[atom2]) {
  				int bond12 = mol.getConnBond(atom1, i);
  				int order12 = mol.getBondOrder(bond12);
  				if ((firstBondIsDouble && order12 >= 2)
  				 || (!firstBondIsDouble && order12 <= 2)) {
  					isUsedAtom[atom2] = true;
  					bondList.add(bond12);
  					for (int j=0; j<mol.getConnAtoms(atom2); j++) {
  						int atom3 = mol.getConnAtom(atom2, j);
  						if (!isUsedAtom[atom3]) {
  							int bond23 = mol.getConnBond(atom2, j);
  							int order23 = mol.getBondOrder(bond23);
  							if (mol.getAtomPi(atom2) + 2 == order12 + order23
  							 && ((firstBondIsDouble && order23 <= 2)
  							  || (!firstBondIsDouble && order23 >= 2))) {
  								isUsedAtom[atom3] = true;
  								bondList.add(bond23);
  								if (isValidDonorAtom(atom3) && (!firstBondIsDouble || hasAcidicHydrogen(mol, atom3))) {
  									BondOrders bondOrders = new BondOrders(mol);
  									for (int k=0; k<bondList.size(); k++) {
  										int bond = bondList.get(k);
  										boolean makeDouble = (k < 2) ? (firstBondIsDouble ^ (k & 1) == 0)
  												: (thirdBondIsDouble ^ (k & 1) == 0);
  										if (makeDouble)
  											bondOrders.setBond(bond, mol.getBondOrder(bond) == 1 ? 2 : 3);
  										else
  											bondOrders.setBond(bond, mol.getBondOrder(bond) == 2 ? 1 : 2);
  										mIsTautomerBond[bond] = true;
  										}
  									addTautomerIfNew(bondOrders);
  									}
  								else {
  									addVinylogousTautomers(mol, atom3, firstBondIsDouble, thirdBondIsDouble, isUsedAtom, bondList);
  									}
  								bondList.remove(bondList.size()-1);
  								isUsedAtom[atom3] = false;
  								}
  							}
  						}
  					bondList.remove(bondList.size()-1);
  					isUsedAtom[atom2] = false;
  					}
  				}
  			}
  		return false;
  		}
  
  	private void addTautomerIfNew(BondOrders bondOrders) {
  		if (mBondOrderSet.add(bondOrders))
  			mBondOrderDeque.add(bondOrders);
  		}
  
  	class BondOrders implements Comparable<BondOrders> {
  		private int[] encoding;
  
  		public BondOrders(StereoMolecule mol) {
  			encoding = new int[(mOriginalMol.getBonds()+15) / 16];
  			for (int i=0; i<mOriginalMol.getBonds(); i++)
  				encoding[i >> 4] |= (Math.min(3, mol.getBondOrder(i)) << (2*(i & 15)));
  			}
  
  		@Override
  		public int compareTo(BondOrders o) {
  			return new IntArrayComparator().compare(encoding, o.encoding);
  			}
  
  		public void setBond(int bond, int order) {
  			int high = bond >> 4;
  			int shift = 2 * (bond & 15);
  			encoding[high] &= ~(3 << shift);
  			encoding[high] |= (order << shift);
  			}
  
  		public void copyToTautomer(StereoMolecule tautomer) {
  			for (int i=0; i<mOriginalMol.getBonds(); i++) {
  				if (mIsTautomerBond[i]) {
  					int bo = 3 & (encoding[i >> 4] >> (2*(i & 15)));
  					tautomer.setBondType(i, bo == 1 ? Molecule.cBondTypeSingle
  							: bo == 2 ? (mIsTautomerBond[i] && !mOriginalMol.isSmallRingBond(i) ? Molecule.cBondTypeCross : Molecule.cBondTypeDouble)
  							: bo == 3 ? Molecule.cBondTypeTriple
  							: Molecule.cBondTypeMetalLigand);
  					}
  				}
  			}
  		}
  	}
  