/*
 * 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.
 */

/*
* 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.
*
*/

package com.actelion.research.chem;

import com.actelion.research.calc.ArrayUtilsCalc;
import com.actelion.research.calc.statistics.StatisticsOverview;
import com.actelion.research.calc.statistics.median.MedianStatisticFunctions;
import com.actelion.research.chem.descriptor.DescriptorEncoder;
import com.actelion.research.chem.descriptor.DescriptorHandler;
import com.actelion.research.chem.descriptor.flexophore.redgraph.SubGraphIndices;
import com.actelion.research.chem.mcs.ListWithIntVec;
import com.actelion.research.util.BurtleHasher;
import com.actelion.research.util.Formatter;
import com.actelion.research.util.datamodel.IntegerDouble;

import java.util.*;

/**
 *
 *
 * ExtendedMoleculeFunctions
 * @author Modest von Korff
 * @version 1.0
 * 2005 MvK: Start implementation
 */
public class ExtendedMoleculeFunctions {


	public static final int NUMBER_ELEMENTS = 118;

	public static final int COLOR_FOR_CENTER_SELECTION = Molecule.cAtomColorRed;


	public static final int [] arrRGroupsAtomicNo = {142,143,144,129,130,131,132,133,134,135,136,137,138,139,140,141};

	public static final String [] arrRGroupsSymbol = {"R1","R2","R3","R4","R5","R6","R7","R8","R9","R10","R11","R12","R13","R14","R15","R16"};


	public final static Coordinates getCenterGravity(ExtendedMolecule mol) {
		int n = mol.getAllAtoms();
		int [] indices = new int [n];
		for (int i = 0; i < indices.length; i++) {
			indices[i]=i;
		}
		return getCenterGravity(mol, indices);
	}

	public final static Coordinates getCenterGravity(ExtendedMolecule mol, int[] indices) {
		Coordinates c = new Coordinates();
		for (int i = 0; i < indices.length; i++) {
			c.x += mol.getAtomX(indices[i]);
			c.y += mol.getAtomY(indices[i]);
			c.z += mol.getAtomZ(indices[i]);
		}
		c.x /= indices.length;
		c.y /= indices.length;
		c.z /= indices.length;
		return c;
	}


	public static void makeSkeleton(StereoMolecule mol) {
		for (int bond=0; bond<mol.getAllBonds(); bond++)
			mol.setBondType(bond, Molecule.cBondTypeSingle);
		for (int atom=0; atom<mol.getAllAtoms(); atom++)
			mol.setAtomicNo(atom, 6);
	}

	public static void analyzeMolecule(StereoMolecule mol){
		int nAtoms = mol.getAtoms();
		for (int atom = 0; atom < nAtoms; atom++) {
			if(AtomFunctionAnalyzer.isAcidicOxygen(mol, atom)){
				System.out.println("Acidic oxygen " + atom);
			}
			if(AtomFunctionAnalyzer.isNitroGroupN(mol, atom)){
				System.out.println("Nitro group N " + atom);
			}
			if(AtomFunctionAnalyzer.isMemberOfNitroGroup(mol, atom)){
				System.out.println("Member of group " + atom);
			}
			if(AtomFunctionAnalyzer.isBasicNitrogen(mol, atom)){
				System.out.println("Basic nitrogen " + atom);
			}
		}
		System.out.println("Index\tAtomic no\tAtom type");
		for (int indexAtom = 0; indexAtom < mol.getAllAtoms(); indexAtom++) {
			long atomType=-1;
			try {
				atomType = AtomTypeCalculator.getAtomType(mol, indexAtom, AtomTypeCalculator.cPropertiesForCLogPCharges);
			} catch (Exception e) {
				e.printStackTrace();
			}
			System.out.println(indexAtom + "\t" + mol.getAtomicNo(indexAtom) + "\t" + atomType + "\t" + AtomTypeCalculator.toString(atomType));
		}
		System.out.println();
	}

	public static String getColorVal2String(Molecule mol, int indexAtom){
		int color = mol.getAtomColor(indexAtom);
		return getcAtomColor2String(color);
	}

	/**
	 *
	 * @param cAtomColor atom color value from Molecule.
	 * @return
	 */
	public static String getcAtomColor2String(int cAtomColor){
		String sColor = "";
		switch (cAtomColor) {
			case Molecule.cAtomColorNone:
				sColor = "black";
				break;
			case Molecule.cAtomColorBlue:
				sColor = "blue";
				break;
			case Molecule.cAtomColorRed:
				sColor = "red";
				break;
			case Molecule.cAtomColorGreen:
				sColor = "green";
				break;
			case Molecule.cAtomColorMagenta:
				sColor = "magenta";
				break;
			case Molecule.cAtomColorOrange:
				sColor = "orange";
				break;
			case Molecule.cAtomColorDarkGreen:
				sColor = "darkGreen";
				break;
			case Molecule.cAtomColorDarkRed:
				sColor = "darkRed";
				break;

			default:
				break;
		}
		return sColor;
	}

	/**
	 *
	 * @param mol
	 * @param liIndexAtom
	 * @param cAtomColor atom color value from Molecule.
	 * @return
	 */
	public static String getColorRecord(Molecule mol, Collection<Integer> liIndexAtom, int cAtomColor){
		StringBuilder sb = new StringBuilder();
		int cc=0;
		for (int indexAtom : liIndexAtom) {
			String sColor = getcAtomColor2String(cAtomColor);
			sColor += ":"+indexAtom;
			sb.append(sColor);
			if(cc<liIndexAtom.size()-1){
				sb.append(";");
			}
			cc++;
		}
		return sb.toString();
	}

	public static int getNumQueryAtoms(ExtendedMolecule mol, int [] arrAtomicNoQuery) {
		int hetero = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			int atomicNo = mol.getAtomicNo(i);
			for (int j = 0; j < arrAtomicNoQuery.length; j++) {
				if(arrAtomicNoQuery[j]==atomicNo){
					hetero++;
					break;
				}
			}
		}
		return hetero;
	}

	public static int getNumBondsNoHydrogen(ExtendedMolecule mol) {
		int bnds = 0;
		for (int i = 0; i < mol.getBonds(); i++) {
			int at1 = mol.getBondAtom(0,i);
			int atNo1 = mol.getAtomicNo(at1);
			int at2 = mol.getBondAtom(1,i);
			int atNo2 = mol.getAtomicNo(at2);
			if(atNo1!=1 && atNo2!=1) {
				bnds++;
			}
		}
		return bnds;
	}
	public static int getNumNonHydrogenAtoms(ExtendedMolecule mol) {
		return getNumNonHydrogenAtoms(mol, 1000);
	}
	public static int getNumNonHydrogenAtoms(ExtendedMolecule mol, int atNosNot2Consider) {
		int non = 0;
		for (int i = 0; i < mol.getAtoms(); i++) {
			int atomicNo = mol.getAtomicNo(i);
			if((atomicNo==1) || (atomicNo>=atNosNot2Consider))
				continue;
			non++;
		}
		return non;
	}
	public static int getNumCarbonAtoms(ExtendedMolecule mol) {
		int carbon = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			int atomicNo = mol.getAtomicNo(i);
			if(atomicNo==6) {
				carbon++;
			}
		}
		return carbon;
	}

	public static int getNumHeteroAtoms(ExtendedMolecule mol) {
		int hetero = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			int atomicNo = mol.getAtomicNo(i);
			if((atomicNo>1) &&
					(atomicNo != 6) &&
					(atomicNo <= NUMBER_ELEMENTS)) {
				hetero++;
			}
		}
		return hetero;
	}

	public static boolean isHetero(ExtendedMolecule mol, int indexAtom){
		boolean hetero = false;
		int atomicNo = mol.getAtomicNo(indexAtom);
		if((atomicNo>1) &&
				(atomicNo != 6) &&
				(atomicNo <= NUMBER_ELEMENTS)) {
			hetero = true;
		}
		return hetero;
	}

	public static int getNumNitroGroupN(StereoMolecule mol) {
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(AtomFunctionAnalyzer.isNitroGroupN(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumAmide(StereoMolecule mol) {
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(AtomFunctionAnalyzer.isAmide(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumCarboxy(StereoMolecule mol) {
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {

			if(isCarboxyC(mol, i)){
				n++;
			}
		}
		return n;
	}


	public static int getNumAcidicOxygen(StereoMolecule mol) {
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(AtomFunctionAnalyzer.isAcidicOxygen(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumBasicNitrogen(StereoMolecule mol) {
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(AtomFunctionAnalyzer.isBasicNitrogen(mol, i)){
				n++;
			}
		}
		return n;
	}
	public static int getNumAliphaticRingAtoms(ExtendedMolecule mol, int atomicNoQuery) {
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			int atomicNo = mol.getAtomicNo(i);

			if(mol.isRingAtom(i) && atomicNo==atomicNoQuery){
				n++;
			}
		}
		return n;
	}
	/**
	 *
	 * @param mol
	 * @return number of atoms which are not hydrogen.
	 */
	public static int getNumHeavyAtoms(ExtendedMolecule mol) {
		int heavy = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			int atomicNo = mol.getAtomicNo(i);
			if(atomicNo>1 && atomicNo <= NUMBER_ELEMENTS) {
				heavy++;
			}
		}
		return heavy;
	}

	public static int getNumAromaticAtoms(ExtendedMolecule mol) {
		int n=0;
		for (int i = 0; i < mol.getAtoms(); i++) {
			if(mol.isAromaticAtom(i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumArylAmine(StereoMolecule mol) {
		int n=0;
		for (int i = 0; i < mol.getAtoms(); i++) {
			if(AtomFunctionAnalyzer.isArylAmine(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumHeteroAromaticAtoms(ExtendedMolecule mol) {
		int n=0;
		for (int i = 0; i < mol.getAtoms(); i++) {
			if(mol.isRingAtom(i) && mol.isAromaticAtom(i) && mol.getAtomicNo(i)!= 6){
				n++;
			}
		}
		return n;
	}

	public static int getNumIsopropyl(ExtendedMolecule mol) {
		int n=0;
		for (int i = 0; i < mol.getAtoms(); i++) {
			if(mol.isRingAtom(i) && mol.isAromaticAtom(i) && mol.getAtomicNo(i)!= 6){
				n++;
			}
		}
		return n;
	}

	public static int getNumSubstructure(StereoMolecule mol, String idcodeFragment) {
		IDCodeParser parser = new IDCodeParser();
		StereoMolecule frag = parser.getCompactMolecule(idcodeFragment);
		frag.ensureHelperArrays(Molecule.cHelperRings);
		SSSearcher ssSearcher = new SSSearcher();
		ssSearcher.setMol(frag, mol);
		int nMatches = ssSearcher.findFragmentInMolecule();
		return nMatches;
	}



	public static int getBondNo(ExtendedMolecule mol, int atm1,int atm2) {
		for (int bnd=0; bnd<mol.getAllBonds(); bnd++)
			if (((mol.getBondAtom(0,bnd) == atm1)
					&& (mol.getBondAtom(1,bnd) == atm2))
					|| ((mol.getBondAtom(0,bnd) == atm2)
					&& (mol.getBondAtom(1,bnd) == atm1)))
				return bnd;
		return -1;
	}

	public static int getBondOrder(ExtendedMolecule mol, int atm1,int atm2) {
		int bndno = getBondNo(mol, atm1,atm2);
		if(bndno==-1)
			return -1;
		int bndord = mol.getBondOrder(bndno);
		return bndord;
	}

	public static int getBondType(ExtendedMolecule mol, int atm1,int atm2) {
		int bndno = getBondNo(mol, atm1,atm2);
		if(bndno==-1)
			return -1;
		int bndtype = mol.getBondType(bndno);
		return bndtype;
	}

	public static int getBondParity(ExtendedMolecule mol, int atm1,int atm2) {
		int bndno = getBondNo(mol, atm1,atm2);
		if(bndno==-1)
			return -1;
		int bndtype = mol.getBondParity(bndno);
		return bndtype;
	}
	public static boolean isPseudoParity(ExtendedMolecule mol, int atm1,int atm2) {
		int bndno = getBondNo(mol, atm1,atm2);
		if(bndno==-1)
			return false;
		return mol.isBondParityPseudo(bndno);
	}

	public static boolean deleteBond(ExtendedMolecule mol, int atm1,int atm2) {
		int bndno = getBondNo(mol, atm1,atm2);
		if(bndno==-1)
			return false;
		mol.deleteBond(bndno);
		return true;
	}

	public static String getBiggestFragmentIDCode(String idCode) {
		StereoMolecule mol = new StereoMolecule();
		new IDCodeParser(false).parse(mol, idCode);
		StereoMolecule [] frags = mol.getFragments();
		String idBiggest = idCode;
		if(frags.length > 1) {
			StereoMolecule biggest = mol;
			int maxAtoms = 0;
			for (int ii = 0; ii < frags.length; ii++) {
				if (frags[ii].getAllAtoms() > maxAtoms) {
					maxAtoms = frags[ii].getAllAtoms();
					biggest = frags[ii];
				}
			}
			Canonizer canFrag = new Canonizer(biggest);
			idBiggest = canFrag.getIDCode();
		}
		return idBiggest;
	}

	public static StereoMolecule getBiggestFragment(StereoMolecule mol) {
		StereoMolecule [] frags = mol.getFragments();
		StereoMolecule biggest = mol;
		if(frags.length > 1) {
			int maxAtoms = 0;
			for (int ii = 0; ii < frags.length; ii++) {
				if (frags[ii].getAllAtoms() > maxAtoms) {
					maxAtoms = frags[ii].getAllAtoms();
					biggest = frags[ii];
				}
			}
		}
		return biggest;
	}
	public static StereoMolecule getFragment(StereoMolecule mol, SubGraphIndices sgi) {
		StereoMolecule frag = new StereoMolecule();
		int [] a = sgi.getAtomIndices();
		boolean [] b = new boolean[mol.getAtoms()];
		for (int at : a) {
			b[at]=true;
		}
		mol.copyMoleculeByAtoms(frag, b, true, null);
		frag.ensureHelperArrays(Molecule.cHelperRings);
		return frag;
	}

	/**
	 * Replaces all hetero-atoms, except hydrogen, with carbon.
	 * @param m
	 * @return
	 */
	public static StereoMolecule getConverted2CarbonSkeleton(StereoMolecule m){
		StereoMolecule skel = new StereoMolecule(m);
		skel.ensureHelperArrays(Molecule.cHelperRings);
		for (int i = 0; i < skel.getAtoms(); i++) {

			if(skel.getAtomicNo(i)>1) {
				skel.setAtomicNo(i, 6);
			}
		}
		skel.ensureHelperArrays(Molecule.cHelperRings);
		return skel;
	}

	public static Comparator<StereoMolecule> getComparatorAtomsBonds(){
		return new Comparator<StereoMolecule>() {
			@Override
			public int compare(StereoMolecule m1, StereoMolecule m2) {
				if(m1.getAllAtoms() > m2.getAllAtoms()){
					return 1;
				}else if(m1.getAllAtoms() < m2.getAllAtoms()){
					return -1;
				} else if(m1.getAllBonds() > m2.getAllBonds()){
					return 1;
				} else if(m1.getAllBonds() < m2.getAllBonds()){
					return -1;
				}
				return 0;
			}
		};
	}

	public static boolean checkBiggestFragmentForUnwanted(StereoMolecule mol, List<Integer> liAtomicNo) {
		boolean bOk=true;
		ExtendedMolecule [] frags = mol.getFragments();
		int indexBiggestFrag = 0;
		if(frags.length > 1) {
			int maxAtoms = 0;
			for (int ii = 0; ii < frags.length; ii++) {
				if (frags[ii].getAllAtoms() > maxAtoms) {
					indexBiggestFrag = ii;
					maxAtoms = frags[ii].getAllAtoms();
				}
			}
		}
		ExtendedMolecule frag = frags[indexBiggestFrag];
		for (int i = 0; i < frag.getAllAtoms(); i++) {
			if(liAtomicNo.contains(Integer.valueOf(frag.getAtomicNo(i)))) {
				bOk=false;
				break;
			}
		}
		return bOk;
	}
	/**
	 *
	 * @param mol
	 * @param hsAtomicNo
	 * @return true if an atomic number from the hash set is found.
	 */
	public static boolean containsAtLeastOneAtomicNumbersFromHashSet(ExtendedMolecule mol, HashSet<Integer> hsAtomicNo) {
		boolean bOk=false;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(hsAtomicNo.contains(mol.getAtomicNo(i))) {
				bOk=true;
				break;
			}
		}
		return bOk;
	}

	public static boolean containsHeteroAtom(ExtendedMolecule mol, int [] arrIndexAt) {
		boolean hetero=false;
		for (int indexAt : arrIndexAt) {
			if(mol.getAtomicNo(indexAt)!=6 && mol.getAtomicNo(indexAt)!=1){
				hetero=true;
				break;
			}
		}
		return hetero;
	}

	/**
	 *
	 * @param mol
	 * @param hsAtomicNo
	 * @return true if the molecule contains an atomic number that is not in the hash set.
	 */
	public static boolean containsSolelyAtomicNumbersFromHashSet(ExtendedMolecule mol, HashSet<Integer> hsAtomicNo) {
		boolean allAtomicNosInHashset=true;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(!hsAtomicNo.contains(mol.getAtomicNo(i))) {
				allAtomicNosInHashset=false;
				break;
			}
		}
		return allAtomicNosInHashset;
	}

	/**
	 *
	 * @param molecule
	 * @param at index to specify atom in molecule
	 * @param fragment one atom must have the color: COLOR_FOR_CENTER_SELECTION.
	 * @return true if the specified atom in molecule equals the colored atom in fragment.
	 */
	public final static boolean atomAtomSubStrucMatch(StereoMolecule molecule, int at, StereoMolecule fragment) {
		boolean bMatch = false;
		SSSearcher sss = new SSSearcher();
		sss.setMol(fragment, molecule);
		int numFrags = sss.findFragmentInMolecule(SSSearcher.cCountModeOverlapping, SSSearcher.cMatchAromDBondToDelocalized);
		if(numFrags == 0)
			return false;
		List<int []> liAtomLists = sss.getMatchList();
		ArrayUtilsCalc.removeDoubletsIntOrderIndepend(liAtomLists);
		int atomMarkedInQuery = -1;
		for (int i = 0; i < fragment.getAllAtoms(); i++) {
			if(fragment.getAtomColor(i)== COLOR_FOR_CENTER_SELECTION){
				atomMarkedInQuery = i;
			}
		}
		for (Iterator<int []> iter = liAtomLists.iterator(); iter.hasNext();) {
			int[] arrInd = (int[]) iter.next();
			if(arrInd[atomMarkedInQuery] == at){
				bMatch = true;
				break;
			}
		}
		return bMatch;
	}

	public static double [][] getDistanceArray(ExtendedMolecule mol) {
		double arr[][]= new double[mol.getAllAtoms()][mol.getAllAtoms()];
		for (int i = 0; i < arr.length; i++) {
			for (int j = i+1; j < arr.length; j++) {
				double dx = mol.getAtomX(i) - mol.getAtomX(j);
				double dy = mol.getAtomY(i) - mol.getAtomY(j);
				double dz = mol.getAtomZ(i) - mol.getAtomZ(j);
				double v= Math.sqrt(dx*dx+dy*dy+dz*dz);
				arr[i][j] = v;
				arr[j][i] = v;
			}
		}
		return arr;
	}
	public static double [][] getDistanceArrayNonHAts(ExtendedMolecule mol) {
		double arr[][]= new double[mol.getAtoms()][mol.getAtoms()];
		for (int i = 0; i < arr.length; i++) {
			for (int j = i+1; j < arr.length; j++) {
				double dx = mol.getAtomX(i) - mol.getAtomX(j);
				double dy = mol.getAtomY(i) - mol.getAtomY(j);
				double dz = mol.getAtomZ(i) - mol.getAtomZ(j);
				double v= Math.sqrt(dx*dx+dy*dy+dz*dz);
				arr[i][j] = v;
				arr[j][i] = v;
			}
		}
		return arr;
	}

	public final static int [] getTopologicalDistances(int [][] topoDistMatrix, int [] at1, int [] at2) {
		int [] d = new int[at1.length*at2.length];
		int cc=0;
		for (int i = 0; i < at1.length; i++) {
			for (int j = 0; j < at2.length; j++) {
				d[cc++]=topoDistMatrix[at1[i]][at2[j]];
			}
		}
		return d;
	}
	public final static int getMedianTopologicalDistance(int [][] topoDistMatrix, int [] at1, int [] at2) {
		int [] d = getTopologicalDistances(topoDistMatrix, at1, at2);
		int medTopoDist = MedianStatisticFunctions.getMedianForInteger(d).median;
		return medTopoDist;
	}

	public static List<StereoMolecule> parseIdCodes(List<String> idcodes){
		List<StereoMolecule> mols = new ArrayList<>();
		IDCodeParser parser = new IDCodeParser();
		for (String idcode : idcodes) {
			StereoMolecule mol = parser.getCompactMolecule(idcode);
			mol.ensureHelperArrays(Molecule.cHelperRings);
			mols.add(mol);
		}

		return mols;
	}

	public final static int getTopologicalDistance(ExtendedMolecule mol, int at1, int at2) {
		int dist = 0;
		if(at1 == at2)
			return 0;
		List<Integer> liExamine = new ArrayList<Integer>();
		List<Integer> liSphere = new ArrayList<Integer>();
		List<Integer> liVisited = new ArrayList<Integer>();
		liExamine.add(Integer.valueOf(at1));
		liSphere.add(Integer.valueOf(1));
		boolean bFound = false;
		while(!liExamine.isEmpty()) {
			dist = ((Integer)liSphere.remove(0)).intValue();
			int indAtCenter = ((Integer)liExamine.remove(0)).intValue();
			liVisited.add(Integer.valueOf(indAtCenter));
			int numJNeighbors = mol.getAllConnAtoms(indAtCenter);
			for (int i = 0; i < numJNeighbors; i++) {
				int indAtNeighb = mol.getConnAtom(indAtCenter, i);
				if(indAtNeighb == at2) {
					bFound = true;
					break;
				}
				if(!liVisited.contains(Integer.valueOf(indAtNeighb))) {
					liExamine.add(Integer.valueOf(indAtNeighb));
					liSphere.add(Integer.valueOf(dist + 1));
				}
			}
			if(bFound)
				break;
		}
		if(!bFound)
			dist = -1;
		return dist;
	}

	public final static int [][] getTopologicalDistanceMatrix(StereoMolecule mol) {
		return getNumberOfBondsBetweenAtoms(mol, mol.getBonds(), null);
	}

	public static int getMaximumTopologicalDistance(int [][] topoDistance){
		int max=0;
		for (int i = 0; i < topoDistance.length; i++) {
			for (int j = i+1; j < topoDistance.length; j++) {
				if(topoDistance[i][j]>max)
					max=topoDistance[i][j];
			}
		}
		return max;
	}

	/**
	 * From Joel Freyss developed for the FFMolecule
	 * Computes a matrix of distances between all the atoms in the graph.
	 * Complexity: O(m*n*maxBonds) m = number of bonds, n = number of atoms
	 * @param mol
	 * @param maxBonds
	 * @return an array A[i][j] = nBonds if i and j are connected by less than maxBonds
	 * 						      or -1  otherwise
	 */
	public static int[][] getNumberOfBondsBetweenAtoms(StereoMolecule mol, int maxBonds, int[][] dist) {
		//Initialization of the data
		if(dist==null)  dist = new int[mol.getAtoms()][mol.getAtoms()];
		int N = dist.length;
		for(int i=0; i<N; i++) {
			dist[i][i] = 0;
			for(int j=i+1; j<N; j++) {
				dist[i][j] = dist[j][i] = -1;
			}
		}

		//Algo: Dynamic Programming
		for(int j=0; j<maxBonds; j++) { //Maximum of nBonds bonds
			for(int i=0; i<mol.getBonds(); i++) {
				int a1 = mol.getBondAtom(0, i);
				int a2 = mol.getBondAtom(1, i);
				if(a1>=N || a2>=N) continue;
				for(int a0=0; a0<N; a0++) {
					// Dynamic Programming: dist(a0,a2) = min(dist(a0, a2), dist(a0,a1)+1) [if dist(a0,a1)>0]
					if(dist[a0][a1]>=0 && (dist[a0][a2]==-1 || dist[a0][a1]+1<dist[a0][a2]) && dist[a0][a1]<maxBonds) {
						dist[a2][a0] = (dist[a0][a2] = (dist[a0][a1] + 1));
					}
					if(dist[a0][a2]>=0 && (dist[a0][a1]==-1 || dist[a0][a2]+1<dist[a0][a1]) && dist[a0][a2]<maxBonds) {
						dist[a1][a0] = (dist[a0][a1] = (dist[a0][a2] + 1));
					}
				}
			}
		}
		return dist;
	}

	/**
	 *
	 * @param mol
	 * @param atm index of atom
	 * @return false if atom is not C or if one of the neighbours is N, O, F, S, P or Cl.
	 */
	public static boolean isAliphaticAtom(StereoMolecule mol, int atm) {
		boolean aliphatic = true;

		if(mol.getAtomicNo(atm)!=6)
			return false;

		int nConn = mol.getAllConnAtoms(atm);

		for (int i = 0; i < nConn; i++) {
			int atmConn = mol.getConnAtom(atm, i);

			int atomicNoConn = mol.getAtomicNo(atmConn);

			if((atomicNoConn == 7) ||
					(atomicNoConn == 8) ||
					(atomicNoConn == 9) ||
					(atomicNoConn == 15) ||
					(atomicNoConn == 16) ||
					(atomicNoConn == 17)) {
				aliphatic = false;
				break;
			}
		}

		return aliphatic;
	}

	public static boolean isAcceptor(StereoMolecule mol, int atom) {
		boolean acceptor = true;

		if(mol.getAtomicNo(atom)!=8 && mol.getAtomicNo(atom)!=7)
			return false;

		return acceptor;
	}

	public static boolean isDonor(StereoMolecule mol, int atom) {
		boolean donor = true;

		if(mol.getAtomicNo(atom)!=8 && mol.getAtomicNo(atom)!=7)
			return false;

		if(mol.getAllHydrogens(atom)==0){
			return false;
		}

		return donor;
	}


	public static boolean isCarbonTwoValencesMinimum(StereoMolecule mol) {
		boolean twoValencesMin = true;
		int atoms = mol.getAtoms();
		for (int i = 0; i < atoms; i++) {
			int atomicNo = mol.getAtomicNo(i);
			if(atomicNo==6){
				int connected = mol.getConnAtoms(i);
				if(connected < 2){
					twoValencesMin = false;
					break;
				}
			}
		}
		return twoValencesMin;
	}

	public static boolean isCarbonOnlyConnected2Hetero(StereoMolecule mol) {
		boolean hetero = true;
		int atoms = mol.getAtoms();
		outer:
		for (int i = 0; i < atoms; i++) {
			int atomicNo = mol.getAtomicNo(i);
			if(atomicNo==6){
				int connected = mol.getConnAtoms(i);
				for (int j = 0; j < connected; j++) {
					int indexConnecetd = mol.getConnAtom(i, j);
					if(mol.getAtomicNo(indexConnecetd)==6){
						hetero = false;
						break outer;
					}
				}
				if(mol.isRingAtom(i)){
					hetero = true;
					break;
				}
			}
		}
		return hetero;
	}

	/**
	 *
	 * @param mol
	 * @param atomIndex
	 * @return true if at least one neighbour is a hetero atom.
	 */
	public static boolean isCarbonConnected2Hetero(StereoMolecule mol, int atomIndex) {
		boolean hetero = false;
		int atomicNo = mol.getAtomicNo(atomIndex);
		if(atomicNo==6){
			int connected = mol.getConnAtoms(atomIndex);
			for (int j = 0; j < connected; j++) {
				int indexConnected = mol.getConnAtom(atomIndex, j);
				if(mol.getAtomicNo(indexConnected)!=6 && mol.getAtomicNo(indexConnected)!=1){
					hetero = true;
					break;
				}
			}
		}
		return hetero;
	}
	public static boolean isOnlyLinkingAtom(StereoMolecule mol, int atomIndex) {
		if(mol.getFragments().length>1){
			throw new RuntimeException("Molecule already fragmented!");
		}
		StereoMolecule m = new StereoMolecule(mol);
		m.ensureHelperArrays(Molecule.cHelperRings);
		m.deleteAtom(atomIndex);
		m.ensureHelperArrays(Molecule.cHelperRings);
		return (m.getFragments().length>1)?true:false;
	}
	/**
	 *
	 * @param mol
	 * @param arrAtomIndex
	 * @return true if one of the atoms from arrAtomIndex is connected to a hetero atom.
	 */
	public static boolean isConnected2Hetero(StereoMolecule mol, int [] arrAtomIndex) {
		boolean hetero = false;
		for (int atomIndex : arrAtomIndex) {
			int connected = mol.getConnAtoms(atomIndex);
			for (int j = 0; j < connected; j++) {
				int indexConnected = mol.getConnAtom(atomIndex, j);
				if(mol.getAtomicNo(indexConnected)!=6 && mol.getAtomicNo(indexConnected)!=1){
					hetero = true;
					break;
				}
			}
		}
		return hetero;
	}

	public static boolean isRingInMolecule(StereoMolecule mol) {
		boolean ring = false;
		int atoms = mol.getAtoms();
		for (int i = 0; i < atoms; i++) {
			if(mol.isRingAtom(i)){
				ring = true;
				break;
			}
		}
		return ring;
	}

	/**
	 *
	 * @param mol
	 * @return true if all atoms are in a ring.
	 */
	public static boolean isRingExclusively(StereoMolecule mol) {
		boolean ring = true;
		int atoms = mol.getAtoms();
		for (int i = 0; i < atoms; i++) {
			if(!mol.isRingAtom(i)){
				ring = false;
				break;
			}
		}
		return ring;
	}

	public static boolean containsFiveBindingCarbon(StereoMolecule mol) {

		boolean five=false;
		for (int i = 0; i < mol.getAtoms(); i++) {
			if(mol.getAtomicNo(i)==6){
				int sumBO=0;
				for (int j = 0; j < mol.getConnAtoms(i); j++) {
					int bo = mol.getConnBondOrder(i,j);
					sumBO+=bo;
				}
				if(sumBO>4){
					five=true;
					break;
				}
			}
		}

		return five;
	}
	public static boolean containsFiveBindingNitrogenWithoutCharge(StereoMolecule mol) {
		boolean fiveBindingNNoPositiveCharge=false;
		for (int i = 0; i < mol.getAtoms(); i++) {
			if(mol.getAtomicNo(i)==5){
				int sumBO=0;
				for (int j = 0; j < mol.getConnAtoms(i); j++) {
					int bo = mol.getConnBondOrder(i,j);
					sumBO+=bo;
				}
				if(sumBO==5){
					if(mol.getAtomCharge(i)!=1) {
						fiveBindingNNoPositiveCharge = true;
						break;
					}
				}
			}
		}

		return fiveBindingNNoPositiveCharge;
	}

	/**
	 *
	 * @param mol
	 * @param atom
	 * @return true for cyano and iso-cyano.
	 */
	public static boolean isCyanoN(StereoMolecule mol, int atom) {
		if(mol.getAtomicNo(atom)!= 7){
			return false;
		}
		int nConn = mol.getConnAtoms(atom);
		if(nConn != 1){
			return false;
		}
		int indexConn = mol.getConnAtom(atom, 0);
		int bond = mol.getBond(atom, indexConn);
		if(mol.getBondOrder(bond) != 3){
			return false;
		}
		return true;
	}

	public static boolean isThioEther(StereoMolecule mol, int atom) {
		if(mol.getAtomicNo(atom)!= 16){
			return false;
		}
		int nConn = mol.getConnAtoms(atom);
		if(nConn != 2){
			return false;
		}
		boolean thio = true;
		for (int i = 0; i < nConn; i++) {
			int indexConn = mol.getConnAtom(atom, i);
			if(mol.getAtomicNo(indexConn) != 6){
				thio=false;
				break;
			}
		}
		return thio;
	}

	public static boolean isWildcard(StereoMolecule mol, int atom) {
		if(mol.getAtomicNo(atom) == PeriodicTable.ConnectionPoint){
			return true;
		}
		return false;
	}

	/**
	 *
	 * @param mol
	 * @param atom
	 * @return true if atom is S and at let one attached atom is O.
	 */
	public static boolean isSulfoxyGroup(StereoMolecule mol, int atom) {
		if(mol.getAtomicNo(atom)!= 16){
			return false;
		}
		int nConn = mol.getConnAtoms(atom);
		boolean oxy = false;
		for (int i = 0; i < nConn; i++) {
			int indexConn = mol.getConnAtom(atom, i);
			if(mol.getAtomicNo(indexConn) == 8){
				oxy=true;
				break;
			}
		}
		return oxy;
	}

	/**
	 *
	 * @param mol
	 * @param indexAtCentral
	 * @param arrIndexAt
	 * @return true if no connected carbon atom is in arrIndexAt
	 */
	public static boolean isIsolatedCarbon(StereoMolecule mol, int indexAtCentral, int [] arrIndexAt){

		boolean isolated=true;
		int nConnected = mol.getConnAtoms(indexAtCentral);
		boolean [] arrConnected = new boolean[mol.getAtoms()];

		for (int i = 0; i < nConnected; i++) {
			arrConnected[mol.getConnAtom(indexAtCentral,i)]=true;
		}

		for (int indexAt : arrIndexAt) {
			if(!arrConnected[indexAt]){
				continue;
			}
			if(mol.getAtomicNo(indexAt)==6){
				isolated=false;
				break;
			}
		}

		return isolated;
	}

	public static int [] extractAromaticRing(StereoMolecule mol, int [] arrIndexAt){

		RingCollection rc = mol.getRingSet();
		boolean [] arrRingMemberMarker = new boolean[mol.getAtoms()];
		int [] arrIndexAromaticRing = null;
		for (int i = 0; i < rc.getSize(); i++) {
			if(!rc.isAromatic(i)){
				continue;
			}
			Arrays.fill(arrRingMemberMarker, false);
			int [] arrRingAtoms = rc.getRingAtoms(i);
			for (int indexRingAtom : arrRingAtoms) {
				arrRingMemberMarker[indexRingAtom]=true;
			}

			int sum=0;
			for (int indexAt : arrIndexAt) {
				if(arrRingMemberMarker[indexAt]){
					sum++;
				}
			}

			if(sum==arrRingAtoms.length){
				arrIndexAromaticRing=arrRingAtoms;
				break;
			}
		}

		return arrIndexAromaticRing;
	}

	/**
	 * Counts cyano and iso-cyano
	 * @param mol
	 * @return
	 */
	public static int getNumCyanoGroups(StereoMolecule mol){
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(isCyanoN(mol, i)){
				n++;
			}
		}
		return n;
	}


	public static int getTotalCharge(StereoMolecule mol){
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			n+=mol.getAtomCharge(i);
		}
		return n;
	}
	public static int getNumAtomsCharged(StereoMolecule mol){
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(mol.getAtomCharge(i)!=0){
				n++;
			}
		}
		return n;
	}

	public static int getNumAlcoholicOxygen(StereoMolecule mol){
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(isAlcoholicOxygen(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumThioEther(StereoMolecule mol){
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(isThioEther(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumSulfOxyGroups(StereoMolecule mol){
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(isSulfoxyGroup(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static int getNumWildcards(StereoMolecule mol){
		int n = 0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(isWildcard(mol, i)){
				n++;
			}
		}
		return n;
	}

	public static boolean isAlcoholicOxygen(StereoMolecule mol, int atom) {
		if (mol.getAtomicNo(atom) != 8 || mol.isAromaticAtom(atom))
			return false;
		int nConnected = mol.getConnAtoms(atom);
		if(nConnected != 1){
			return false;
		}
		int indConn = mol.getConnAtom(atom, 0);
		if(mol.getAtomicNo(indConn) != 6){
			return false;
		}
		if(AtomFunctionAnalyzer.getNegativeNeighbourCount(mol, indConn) > 1){
			return false;
		}
		return true;
	}

	public static boolean isEtherOxygenAtAromatic(StereoMolecule mol, int atom) {
		boolean aromaticEtherO = false;
		if (mol.getAtomicNo(atom) != 8 || mol.isAromaticAtom(atom))
			return false;
		int nConnected = mol.getConnAtoms(atom);
		if(nConnected != 2){
			return false;
		}
		int indConn1 = mol.getConnAtom(atom, 0);
		int indConn2 = mol.getConnAtom(atom, 1);
		if(mol.isAromaticAtom(indConn1)){
			aromaticEtherO = true;
		} else if(mol.isAromaticAtom(indConn2)){
			aromaticEtherO = true;
		}
		return aromaticEtherO;
	}

	public static boolean isCarboxyC(StereoMolecule mol, int atom) {
		if (mol.getAtomicNo(atom) != 6 || mol.getAtomPi(atom) != 0)
			return false;
		boolean carboxyO = false;
		boolean etherO = false;
		for (int i=0; i<mol.getConnAtoms(atom); i++) {
			int conn = mol.getConnAtom(atom, i);
			if (mol.getAtomicNo(conn) == 6) {
				int bond = mol.getBond(atom, conn);
				int bo = mol.getBondOrder(bond);
				if(bo==1){
					etherO=true;
				} else if(bo==2){
					carboxyO=true;
				}
			}
		}
		boolean carboxyC=false;
		if(carboxyO && etherO){
			carboxyC=true;
		}
		return carboxyC;
	}

	public static int replaceAtoms(ExtendedMolecule [] arr, int atnoOrig,int atnoRpl) {
		int cc=0;
		for (int i = 0; i < arr.length; i++) {
			cc+=replaceAtoms(arr[i], atnoOrig,atnoRpl);
		}
		return cc;
	}

	public static int replaceAtoms(ExtendedMolecule mol, int atnoOrig,int atnoRpl) {
		int cc=0;
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			if(mol.getAtomicNo(i)==atnoOrig) {
				mol.setAtomicNo(i, atnoRpl);
				cc++;
			}
		}
		return cc;
	}
	public static void createSkeleton(ExtendedMolecule mol) {
		for (int i = 0; i < mol.getAllAtoms(); i++) {
			mol.setAtomicNo(i, 6);
		}
	}

	/**
	 * Removes all molecules that are a substructure of one of the molecules in the input list.
	 * @param liInput
	 * @return
	 */
	public static final LinkedList<StereoMolecule> removeSubStructures(List<StereoMolecule> liInput){
		SSSearcher sss = new SSSearcher();
		LinkedList<StereoMolecule> li = new LinkedList<StereoMolecule>(liInput);
		for (int i = li.size()-1; i >= 0; i--) {
			StereoMolecule mol1 = li.get(i);
			for (int j = 0; j < li.size(); j++) {
				if(i!=j){
					StereoMolecule mol2 = li.get(j);
					if(mol1.getAtoms()<mol2.getAtoms()){
						sss.setMol(mol1, mol2);
						if(sss.isFragmentInMolecule()){
							li.remove(i);
							break;
						}
					}
				}
			}
		}

		return li;
	}

	/**
	 * Deletes the substructures, if found, in the molecule.
	 * Starts with the first structure in the list.
	 * Helper arrays are ensured.
	 * @param mol
	 * @param liFragment
	 * @return
	 */
	public static StereoMolecule removeSubstructuresFromMolecule(StereoMolecule mol, List<StereoMolecule> liFragment){
		StereoMolecule molReduced = new StereoMolecule(mol);
		molReduced.ensureHelperArrays(Molecule.cHelperRings);
		SSSearcher sss = new SSSearcher();
		HashSet<Integer> hsIndexMatchingAtoms = new HashSet<Integer>();
		for (StereoMolecule frag : liFragment) {
			sss.setMol(frag, molReduced);
			if(sss.findFragmentInMolecule()>0){
				List<int[]> liMatch = sss.getMatchList();
				hsIndexMatchingAtoms.clear();
				for (int[] arrMatch : liMatch) {
					for (int i = 0; i < arrMatch.length; i++) {
						hsIndexMatchingAtoms.add(arrMatch[i]);
					}
				}
				int [] arrIndexMatchingAtomsUnique = ArrayUtilsCalc.toIntArray(hsIndexMatchingAtoms);
				molReduced.deleteAtoms(arrIndexMatchingAtomsUnique);
				molReduced.ensureHelperArrays(Molecule.cHelperRings);
			}
		}
		return molReduced;
	}

	public static StereoMolecule removeSubstructureFromMolecule(StereoMolecule mol, StereoMolecule frag){
		StereoMolecule molReduced = new StereoMolecule(mol);
		molReduced.ensureHelperArrays(Molecule.cHelperRings);
		SSSearcher sss = new SSSearcher();
		sss.setMol(frag, molReduced);
		if(sss.findFragmentInMolecule()>0){
			List<int[]> liMatch = sss.getMatchList();
			if(liMatch.size() > 1){
				throw new RuntimeException("Fragment found more than once!");

			} if(liMatch.size() == 1){
				int [] arrMatchIndex = liMatch.get(0);
				molReduced.deleteAtoms(arrMatchIndex);
				molReduced.ensureHelperArrays(Molecule.cHelperRings);
//				boolean [] arrMatchingAtom = new boolean [molReduced.getAtoms()];
//
//
//
//				for (int i = 0; i < arrMatchIndex.length; i++) {
//					arrMatchingAtom[arrMatchIndex[i]]=true;
//				}
//
//				int indexRGroup = 1;
//
//				for (int i = 0; i < arrMatchIndex.length; i++) {
//
//					int atMatch = arrMatchIndex[i];
//
//					int nConnected = molReduced.getConnAtoms(atMatch);
//
//					for (int j = 0; j < nConnected; j++) {
//
//						int atConnected = molReduced.getConnAtom(atMatch, j);
//
//						if(arrMatchingAtom[atConnected]){
//							continue;
//						}
//
//						int bond = molReduced.getBond(atMatch, nConnected);
//
//						molReduced.deleteBond(bond);
//
//						int atNoRGroup = getAtomicNoRGroup(indexRGroup++);
//
//						int indexAtRGroup = molReduced.addAtom(atNoRGroup);
//
//						molReduced.addBond(atConnected, indexAtRGroup, Molecule.cBondTypeSingle);
//
//						molReduced.ensureHelperArrays(Molecule.cHelperNeighbours);
//
//					}
//
//				}
//
			}
		}
		return molReduced;
	}

	public static StereoMolecule removeWildcards(StereoMolecule mol){
		StereoMolecule molReduced = new StereoMolecule(mol);
		molReduced.ensureHelperArrays(Molecule.cHelperRings);
		for (int i = molReduced.getAtoms()-1; i >= 0; i--) {
			if(molReduced.getAtomicNo(i)==0){
				molReduced.deleteAtom(i);
			}
		}
		molReduced.ensureHelperArrays(Molecule.cHelperRings);
		return molReduced;
	}

	/**
	 *
	 * @param mol
	 * @param bond
	 * @param minAtomsDistance minimum number bonds between the atoms.
	 * @param maxSpanAtoms minAtomsDistance + maxSpanAtoms is the maximum number bonds between the atoms.
	 */
	public static void setAtomBridge(ExtendedMolecule mol, int bond, int minAtomsDistance, int maxSpanAtoms){
		int queryFeatures = 0;
		queryFeatures |= (minAtomsDistance << Molecule.cBondQFBridgeMinShift);
		queryFeatures |= (maxSpanAtoms << Molecule.cBondQFBridgeSpanShift);
		queryFeatures &= ~Molecule.cBondQFAllBondTypes;
		mol.setBondType(bond, Molecule.cBondTypeSingle);
		mol.setBondQueryFeature(bond, Molecule.cBondQFAllFeatures, false);
		mol.setBondQueryFeature(bond, queryFeatures, true);
		mol.adaptBondTypeToQueryFeatures(bond);
	}

	public static void setColorMCS2Molecule(StereoMolecule mol, StereoMolecule molMCS){
		for (int i = 0; i < mol.getAtoms(); i++) {
			mol.setAtomColor(i, Molecule.cAtomColorNone);
		}
		SSSearcher sss = new SSSearcher();
		sss.setMol(molMCS, mol);
		if(sss.findFragmentInMolecule()>0){
			List<int[]> liMatch = sss.getMatchList();
			int [] arrIndexMatch = liMatch.get(0);
			for (int i = 0; i < arrIndexMatch.length; i++) {
				mol.setAtomColor(arrIndexMatch[i], Molecule.cAtomColorBlue);
			}
		}
	}

	public static void setColorMolecule(StereoMolecule mol, int [] arrIndexMatch){
		for (int i = 0; i < mol.getAtoms(); i++) {
			mol.setAtomColor(i, Molecule.cAtomColorNone);
		}
		for (int i = 0; i < arrIndexMatch.length; i++) {
			mol.setAtomColor(arrIndexMatch[i], Molecule.cAtomColorBlue);
		}
	}

	/**
	 *
	 * @param mol
	 * @param arrIndexBonds
	 * @param color i.e. Molecule.cAtomColorBlue
	 */
	public static void setColorMoleculeFromBondIndex(StereoMolecule mol, int [] arrIndexBonds, int color){
		for (int i = 0; i < arrIndexBonds.length; i++) {
			final int indexAtom1 = mol.getBondAtom(0, arrIndexBonds[i]);
			final int indexAtom2 = mol.getBondAtom(1, arrIndexBonds[i]);
			mol.setAtomColor(indexAtom1, color);
			mol.setAtomColor(indexAtom2, color);

		}
	}

	public static void setCoordinatesNull(StereoMolecule mol){
		for (int i = 0; i < mol.getAtoms(); i++) {
			mol.setAtomX(i,0);
			mol.setAtomY(i,0);
			mol.setAtomZ(i,0);
		}
	}

	/**
	 * Returns the colors vector for the substructure in mol.
	 * @param mol
	 * @param frag
	 * @param atomColor Molecule.cAtomColor
	 * @return
	 */
	public static String getColorVectorSubstructure(StereoMolecule mol, StereoMolecule frag, int atomColor){
		SSSearcher sss = new SSSearcher();
		sss.setMol(frag, mol);
		HashSet<Integer> hsAtomIndex = new HashSet<Integer>();
		if(sss.findFragmentInMolecule()>0){
			List<int[]> liMatch = sss.getMatchList();
			int[] arr = liMatch.get(0);
			for (int i = 0; i < arr.length; i++) {
				hsAtomIndex.add(arr[i]);
			}

//			for (int[] arr : liMatch) {
//				for (int i = 0; i < arr.length; i++) {
//					hsAtomIndex.add(arr[i]);
//				}
//			}
		}
		String sAtomColor = ExtendedMoleculeFunctions.getColorRecord(mol, hsAtomIndex, atomColor);
		return sAtomColor;
	}

	public static float getSimilarity(StereoMolecule m1, StereoMolecule m2, DescriptorHandler dh){
		Object d1 = dh.createDescriptor(m1);
		Object d2 = dh.createDescriptor(m2);
		return dh.getSimilarity(d1, d2);
	}

	/**
	 * Taken from Thomas Sander SkeletonSpheres descriptor
	 * @param mol
	 * @param rootAtom atom index to start
	 * @param depth so many spheres are taken
	 * @return Fragment containing the spheres started at rootAtom
	 */
	public static StereoMolecule getSphere(StereoMolecule mol, int rootAtom, int depth){
		mol.ensureHelperArrays(Molecule.cHelperRings);
		StereoMolecule fragment = new StereoMolecule(mol.getAtoms(), mol.getBonds());

		boolean[] atomMask = getSphereAtomMask(mol, rootAtom, depth);

		int [] atomMap = new int[atomMask.length];
		mol.copyMoleculeByAtoms(fragment, atomMask, true, atomMap);
		fragment.ensureHelperArrays(Molecule.cHelperRings);
		fragment.setFragment(true);
		for (int i = 0; i < atomMap.length; i++) {
			int indAtFrag = atomMap[i];
			if(indAtFrag==-1) continue;
			long queryFeatures = 0;
			if(mol.isRingAtom(i))
				queryFeatures |= Molecule.cAtomQFNotChain;
			else
				queryFeatures |= Molecule.cAtomQFNot2RingBonds | Molecule.cAtomQFNot3RingBonds | Molecule.cAtomQFNot4RingBonds ;
			if(mol.isAromaticAtom(i))
				queryFeatures |= Molecule.cAtomQFAromatic;
			else
				queryFeatures |= Molecule.cAtomQFNotAromatic;

			fragment.setAtomQueryFeature(indAtFrag, queryFeatures, true);
		}
		return fragment;
	}


	public static StereoMolecule getSphereWithDummyAtom(StereoMolecule mol, int rootAtom, int depth, int atNoDummy){

		mol.ensureHelperArrays(Molecule.cHelperRings);
		StereoMolecule fragment = new StereoMolecule(mol.getAtoms(), mol.getBonds());

		boolean[] atomMask = getSphereAtomMask(mol, rootAtom, depth);
		// Atoms that contained a connection to an atom outside the fragment.
		boolean[] atomMask4Dummies = new boolean[mol.getAtoms()];

		for (int i = 0; i < mol.getAtoms(); i++) {
			if(atomMask[i]){
				int nConn = mol.getConnAtoms(i);
				for (int j = 0; j < nConn; j++) {
					int indConAt = mol.getConnAtom(i,j);
					if(!atomMask[indConAt]){
						atomMask4Dummies[i]=true;
					}
				}
			}
		}
		int [] atomMap = new int[atomMask.length];
		mol.copyMoleculeByAtoms(fragment, atomMask, true, atomMap);

		int [] atomMapInvers = new int[atomMask.length];
		for (int i = 0; i < atomMap.length; i++) {
			if(atomMap[i]==-1)
				continue;
			atomMapInvers[atomMap[i]]=i;
		}

		for (int i = 0; i < atomMask4Dummies.length; i++) {
			if(atomMask4Dummies[i]){
				int indAtConn = atomMapInvers[i];
				int indAtNew = fragment.addAtom(atNoDummy);
				int bnd = fragment.addBond(indAtConn, indAtNew);
				fragment.setBondType(bnd, Molecule.cBondTypeSingle);
			}
		}
		fragment.ensureHelperArrays(Molecule.cHelperRings);
		return fragment;
	}
	public static boolean [] getSphereAtomMask(StereoMolecule mol, int rootAtom, int depth){

		mol.ensureHelperArrays(Molecule.cHelperRings);

		int[] atomList = new int[mol.getAtoms()];
		boolean[] atomMaskSphere = new boolean[mol.getAtoms()];
		boolean[] atomMask = new boolean[mol.getAtoms()];

		int min = 0;
		int max = 0;

		for (int sphere=0; sphere<depth && max<mol.getAtoms(); sphere++) {
			if (max == 0) {
				atomList[0] = rootAtom;
				atomMaskSphere[rootAtom] = true;
				max = 1;
			}
			else {
				int newMax = max;
				for (int i=min; i<max; i++) {
					int atom = atomList[i];
					for (int j=0; j<mol.getConnAtoms(atom); j++) {
						int connAtom = mol.getConnAtom(atom, j);
						if (!atomMaskSphere[connAtom]) {
							atomMaskSphere[connAtom] = true;
							atomList[newMax++] = connAtom;
						}
					}
				}
				min = max;
				max = newMax;
			}
			for (int i = 0; i < atomMaskSphere.length; i++) {
				if(atomMaskSphere[i])
					atomMask[i]=true;
			}
		}
		return atomMask;
	}


	/**
	 * Starts with 1 and goes until 16
	 * @return
	 */
	public static int getAtomicNoRGroup(int r){
		if(r > arrRGroupsAtomicNo.length){
			throw new RuntimeException("");
		}
		return arrRGroupsAtomicNo[r-1];
	}

	/**
	 * Topological center atoms are the atoms with the lowest squared sum of topological distances to all atoms.
	 * @param
	 * @return
	 */
	public final static List<Integer> getTopologicalCenter(int [][] arrTopoDist) {
		List<Integer> liTopoCenterAtoms = new ArrayList<>();
		List<IntegerDouble> li = new ArrayList<>();
		for (int i = 0; i < arrTopoDist.length; i++) {
			double sum=0;
			for (int j = 0; j < arrTopoDist.length; j++) {
				sum += arrTopoDist[i][j]*arrTopoDist[i][j];
			}
			li.add(new IntegerDouble(i,sum));
		}
		Collections.sort(li, IntegerDouble.getComparatorDouble());
		liTopoCenterAtoms.add(li.get(0).getInt());
		for (int i = 1; i < li.size(); i++) {
			if(li.get(i).getDouble()==li.get(0).getDouble()){
				liTopoCenterAtoms.add(li.get(i).getInt());
			}
		}
		return liTopoCenterAtoms;
	}

	public final static List<Integer> getTopologicalCenter(int [][] arrTopoDist, ListWithIntVec ilIndexAtoms) {
		List<Integer> liTopoCenterAtoms = new ArrayList<Integer>();

		List<IntegerDouble> li = new ArrayList<IntegerDouble>();

		for (int i = 0; i < ilIndexAtoms.size(); i++) {

			int indexAt1 = ilIndexAtoms.get(i);

			double sum=0;
			for (int j = 0; j < ilIndexAtoms.size(); j++) {

				int indexAt2 = ilIndexAtoms.get(j);

				sum += arrTopoDist[indexAt1][indexAt2]*arrTopoDist[indexAt1][indexAt2];
			}

			li.add(new IntegerDouble(indexAt1,sum));
		}

		Collections.sort(li, IntegerDouble.getComparatorDouble());

		liTopoCenterAtoms.add(li.get(0).getInt());

		for (int i = 1; i < li.size(); i++) {
			if(li.get(i).getDouble()==li.get(0).getDouble()){
				liTopoCenterAtoms.add(li.get(i).getInt());
			}
		}

		return liTopoCenterAtoms;
	}

	/**
	 * Gets the points with the maximum sum of squared topological distances to all atoms.

	 * @param arrTopoDist
	 * @return
	 */
	public final static List<Integer> getPeriphericPoints(int [][] arrTopoDist) {
		List<Integer> liPeripheric = new ArrayList<>();
		List<IntegerDouble> li = new ArrayList<>();
		for (int i = 0; i < arrTopoDist.length; i++) {
			double sum=0;
			for (int j = 0; j < arrTopoDist.length; j++) {
				sum += arrTopoDist[i][j]*arrTopoDist[i][j];
			}
			li.add(new IntegerDouble(i,sum));
		}
		Collections.sort(li, IntegerDouble.getComparatorDouble());
		Collections.reverse(li);
		liPeripheric.add(li.get(0).getInt());
		for (int i = 1; i < li.size(); i++) {
			if(li.get(i).getDouble()==li.get(0).getDouble()){
				liPeripheric.add(li.get(i).getInt());
			}
		}
		return liPeripheric;
	}

	/**
	 * Distance is the sum of squares for every atom index.
	 * Sorted in ascending order.
	 * @param arrTopoDist
	 * @return
	 */
	public final static List<IntegerDouble> getAllIndicesSortedByDistance(int [][] arrTopoDist) {
		List<IntegerDouble> li = new ArrayList<>();
		for (int i = 0; i < arrTopoDist.length; i++) {
			double sum=0;
			for (int j = 0; j < arrTopoDist.length; j++) {
				sum += arrTopoDist[i][j]*arrTopoDist[i][j];
			}
			li.add(new IntegerDouble(i,sum));
		}
		Collections.sort(li, IntegerDouble.getComparatorDouble());
		return li;
	}

	public final static String toStringCoordinates(StereoMolecule m){

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < m.getAllAtoms(); i++) {
			if(i>0)
				sb.append("\n");
			sb.append(Formatter.format2(m.getAtomX(i)));
			sb.append(", ");
			sb.append(Formatter.format2(m.getAtomY(i)));
			sb.append(", ");
			sb.append(Formatter.format2(m.getAtomZ(i)));
		}
		return sb.toString();
	}
}
