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 java.util.ArrayList;
  
  public class RingCollection {
  	public static final int MAX_SMALL_RING_SIZE = 7;
  	public static final int MAX_SMALL_RING_COUNT = 1024; // to prevent explosions with highly connected metal grids, etc.
  
  	private static final int MODE_SMALL_RINGS = 1;
  	private static final int MODE_LARGE_RINGS = 2;
  	private static final int MODE_AROMATICITY = 4;
  	public static final int MODE_SMALL_RINGS_ONLY = MODE_SMALL_RINGS;
  	public static final int MODE_SMALL_AND_LARGE_RINGS = MODE_SMALL_RINGS
  													   | MODE_LARGE_RINGS;
  	public static final int MODE_SMALL_RINGS_AND_AROMATICITY = MODE_SMALL_RINGS
  															 | MODE_AROMATICITY;
  	public static final int MODE_SMALL_AND_LARGE_RINGS_AND_AROMATICITY = MODE_SMALL_RINGS
  																	   | MODE_LARGE_RINGS
  																	   | MODE_AROMATICITY;
  	public static final int MODE_INCLUDE_TAUTOMERIC_BONDS = 8;
  
  	private static final int FEATURES_RING_SIZE = 0x0000FFFF;
  	private static final int FEATURES_AROMATIC = 0x00010000;
  	private static final int FEATURES_DELOCALIZED = 0x00020000;
  	private static final int FEATURES_HETERO_AROMATIC = 0x00040000;
  
  	private final ExtendedMolecule mMol;
  	private final ArrayList<int[]> mRingAtomSet;
  	private final ArrayList<int[]> mRingBondSet;
  	private final ArrayList<Integer> mAzuleneBridgeBonds;
  	private final int[] mAtomRingFeatures;
  	private final int[] mBondRingFeatures;
  	private int[] mHeteroPosition;
  	private boolean[] mIsAromatic;
  	private boolean[] mIsDelocalized;
  	private final int mMaxSmallRingSize;
  
  	/**
  	 * Generates the complete set of small rings, which don't contain metal atoms
  	 * and have up to 7 members.<br> If mode includes LARGE_RINGS, then it determines
  	 * for every atom and bond the size of the smallest ring, which they are
  	 * a member of.<br>If mode includes AROMATICITY then every small ring
  	 * is checked, whether it is aromatic.
  	 * @param mol
  	 * @param mode one of the public MODE_ options
  	 */
  	public RingCollection(ExtendedMolecule mol, int mode) {
  		this(mol, mode, MAX_SMALL_RING_SIZE);
  		}
  
  	/**
  	 * Generates the complete set of small rings, which don't contain metal atoms
  	 * and have up to 7 members.<br> If mode includes LARGE_RINGS, then it determines
  	 * for every atom and bond the size of the smallest ring, which they are
  	 * a member of.<br>If mode includes AROMATICITY then every small ring
  	 * is checked, whether it is aromatic.
  	 * @param mol
  	 * @param mode one of the public MODE_ options
  	 * @param maxSmallRingSize largest ring size considered a small ring
  	 */
  	public RingCollection(ExtendedMolecule mol, int mode, int maxSmallRingSize) {
  		mMol = mol;
  		mMaxSmallRingSize = maxSmallRingSize;
  		mRingAtomSet = new ArrayList<>();
  		mRingBondSet = new ArrayList<>();
  		mAzuleneBridgeBonds = new ArrayList<>();
  
  		mAtomRingFeatures = new int[mMol.getAtoms()];
  		mBondRingFeatures = new int[mMol.getBonds()];
  
  		mMol.ensureHelperArrays(ExtendedMolecule.cHelperNeighbours);
  
  		boolean[] isConfirmedChainAtom = new boolean[mMol.getAtoms()];
  		boolean[] isConfirmedChainBond = new boolean[mMol.getBonds()];
  
  		boolean found;
  		do {	// detect atoms of side chains as non-ring-atoms
  			found = false;
  			for (int atom=0; atom<mMol.getAtoms(); atom++) {
  				if (!isConfirmedChainAtom[atom]) {
  					int potentialRingNeighbours = 0;
  					for (int i=0; i<mMol.getConnAtoms(atom); i++)
  						if (!isConfirmedChainAtom[mMol.getConnAtom(atom, i)])
  							potentialRingNeighbours++;
  	
  					if (potentialRingNeighbours < 2) {
  						isConfirmedChainAtom[atom] = true;
  						for (int i=0; i<mMol.getConnAtoms(atom); i++)
  							isConfirmedChainBond[mMol.getConnBond(atom, i)] = true;
  
  						found = true;
  						}
  					}
  				}
  			} while (found);
  
  				// generate graph of potential ring atoms to find ring closure bonds
  		int startAtom = 0;  // simply take the first potential ring atom as graph base
  		while ((startAtom < mMol.getAtoms()) && isConfirmedChainAtom[startAtom])
  			startAtom++;
  		if (startAtom == mMol.getAtoms())
  			return;		 // no rings found
  
  				// find all rings with less than 8 members of all closure bonds
  		int[] graphAtom = new int[mMol.getAtoms()];
  		graphAtom[0] = startAtom;
  		int[] parent = new int[mMol.getAtoms()];
  		parent[0] = -1;
  		int[] fragmentNo = new int[mMol.getAtoms()];
  		fragmentNo[startAtom] = 1;
  		int current = 0;
  		int highest = 0;
  		int noOfFragments = 1;
  		while (current <= highest) {
  			for (int i=0; i<mMol.getConnAtoms(graphAtom[current]); i++) {
  				int candidate = mMol.getConnAtom(graphAtom[current], i);
  				if (candidate == parent[graphAtom[current]])
  					continue;
  
  				if (fragmentNo[candidate] != 0) {   // closure bond
  					addSmallRingsToSet(mMol.getConnBond(graphAtom[current], i), isConfirmedChainAtom);
  					continue;
  					}
  
  				if (!isConfirmedChainAtom[candidate]) {
  					fragmentNo[candidate] = noOfFragments;
  					parent[candidate] = graphAtom[current];
  					graphAtom[++highest] = candidate;
  					}
  				}
  			current++;
  			if (current > highest) {
  					// if run out of atoms look for new base atom of other fragment
  				for (int atom=0; atom<mMol.getAtoms(); atom++) {
  					if (fragmentNo[atom] == 0 && !isConfirmedChainAtom[atom]) {
  						fragmentNo[atom] = ++noOfFragments;
  						graphAtom[++highest] = atom;
  						parent[atom] = -1;
  						break;
  						}
  					}
  				}
  			}
  
  		if ((mode & MODE_AROMATICITY) != 0) {
  			mIsAromatic = new boolean[mRingAtomSet.size()];
  			mIsDelocalized = new boolean[mRingAtomSet.size()];
  			mHeteroPosition = new int[mRingAtomSet.size()];
  			determineAromaticity(mIsAromatic, mIsDelocalized, mHeteroPosition, (mode & MODE_INCLUDE_TAUTOMERIC_BONDS) != 0);
  			updateAromaticity();
  			}
  
  		// find large rings by examining every potential ring bond
  		// which is not a member of a small ring
  		if ((mode & MODE_LARGE_RINGS) != 0) {
  			for (int bond=0; bond<mMol.getBonds(); bond++) {
  				if (!isConfirmedChainBond[bond] && mMol.getBondOrder(bond) != 0) {
  					int[] ringAtom = findSmallestRing(bond, isConfirmedChainAtom);
  					if (ringAtom != null)
  						updateRingSize(ringAtom, getRingBonds(ringAtom));
  					}
  				}
  			}
  		}
  
  
  	/**
  	 * Find the smallest ring of the given bond
  	 * @param bond
  	 * @param isConfirmedChainAtom
  	 * @return
  	 */
  	private int[] findSmallestRing(int bond, boolean[] isConfirmedChainAtom) {
  		int atom1 = mMol.getBondAtom(0, bond);
  		int atom2 = mMol.getBondAtom(1, bond);
  		int[] graphAtom = new int[mMol.getAtoms()];
  		int[] graphLevel = new int[mMol.getAtoms()];
  		int[] graphParent = new int[mMol.getAtoms()];
  		graphAtom[0] = atom1;
  		graphAtom[1] = atom2;
  		graphLevel[atom1] = 1;
  		graphLevel[atom2] = 2;
  		graphParent[atom1] = -1;
  		graphParent[atom2] = atom1;
  		int current = 1;
  		int highest = 1;
  		while (current <= highest) {
  			for (int i=0; i<mMol.getConnAtoms(graphAtom[current]); i++) {
  				int candidate = mMol.getConnAtom(graphAtom[current], i);
  				if ((current > 1) && candidate == atom1) {
  					int[] ringAtom = new int[graphLevel[graphAtom[current]]];
  					int atom = graphAtom[current];
  					for (int j = 0; j < ringAtom.length; j++) {
  						ringAtom[j] = atom;
  						atom = graphParent[atom];
  						}
  					return ringAtom;
  					}
  				if (graphLevel[candidate] == 0 && !isConfirmedChainAtom[candidate]) {
  					graphAtom[++highest] = candidate;
  					graphLevel[candidate] = graphLevel[graphAtom[current]] + 1;
  					graphParent[candidate] = graphAtom[current];
  					}
  				}
  			current++;
  			}
  		return null;
  		}
  
  
  	/**
  	 * An atom's ring size is the size of the smallest ring the atom is a member of.
  	 * If the atom doesn't belong to any ring the ring size is 0. If an atom is member
  	 * of rings larger than 7 members and if the mode parameter of the constructor
  	 * didn't include LARGE_RINGS, then the returned ring size is also 0.
  	 * @param atom
  	 * @return ring size or 0
  	 */
  	public int getAtomRingSize(int atom) {
  		return mAtomRingFeatures[atom] & FEATURES_RING_SIZE;
  		}
  
  
  	/**
  	 * A bond's ring size is the size of the smallest ring the bond is a member of.
  	 * If the bond doesn't belong to any ring the ring size is 0. If a bond is member
  	 * of rings larger than 7 members and if the mode parameter of the constructor
  	 * didn't include LARGE_RINGS, then the returned ring size is also 0.
  	 * @param bond
  	 * @return ring size or 0
  	 */
  	public int getBondRingSize(int bond) {
  		return mBondRingFeatures[bond] & FEATURES_RING_SIZE;
  		}
  
  
  	private void addSmallRingsToSet(int closureBond, boolean[] isConfirmedChainAtom) {
  		int[] graphAtom = new int[mMaxSmallRingSize];
  		int[] connIndex = new int[mMaxSmallRingSize];
  		boolean[] isUsed = new boolean[mMol.getAtoms()];
  
  		int atom1 = mMol.getBondAtom(0, closureBond);
  		int atom2 = mMol.getBondAtom(1, closureBond);
  
  		graphAtom[0] = atom1;
  		graphAtom[1] = atom2;
  		connIndex[1] = -1;
  		isUsed[atom2] = true;
  		int current = 1;
  
  		while(current >= 1) {
  			connIndex[current]++;
  			if (connIndex[current] == mMol.getConnAtoms(graphAtom[current])) {
  				isUsed[graphAtom[current]] = false;
  				current--;
  				continue;
  				}
  
  			int candidate = mMol.getConnAtom(graphAtom[current], connIndex[current]);
  			if (isUsed[candidate] || isConfirmedChainAtom[candidate])
  				continue;
  
  			if (candidate == atom1 && current > 1) {
  				addRingIfNew(graphAtom, current+1);
  
  				// if we have already such many rings, we only collect the smallest ring to avoid a combinatorial explosion
  				if (mRingAtomSet.size() >= MAX_SMALL_RING_COUNT)
  					return;
  
  				continue;
  				}
  
  			if (current+1 < mMaxSmallRingSize) {
  				current++;
  				graphAtom[current] = candidate;
  				isUsed[candidate] = true;
  				connIndex[current] = -1;
  				}
  			}
  		}
  
  
  	private void addRingIfNew(int ringAtom[], int ringSize) {
  		int lowAtom = mMol.getMaxAtoms();
  		int lowIndex = 0;
  		for (int i=0; i<ringSize; i++) {
  			if (lowAtom > ringAtom[i]) {
  				lowAtom = ringAtom[i];
  				lowIndex = i;
  				}
  			}
  
  		int[] sortedRing = new int[ringSize];
  		int leftIndex = (lowIndex > 0) ? lowIndex - 1 : ringSize - 1;
  		int rightIndex = (lowIndex < ringSize - 1) ? lowIndex + 1 : 0;
  		boolean inverse = (ringAtom[leftIndex] < ringAtom[rightIndex]);
  		for (int i=0; i<ringSize; i++) {
  			sortedRing[i] = ringAtom[lowIndex];
  			if (inverse) {
  				if (--lowIndex < 0)
  					lowIndex = ringSize - 1;
  				}
  			else {
  				if (++lowIndex == ringSize)
  					lowIndex = 0;
  				}
  			}
  
  		for (int[] ringOfSet : mRingAtomSet) {
  			if (ringOfSet.length == ringSize) {
  				boolean equal = true;
  				for (int j = 0; j<ringSize; j++) {
  					if (ringOfSet[j] != sortedRing[j]) {
  						equal = false;
  						break;
  					}
  				}
  				if (equal)
  					return;
  			}
  		}
  
  		mRingAtomSet.add(sortedRing);
  		int[] ringBond = getRingBonds(sortedRing);
  		mRingBondSet.add(ringBond);
  
  		updateRingSize(sortedRing, ringBond);
  		}
  
  
  	public int getSize() {
  		return mRingAtomSet.size();
  		}
  
  
  	/**
  	 * Returns normalised list of all ring atoms such that the smallest atom index is first,
  	 * followed by that one of its neighbours, which has the smaller atom index,
  	 * continuing with all remaining ring atoms in the direction defined by the first two.
  	 * @param ringNo
  	 * @return
  	 */
  	public int[] getRingAtoms(int ringNo) {
  		return mRingAtomSet.get(ringNo);
  		}
  
  
  	/**
  	 * Returns all ring bonds starting with the bond connecting ringAtom[0] and ringAtom[1]
  	 * and continuing with the neighbour ring bonds in the same direction that getRingAtom()
  	 * returns ring atoms.
  	 * @param ringNo
  	 * @return
  	 */
  	public int[] getRingBonds(int ringNo) {
  		return mRingBondSet.get(ringNo);
  		}
  
  
  	public int getRingSize(int ringNo) {
  		return mRingBondSet.get(ringNo).length;
  		}
  
  
  	/**
  	 * Return whether the ring is considered aromatic.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param ringNo
  	 * @return
  	 */
  	public boolean isAromatic(int ringNo) {
  		return mIsAromatic[ringNo];
  		}
  
  
  	/**
  	 * Return whether the atom is member if an aromatic ring.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param atom
  	 * @return
  	 */
  	public boolean isAromaticAtom(int atom) {
  		return (mAtomRingFeatures[atom] & FEATURES_AROMATIC) != 0;
  		}
  
  
  	/**
  	 * Return whether the atom is member if a delocalized ring.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param atom
  	 * @return
  	 */
  	public boolean isDelocalizedAtom(int atom) {
  		return (mAtomRingFeatures[atom] & FEATURES_DELOCALIZED) != 0;
  		}
  
  
  	/**
  	 * Return whether the atom is member if a hetero-aromatic ring.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param atom
  	 * @return
  	 */
  	public boolean isHeteroAromaticAtom(int atom) {
  		return (mAtomRingFeatures[atom] & FEATURES_HETERO_AROMATIC) != 0;
  		}
  
  
  	/**
  	 * Return whether the bond is considered aromatic.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param bond
  	 * @return
  	 */
  	public boolean isAromaticBond(int bond) {
  		return (mBondRingFeatures[bond] & FEATURES_AROMATIC) != 0;
  		}
  
  
  	/**
  	 * Return whether the bond is member if a delocalized ring.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param bond
  	 * @return
  	 */
  	public boolean isDelocalizedBond(int bond) {
  		return (mBondRingFeatures[bond] & FEATURES_DELOCALIZED) != 0;
  		}
  
  
  	/**
  	 * Return whether the bond is member if a hetero-aromatic ring.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param bond
  	 * @return
  	 */
  	public boolean isHeteroAromaticBond(int bond) {
  		return (mBondRingFeatures[bond] & FEATURES_HETERO_AROMATIC) != 0;
  		}
  
  
  	/**
  	 * Return whether the ring is considered delocalized, which are 6-membered
  	 * aromatic rings with no preference concerning where the double bonds are located.
  	 * Pyrrole bonds are not considered delocalized.
  	 * If the mode parameter passed to the constructor didn't include AROMATICITY, then
  	 * a NullPointerException is raised.
  	 * @param ringNo
  	 * @return
  	 */
  	public boolean isDelocalized(int ringNo) {
  		return mIsDelocalized[ringNo];
  		}
  
  
  	public int getAtomIndex(int ringNo, int atom) {
  		int[] ringAtom = mRingAtomSet.get(ringNo);
  		for (int i=0; i<ringAtom.length; i++)
  			if (atom == ringAtom[i])
  				return i;
  
  		return -1;
  		}
  
  	
  	public int getBondIndex(int ringNo, int bond) {
  		int[] ringBond = mRingBondSet.get(ringNo);
  		for (int i=0; i<ringBond.length; i++)
  			if (bond == ringBond[i])
  				return i;
  
  		return -1;
  		}
  
  
  	/**
  	 * Adds or subtracts the ring size from index to move it
  	 * into the valid range from 0 to ringSize-1.
  	 * @param ringNo
  	 * @param index
  	 * @return
  	 */
  	public int validateMemberIndex(int ringNo, int index) {
  		int ringSize = mRingBondSet.get(ringNo).length;
  		while (index >= ringSize)
  			index -= ringSize;
  		while (index < 0)
  			index += ringSize;
  		return index;
  		}
  
  	/**
  	 * Returns the position of the electron pair providing hetero atom
  	 * or carbenium atom in case of 5-membered, respective 7-membered
  	 * aromatic ring.
  	 * @param ringNo
  	 * @return position index referring to ringAtom array
  	 */
  	public int getHeteroPosition(int ringNo) {
  		return mHeteroPosition[ringNo];
  		}
  
  
  	public boolean isAtomMember(int ringNo, int atom) {
          for (int ringAtom : mRingAtomSet.get(ringNo))
              if (atom == ringAtom)
                  return true;
  
  		return false;
  		}
  
  
  	public boolean isBondMember(int ringNo, int bond) {
          for (int ringBond : mRingBondSet.get(ringNo))
              if (bond == ringBond)
                  return true;
  
  		return false;
  		}
  
  
  	/**
  	 * brute force method to check, whether and which ring is shared by two bonds
  	 * @param bond1
  	 * @param bond2
  	 * @return -1 if bond1 and bond2 don't share a common ring
  	 */
  	public int getSharedRing(int bond1, int bond2) {
  		for (int i=0; i<mRingBondSet.size(); i++)
  			if (isBondMember(i, bond1) && isBondMember(i, bond2))
  				return i;
  		return -1;
  		}
  
  	private void updateRingSize(int[] ringAtom, int[] ringBond) {
  		int ringSize = ringAtom.length;
  
          for (int atom : ringAtom) {
              int currentSize = mAtomRingFeatures[atom] & FEATURES_RING_SIZE;
              if (currentSize == 0 || currentSize > ringSize) {
                  mAtomRingFeatures[atom] &= ~FEATURES_RING_SIZE;
                  mAtomRingFeatures[atom] |= ringSize;
              }
          }
  
  		for (int bond : ringBond) {
  			int currentSize = mBondRingFeatures[bond] & FEATURES_RING_SIZE;
  			if (currentSize == 0 || currentSize>ringSize) {
  				mBondRingFeatures[bond] &= ~FEATURES_RING_SIZE;
  				mBondRingFeatures[bond] |= ringSize;
  				}
  			}
  		}
  
  	private void updateAromaticity() {
  		for (int ring=0; ring<mIsAromatic.length; ring++) {
  			if (mIsAromatic[ring]) {
  				boolean isHeteroAromatic = false;
  				for (int atom:mRingAtomSet.get(ring)) {
  					mAtomRingFeatures[atom] |= FEATURES_AROMATIC;
  					if (qualifiesAsHeteroAtom(atom))
  						isHeteroAromatic = true;
  					}
  				for (int bond:mRingBondSet.get(ring))
  					mBondRingFeatures[bond] |= FEATURES_AROMATIC;
  
  				if (mIsDelocalized[ring]) {
  					for (int atom:mRingAtomSet.get(ring))
  						mAtomRingFeatures[atom] |= FEATURES_DELOCALIZED;
  					for (int bond:mRingBondSet.get(ring))
  						mBondRingFeatures[bond] |= FEATURES_DELOCALIZED;
  					}
  
  				if (isHeteroAromatic) {
  					for (int atom:mRingAtomSet.get(ring))
  						mAtomRingFeatures[atom] |= FEATURES_HETERO_AROMATIC;
  					for (int bond:mRingBondSet.get(ring))
  						mBondRingFeatures[bond] |= FEATURES_HETERO_AROMATIC;
  					}
  				}
  			}
  
  		for (int bond : mAzuleneBridgeBonds)
  			mBondRingFeatures[bond] = 0;
  		}
  
  	private boolean qualifiesAsHeteroAtom(int atom) {
  		if (mMol.isFragment()) {
  			if ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFAny) != 0)
  				return false;
  			int[] atomList = mMol.getAtomList(atom);
  			if (atomList != null) {
  				for (int atomicNo : atomList)
  					if (!Molecule.isAtomicNoElectronegative(atomicNo))
  						return false;
  				return true;
  				}
  			}
  		return Molecule.isAtomicNoElectronegative(mMol.getAtomicNo(atom));
  		}
  
  	private int[] getRingBonds(int[] ringAtom) {
  		int ringAtoms = ringAtom.length;
  		int[] ringBond = new int[ringAtoms];
  		for (int i=0; i<ringAtoms; i++) {
  			int atom = (i == ringAtoms - 1) ? ringAtom[0] : ringAtom[i+1];
  			for (int j=0; j<mMol.getConnAtoms(ringAtom[i]); j++) {
  				if (mMol.getConnAtom(ringAtom[i],j) == atom) {
  					ringBond[i] = mMol.getConnBond(ringAtom[i],j);
  					break;
  					}
  				}
  			}
  		return ringBond;
  		}
  
  
  	/**
  	 *
  	 * @param isAromatic empty array sizes as getSize()
  	 * @param isDelocalized
  	 * @param heteroPosition
  	 * @param includeTautomericBonds whether to treat non-methylated amide/thio-amide bonds as pi-bonds
  	 */
  	public void determineAromaticity(boolean[] isAromatic, boolean[] isDelocalized, int[] heteroPosition,
  									  boolean includeTautomericBonds) {
  		int[][] annelatedRing = new int[mRingAtomSet.size()][];
  		for (int i=0; i<mRingAtomSet.size(); i++) {
  			annelatedRing[i] = new int[mRingAtomSet.get(i).length];
  			for (int j=0; j<mRingAtomSet.get(i).length; j++)
  				annelatedRing[i][j] = -1;
  			}
  
  		int[] ringMembership = new int[mMol.getBonds()];
  		for (int ring=0; ring<mRingBondSet.size(); ring++) {
  			int[] ringBond = mRingBondSet.get(ring);
  			if (ringBond.length == 3 || (ringBond.length >= 5 && ringBond.length <= 7)) {
  				for (int i=0; i<ringBond.length; i++) {
  					int bond = ringBond[i];
  					if (mMol.getConnAtoms(mMol.getBondAtom(0, bond)) == 3
  					 && mMol.getConnAtoms(mMol.getBondAtom(1, bond)) == 3) {
  						if (ringMembership[bond] > 0) {
  							annelatedRing[ringMembership[bond] >>> 16]
  										 [ringMembership[bond] & 0x7FFF] = ring;
  							annelatedRing[ring][i] = (ringMembership[bond] >>> 16);
  							}
  						else {
  							ringMembership[bond] = (ring << 16) + 0x8000 + i;
  							}
  						}
  					}
  				}
  			}
  
  		boolean[] aromaticityHandled = new boolean[mRingAtomSet.size()];
  		int ringsHandled = 0;
  		int lastRingsHandled = -1;
  		while (ringsHandled > lastRingsHandled) {
  			lastRingsHandled = ringsHandled;
  			for (int ring=0; ring<mRingAtomSet.size(); ring++) {
  				if (!aromaticityHandled[ring]) {
  					if (determineAromaticity(ring, annelatedRing, aromaticityHandled,
  							isAromatic, isDelocalized, heteroPosition, includeTautomericBonds)) {
  						aromaticityHandled[ring] = true;
  						ringsHandled++;
  						}
  					}
  				}
  			}
  		}
  
  	/**
  	 * @param ringNo
  	 * @param annelatedRing
  	 * @param aromaticityHandled
  	 * @param isAromatic
  	 * @param isDelocalized
  	 * @param heteroPosition
  	 * @param includeTautomericBonds
  	 * @return true if it can successfully determine and set the ring's aromaticity
  	 */
  	private boolean determineAromaticity(int ringNo, int[][] annelatedRing, boolean[] aromaticityHandled,
  										 boolean []isAromatic, boolean[] isDelocalized, int[] heteroPosition,
  										 boolean includeTautomericBonds) {
  		int[] ringAtom = mRingAtomSet.get(ringNo);
  		for (int atom:ringAtom)
  			if (!qualifiesAsAromaticAtom(atom))
  				return true;
  
  		int[] ringBond = mRingBondSet.get(ringNo);
  		int ringBonds = ringBond.length;
  
  		int bondSequence = 0;
  		int aromaticButNotDelocalizedSequence = 0;
  		boolean unhandledAnnelatedRingFound = false;
  		for (int i=0; i<ringBonds; i++) {
  			bondSequence <<= 1;
  			aromaticButNotDelocalizedSequence <<= 1;
  			if (qualifiesAsPiBond(ringBond[i])) {
  				bondSequence |= 1;
  				}
  			else if (includeTautomericBonds && qualifiesAsAmideTypeBond(ringBond[i])) {
  				bondSequence |= 1;
  				aromaticButNotDelocalizedSequence |= 1;
  				}
  			else {
  				int annelated = annelatedRing[ringNo][i];
  				if (annelated != -1) {
  					if (aromaticityHandled[annelated]) {
  						if (isAromatic[annelated]) {
  							bondSequence |= 1;
  							if (!isDelocalized[annelated])
  								aromaticButNotDelocalizedSequence |= 1;
  							}
  						}
  					else {
  						unhandledAnnelatedRingFound = true;
  						}
  					}
  				}
  			}
  
  		boolean hasDelocalizationLeak = false;
  		switch (ringBonds) {
  		case 3:
  			final int[] cSequence3Ring = {
  				2,	 // 010
  				1,	 // 001
  				4 }; // 100
  			hasDelocalizationLeak = true;
  			for (int carbeniumPosition=0; carbeniumPosition<3; carbeniumPosition++) {
  				if ((bondSequence & cSequence3Ring[carbeniumPosition]) == cSequence3Ring[carbeniumPosition]) {
  					if ((mMol.getAtomicNo(ringAtom[carbeniumPosition]) == 6
  							&& mMol.getAtomCharge(ringAtom[carbeniumPosition]) == 1)
  							|| (mMol.getAtomicNo(ringAtom[carbeniumPosition]) == 5
  							&& mMol.getAtomCharge(ringAtom[carbeniumPosition]) == 0)) {
  						isAromatic[ringNo] = true;
  						heteroPosition[ringNo] = carbeniumPosition;
  						if ((aromaticButNotDelocalizedSequence & cSequence3Ring[carbeniumPosition]) == 0)
  							hasDelocalizationLeak = false;
  						}
  					}
  				}
  			break;
  		case 5:
  			final int[] cSequence5Ring = {
  			   10,	// 01010
  				5,	// 00101
  			   18,	// 10010
  				9,	// 01001
  			   20 };// 01010
  			hasDelocalizationLeak = true;
  			for (int position=0; position<5; position++) {
  				if ((bondSequence & cSequence5Ring[position]) == cSequence5Ring[position]) {
  					switch (mMol.getAtomicNo(ringAtom[position])) {
  					case 6:
  						if (mMol.getAtomCharge(ringAtom[position]) == -1) {
  							isAromatic[ringNo] = true;
  							heteroPosition[ringNo] = position;
  							if ((aromaticButNotDelocalizedSequence & cSequence5Ring[position]) == 0)
  								hasDelocalizationLeak = false;
  							}
  						break;
  					case 7:
  						if (mMol.getAtomCharge(ringAtom[position]) <= 0) {
  							isAromatic[ringNo] = true;
  							heteroPosition[ringNo] = position;
  							}
  						break;
  					case 8:
  						isAromatic[ringNo] = true;
  						heteroPosition[ringNo] = position;
  						break;
  					case 16:
  					case 34:
  					case 52:
  						if (mMol.getConnAtoms(ringAtom[position]) == 2) {
  							isAromatic[ringNo] = true;
  							heteroPosition[ringNo] = position;
  							}
  						break;
  						}
  					}
  				}
  			break;
  		case 6:
  			hasDelocalizationLeak = true;
  			if ((bondSequence & 21) == 21) {   // 010101
  				isAromatic[ringNo] = true;
  				if ((aromaticButNotDelocalizedSequence & 21) == 0)
  					hasDelocalizationLeak = false;
  				}
  			if ((bondSequence & 42) == 42) {   // 101010
  				isAromatic[ringNo] = true;
  				if ((aromaticButNotDelocalizedSequence & 42) == 0)
  					hasDelocalizationLeak = false;
  				}
  			break;
  		case 7:
  			final int[] cSequence7Ring = {
  				   42,	// 0101010
  				   21,	// 0010101
  				   74,	// 1001010
  				   37,	// 0100101
  				   82,	// 1010010
  				   41,	// 0101001
  				   84 };// 1010100
  			hasDelocalizationLeak = true;
  			for (int carbeniumPosition=0; carbeniumPosition<7; carbeniumPosition++) {
  				if ((bondSequence & cSequence7Ring[carbeniumPosition]) == cSequence7Ring[carbeniumPosition]) {
  					if ((mMol.getAtomicNo(ringAtom[carbeniumPosition]) == 6
  					  && (mMol.getAtomCharge(ringAtom[carbeniumPosition]) == 1
  					|| (includeTautomericBonds && hasOxo(ringAtom[carbeniumPosition]))))
  					 || (mMol.getAtomicNo(ringAtom[carbeniumPosition]) == 5
  					  && mMol.getAtomCharge(ringAtom[carbeniumPosition]) == 0)) {
  						isAromatic[ringNo] = true;
  						heteroPosition[ringNo] = carbeniumPosition;
  						if ((aromaticButNotDelocalizedSequence & cSequence7Ring[carbeniumPosition]) == 0)
  							hasDelocalizationLeak = false;
  						}
  					else {	// Check for Azulene-like annelated rings and in case of success mark both rings as aromatic and delocalized,
  							// but put shared bond into a list to remove aromaticity from it later.
  						for (int i=0; i<2; i++) {
  							int sharedBondIndex = (carbeniumPosition+6+i) % 7;
  							int sharedBond = ringBond[sharedBondIndex];
  							int sharedRing = annelatedRing[ringNo][sharedBondIndex];
  							if (sharedRing != -1 && !isAromatic[sharedRing] && getRingSize(sharedRing) == 5) {
  								int[] sharedRingBond = mRingBondSet.get(sharedRing);
  								int index = 0;
  								while (sharedRingBond[index] != sharedBond)
  									index++;
  								boolean isPiBond = false;
  								int piBondCount = 0;
  								for (int j=1; j<=4; j++) {
  									int bond = sharedRingBond[(index+j) % 5];
  									boolean nextIsPiBond = qualifiesAsPiBond(bond)
  										|| (includeTautomericBonds && qualifiesAsAmideTypeBond(bond));
  									if (nextIsPiBond && isPiBond) {	// in the annelated ring we expect 2 pi-bonds, which don't touch
  										piBondCount = 0;
  										break;
  										}
  									isPiBond = nextIsPiBond;
  									if (isPiBond)
  										piBondCount++;
  									}
  								if (piBondCount == 2) {
  									isAromatic[ringNo] = true;
  									isAromatic[sharedRing] = true;
  									isDelocalized[ringNo] = true;
  									isDelocalized[sharedRing] = true;
  									mAzuleneBridgeBonds.add(sharedBond);
  									break;
  									}
  								}
  							}
  						}
  					}
  				}
  			break;
  			}
  		
  		if (isAromatic[ringNo] && !hasDelocalizationLeak)
  			isDelocalized[ringNo] = true;
  
  		if (isAromatic[ringNo])
  			return true;
  
  		return !unhandledAnnelatedRingFound;
  		}
  
  	private boolean hasOxo(int carbonAtom) {
  		for (int i=0; i<mMol.getConnAtoms(carbonAtom); i++)
  			if (mMol.getConnBondOrder(carbonAtom, i) == 2
  			 && mMol.getAtomicNo(mMol.getConnAtom(carbonAtom, i)) == 8)
  				return true;
  		return false;
  		}
  
  	private boolean qualifiesAsPiBond(int bond) {
  		return (mMol.getBondOrder(bond) > 1
  			 || mMol.getBondType(bond) == Molecule.cBondTypeDelocalized);
  		}
  
  	/**
  	 * Checks, whether this bond may contribute pi-electrons from an amide-resonance
  	 * to an aromatic ring. According to M J Cook, A R Katritzky, P Linda, R D Tack
  	 * J. Chem. Soc., Perkin Trans. 2, 1972, 1295-1301
  	 * 2-pyridone and 2-pyridinethione retain most of the aromatic resonance
  	 * energy of pyridine unless the nitrogen atom is methylated.
  	 * @param bond
  	 * @return
  	 */
  	public boolean qualifiesAsAmideTypeBond(int bond) {
  		for (int i=0; i<2; i++) {
  			int atom1 = mMol.getBondAtom(i, bond);
  			if ((mMol.getAtomicNo(atom1) == 7)
  			 && mMol.getConnAtoms(atom1) == 2) {
  				int atom2 = mMol.getBondAtom(1-i, bond);
  				if (mMol.getAtomicNo(atom2) == 6) {
  					for (int j=0; j<mMol.getConnAtoms(atom2); j++) {
  						int connAtom = mMol.getConnAtom(atom2, j);
  						int connBond = mMol.getConnBond(atom2, j);
  						if ((mMol.getAtomicNo(connAtom) == 8 || mMol.getAtomicNo(connAtom) == 16)
  						 && mMol.getBondOrder(connBond) == 2
  						 && mMol.getConnAtoms(connAtom) == 1)
  							return true;
  						}
  					}
  				}
  			}
  
  		return false;
  		}
  
  	private boolean qualifiesAsAromaticAtom(int atom) {
  		// If we have a list or wildcard atom, then the atomicNo is meaningless...
  		if (mMol.isFragment()) {
  			// We consider wildcard atoms as being compatible with aromaticity
  			if ((mMol.getAtomQueryFeatures(atom) & Molecule.cAtomQFAny) != 0) {
  				// in theory, we must return false, if all atomicNos, which qualify for aromaticity,
  				// are part of the exclude list. In reality that is rather unlikely...
  				return true;
  				}
  			else {
  				int[] list = mMol.getAtomList(atom);
  				if (list != null) {
  					for (int atomicNo:list)
  						if (qualifiesAsAromaticAtomicNo(atomicNo))
  							return true;
  
  					return false;
  					}
  				}
  			}
  
  		return qualifiesAsAromaticAtomicNo(mMol.getAtomicNo(atom));
  		}
  
  	public static boolean qualifiesAsAromaticAtomicNo(int atomicNo) {
  		return atomicNo == 5
  			|| atomicNo == 6
  			|| atomicNo == 7
  			|| atomicNo == 8
  			|| atomicNo == 15   // P
  			|| atomicNo == 16   // S
  			|| atomicNo == 33   // As
  			|| atomicNo == 34;   // Se
  		}
  	}