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.
  *
  */
  
  package com.actelion.research.chem;
  
  import com.actelion.research.util.DoubleFormat;
  
  import java.nio.charset.StandardCharsets;
  
  /**
   * Typically you should use IDCodeParser instead of this class. You may instantiate this class
   * if you need to avoid a dependency to the CoordinateInventor and if you pass encoded coordinates
   * together with any idcode for parsing.
   * We needed to introduce this class to avoid a cyclic dependency between the IDCodeParser and
   * the CoordinateInventor: If encoded atom coords are not given, then the IDcodeParser needs
   * to invent then in order to assign proper up-/down-bonds. The CoordinateInventor needs the
   * IDCodeParser to unpack its default template list.
   */
  public class IDCodeParserWithoutCoordinateInvention {
  	private StereoMolecule	mMol;
  	private byte[]			mDecodingBytes;
  	private	int				mIDCodeBitsAvail,mIDCodeTempData,mIDCodeBufferIndex;
  	private boolean         mNeglectSpaceDelimitedCoordinates;
  
  	protected boolean ensure2DCoordinates() {
  		return false;
  		}
  
  	/**
  	 * IDCodeParsers allow passing idcode and coordinates as one String with a space
  	 * as separator in between. If an idcode is followed by a space and more, and if
  	 * the following shall not be interpreted as encoded coordinates, then call this
  	 * method after instantiation.
  	 */
  	public void neglectSpaceDelimitedCoordinates() {
  		mNeglectSpaceDelimitedCoordinates = true;
  		}
  
  	/**
  	 * Creates and returns a molecule from the idcode with its atom and bond arrays being
  	 * just as large as needed to hold the molecule. Use this to conserve memory if no
  	 * atoms or bonds are added to the molecule afterwards. This version of the method
  	 * allows to pass idcode and atom coordinates in one String object.
  	 * @param idcode null or idcode, which may contain coordinates separated by a space character
  	 * @return
  	 */
  	public StereoMolecule getCompactMolecule(String idcode) {
  		return (idcode == null || idcode.isEmpty()) ? null : getCompactMolecule(idcode.getBytes(StandardCharsets.UTF_8));
  		}
  
  	/**
  	 * Creates and returns a molecule from the idcode with its atom and bond arrays being
  	 * just as large as needed to hold the molecule. Use this to conserve memory if no
  	 * atoms or bonds are added to the molecule afterwards.
  	 * @param idcode may be null
  	 * @return
  	 */
  	public StereoMolecule getCompactMolecule(byte[] idcode) {
  		if (idcode == null || idcode.length == 0)
  			return null;
  
  		for (int i=2; i<idcode.length-2; i++)
  			if (idcode[i] == ' ')
  				return getCompactMolecule(idcode, idcode, 0, i+1);
  
  		return getCompactMolecule(idcode, null);
  		}
  
  	/**
  	 * Creates and returns a molecule from the idcode with its atom and bond arrays being
  	 * just as large as needed to hold the molecule. Use this to conserve memory if no
  	 * atoms or bonds are added to the molecule afterwards.
  	 * @param idcode may be null
  	 * @param coordinates may be null
  	 * @return
  	 */
  	public StereoMolecule getCompactMolecule(String idcode, String coordinates) {
  		return (idcode == null) ? null : getCompactMolecule(idcode.getBytes(StandardCharsets.UTF_8),
  							(coordinates == null) ? null : coordinates.getBytes(StandardCharsets.UTF_8));
  		}
  
  	/**
  	 * Creates and returns a molecule from the idcode with its atom and bond arrays being
  	 * just as large as needed to hold the molecule. Use this to conserve memory if no
  	 * atoms or bonds are added to the molecule afterwards.
  	 * @param idcode may be null
  	 * @param coordinates may be null
  	 * @return
  	 */
  	public StereoMolecule getCompactMolecule(byte[] idcode, byte[] coordinates) {
  		return getCompactMolecule(idcode, coordinates, 0, 0);
  		}
  
  	public StereoMolecule getCompactMolecule(byte[] idcode, int idcodeStart) {
  		return getCompactMolecule(idcode, null, idcodeStart, -1);
  		}
  
  	public StereoMolecule getCompactMolecule(byte[] idcode, byte[] coordinates, int idcodeStart, int coordsStart) {
  		if (idcode == null)
  			return null;
  
  		decodeBitsStart(idcode, idcodeStart);
  		int abits = decodeBits(4);
  		int bbits = decodeBits(4);
  
  		if (abits > 8)	// abits is the version number
  			abits = bbits;
  
  		int allAtoms = decodeBits(abits);
  		int allBonds = decodeBits(bbits);
  
  		StereoMolecule mol = new StereoMolecule(allAtoms, allBonds);
  		parse(mol, idcode, coordinates, idcodeStart, coordsStart);
  		return mol;
  		}
  
  	/**
  	 * Parses the idcode and populates the given molecule to represent the passed idcode.
  	 * This version of the method allows to pass idcode and atom coordinates in one String object.
  	 * @param mol molecule object to be filled with the idcode content
  	 * @param idcode null or idcode, which may contain coordinates separated by a space character
  	 */
  	public void parse(StereoMolecule mol, String idcode) {
  		if (idcode == null || idcode.isEmpty()) {
  			parse(mol, (byte[])null, (byte[])null);
  			return;
  			}
  
  		int index = idcode.indexOf(' ');
  		if (index > 0 && index < idcode.length()-1)
  			parse(mol, idcode.substring(0, index).getBytes(StandardCharsets.UTF_8), idcode.substring(index+1).getBytes(StandardCharsets.UTF_8));
  		else
  			parse(mol, idcode.getBytes(StandardCharsets.UTF_8), null);
  		}
  
  	/**
  	 * Parses the idcode and populates the given molecule to represent the passed idcode.
  	 * @param mol molecule object to be filled with the idcode content
  	 * @param idcode null or valid idcode optionally concatenates with SPACE and encoded coordinates
  	 */
  	public void parse(StereoMolecule mol, byte[] idcode) {
  		parse(mol, idcode, null);
  		}
  
  	/**
  	 * Parses the idcode and populates the given molecule to represent the passed idcode.
  	 * @param mol molecule object to be filled with the idcode content
  	 * @param idcode may be null
  	 * @param coordinates may be null
  	 */
  	public void parse(StereoMolecule mol, String idcode, String coordinates) {
  		byte[] idcodeBytes = (idcode == null) ? null : idcode.getBytes(StandardCharsets.UTF_8);
  		byte[] coordinateBytes = (coordinates == null) ? null : coordinates.getBytes(StandardCharsets.UTF_8);
  		parse(mol, idcodeBytes, coordinateBytes);
  		}
  
  	/**
  	 * Parses the idcode and populates the given molecule to represent the passed idcode.
  	 * @param mol molecule object to be filled with the idcode content
  	 * @param idcode may be null
  	 * @param coordinates may be null
  	 */
  	public void parse(StereoMolecule mol, byte[] idcode, byte[] coordinates) {
  		if (idcode == null || idcode.length == 0) {
  			mol.clear();
  			return;
  			}
  
  		parse(mol, idcode, coordinates, 0, 0);
  		}
  
  	/**
  	 * Parses the idcode and populates the given molecule to represent the passed idcode.
  	 * @param mol molecule object to be filled with the idcode content
  	 * @param idcode may be null
  	 * @param idcodeStart offset in idcode array to first idcode byte
  	 */
  	public void parse(StereoMolecule mol, byte[] idcode, int idcodeStart) {
  		parse(mol, idcode, null, idcodeStart, -1);
  		}
  
  	/**
  	 * Parses the idcode and populates the given molecule to represent the passed idcode.
  	 * @param mol molecule object to be filled with the idcode content
  	 * @param idcode may be null
  	 * @param coordinates may be null
  	 * @param idcodeStart offset in idcode array to first idcode byte
  	 * @param coordsStart offset in coordinates array to first coords byte
  	 */
  	public void parse(StereoMolecule mol, byte[] idcode, byte[] coordinates, int idcodeStart, int coordsStart) {
  		mol.clear();
  
  		if (idcode==null || idcodeStart < 0 || idcodeStart >= idcode.length)
  			return;
  
  		mMol = mol;
  		int version = Canonizer.cIDCodeVersion2;
  
  		if (coordinates != null && (coordsStart < 0 || coordsStart >= coordinates.length))
  			coordinates = null;
  
  		decodeBitsStart(idcode, idcodeStart);
  		int abits = decodeBits(4);
  		int bbits = decodeBits(4);
  
  		if (abits > 8) {	// abits is the version number
  			version = abits;
  			abits = bbits;
  			}
  
  		if (abits == 0) {
  			mMol.setFragment(decodeBits(1) == 1);
  			return;
  			}
  
  		int allAtoms = decodeBits(abits);
  		int allBonds = decodeBits(bbits);
  		int nitrogens = decodeBits(abits);
  		int oxygens = decodeBits(abits);
  		int otherAtoms = decodeBits(abits);
  		int chargedAtoms = decodeBits(abits);
  		for (int atom=0; atom<allAtoms; atom++)
  			mMol.addAtom(6);
  		for (int i=0; i<nitrogens; i++)
  			mMol.setAtomicNo(decodeBits(abits), 7);
  		for (int i=0; i<oxygens; i++)
  			mMol.setAtomicNo(decodeBits(abits), 8);
  		for (int i=0; i<otherAtoms; i++)
  			mMol.setAtomicNo(decodeBits(abits),
  							 decodeBits(8));
  		for (int i=0; i<chargedAtoms; i++)
  			mMol.setAtomCharge(decodeBits(abits),
  							   decodeBits(4) - 8);
  
  		int closureBonds = 1 + allBonds - allAtoms;
  		int dbits = decodeBits(4);
  		int base = 0;
  
  		mMol.setAtomX(0, 0.0);
  		mMol.setAtomY(0, 0.0);
  		mMol.setAtomZ(0, 0.0);
  
  		boolean decodeOldCoordinates = (coordinates != null && coordinates[coordsStart] >= '\'');
  		double targetAVBL = 0.0;
  		double xOffset = 0.0;
  		double yOffset = 0.0;
  		double zOffset = 0.0;
  		boolean coordsAre3D = false;
  		boolean coordsAreAbsolute = false;
  
  		if (decodeOldCoordinates) {	// old coordinate encoding
  			if ((coordinates.length > 2*allAtoms-2 && coordinates[2*allAtoms-2] == '\'')
  			 || (coordinates.length > 3*allAtoms-3 && coordinates[3*allAtoms-3] == '\'')) {	// old faulty encoding
  				coordsAreAbsolute = true;
  				coordsAre3D = (coordinates.length == 3*allAtoms-3+9);
  				int index = coordsAre3D ? 3*allAtoms-3 : 2*allAtoms-2;
  				int avblInt = 86*((int)coordinates[index+1]-40)+(int)coordinates[index+2]-40;
  				targetAVBL = Math.pow(10.0, avblInt/2000.0-1.0);
  				index += 2;
  				int xInt = 86*((int)coordinates[index+1]-40)+(int)coordinates[index+2]-40;
  				xOffset = Math.pow(10.0, xInt/1500.0-1.0);
  				index += 2;
  				int yInt = 86*((int)coordinates[index+1]-40)+(int)coordinates[index+2]-40;
  				yOffset = Math.pow(10.0, yInt/1500.0-1.0);
  				if (coordsAre3D) {
  					index += 2;
  					int zInt = 86*((int)coordinates[index+1]-40)+(int)coordinates[index+2]-40;
  					zOffset = Math.pow(10.0, zInt/1500.0-1.0);
  					}
  				}
  			else {
  				coordsAre3D = (coordinates.length == 3*allAtoms-3);
  				}
  			}
  
  		// don't use 3D coordinates, if we need 2D
  		if (ensure2DCoordinates() && coordsAre3D) {
  			coordinates = null;
  			decodeOldCoordinates = false;
  			}
  
  		for (int i=1; i<allAtoms; i++) {
  			int dif = decodeBits(dbits);
  			if (dif == 0) {
  				if (decodeOldCoordinates) {
  					mMol.setAtomX(i, mMol.getAtomX(0) + 8 * (coordinates[i*2-2]-83));
  					mMol.setAtomY(i, mMol.getAtomY(0) + 8 * (coordinates[i*2-1]-83));
  					if (coordsAre3D)
  						mMol.setAtomZ(i, mMol.getAtomZ(0) + 8 * (coordinates[2*allAtoms-3+i]-83));
  					}
  
  				closureBonds++;
  				continue;
  				}
  
  			base += dif - 1;
  
  			if (decodeOldCoordinates) {
  				mMol.setAtomX(i, mMol.getAtomX(base) + coordinates[i*2-2] - 83);
  				mMol.setAtomY(i, mMol.getAtomY(base) + coordinates[i*2-1] - 83);
  				if (coordsAre3D)
  					mMol.setAtomZ(i, mMol.getAtomZ(base) + (coordinates[2*allAtoms-3+i]-83));
  				}
  			mMol.addBond(base, i, Molecule.cBondTypeSingle);
  			}
  
  		for (int i=0; i<closureBonds; i++)
  			mMol.addBond(decodeBits(abits),
  						 decodeBits(abits), Molecule.cBondTypeSingle);
  
  		boolean[] isDelocalizedBond = new boolean[allBonds];
  
  		for (int bond=0; bond<allBonds; bond++) {
  			int bondOrder = decodeBits(2);
  			switch (bondOrder) {
  			case 0:
  				isDelocalizedBond[bond] = true;
  				break;
  			case 2:
  				mMol.setBondType(bond, Molecule.cBondTypeDouble);
  				break;
  			case 3:
  				mMol.setBondType(bond, Molecule.cBondTypeTriple);
  				break;
  				}
  			}
  
  		int THCount = decodeBits(abits);
  		for (int i=0; i<THCount; i++) {
  			int atom = decodeBits(abits);
  			if (version == Canonizer.cIDCodeVersion2) {
  				int parity = decodeBits(2);
  				if (parity == 3) {
  					// this was the old discontinued Molecule.cAtomParityMix
  					// version2 idcodes had never more than one center with parityMix
  					mMol.setAtomESR(atom, Molecule.cESRTypeAnd, 0);
  					mMol.setAtomParity(atom, Molecule.cAtomParity1, false);
  					}
  				else {
  					mMol.setAtomParity(atom, parity, false);
  					}
  				}
  			else {
  				int parity = decodeBits(3);
  				switch (parity) {
  				case Canonizer.cParity1And:
  					mMol.setAtomParity(atom, Molecule.cAtomParity1, false);
  					mMol.setAtomESR(atom, Molecule.cESRTypeAnd, decodeBits(3));
  					break;
  				case Canonizer.cParity2And:
  					mMol.setAtomParity(atom, Molecule.cAtomParity2, false);
  					mMol.setAtomESR(atom, Molecule.cESRTypeAnd, decodeBits(3));
  					break;
  				case Canonizer.cParity1Or:
  					mMol.setAtomParity(atom, Molecule.cAtomParity1, false);
  					mMol.setAtomESR(atom, Molecule.cESRTypeOr, decodeBits(3));
  					break;
  				case Canonizer.cParity2Or:
  					mMol.setAtomParity(atom, Molecule.cAtomParity2, false);
  					mMol.setAtomESR(atom, Molecule.cESRTypeOr, decodeBits(3));
  					break;
  				default:
  					mMol.setAtomParity(atom, parity, false);
  					}
  				}
  			}
  
  		if (version == Canonizer.cIDCodeVersion2)
  			if ((decodeBits(1) == 0))   // translate chiral flag
  				mMol.setToRacemate();
  
  		int EZCount = decodeBits(bbits);
  		for (int i=0; i<EZCount; i++) {
  			int bond = decodeBits(bbits);
  			if (mMol.getBondType(bond) == Molecule.cBondTypeSingle) {	// BINAP type of axial chirality
  				int parity = decodeBits(3);
  				switch (parity) {
  				case Canonizer.cParity1And:
  					mMol.setBondParity(bond, Molecule.cBondParityEor1, false);
  					mMol.setBondESR(bond, Molecule.cESRTypeAnd, decodeBits(3));
  					break;
  				case Canonizer.cParity2And:
  					mMol.setBondParity(bond, Molecule.cBondParityZor2, false);
  					mMol.setBondESR(bond, Molecule.cESRTypeAnd, decodeBits(3));
  					break;
  				case Canonizer.cParity1Or:
  					mMol.setBondParity(bond, Molecule.cBondParityEor1, false);
  					mMol.setBondESR(bond, Molecule.cESRTypeOr, decodeBits(3));
  					break;
  				case Canonizer.cParity2Or:
  					mMol.setBondParity(bond, Molecule.cBondParityZor2, false);
  					mMol.setBondESR(bond, Molecule.cESRTypeOr, decodeBits(3));
  					break;
  				default:
  					mMol.setBondParity(bond, parity, false);
  					}
  				}
  			else {
  				mMol.setBondParity(bond, decodeBits(2), false);	// double bond
  				}
  			}
  
  		mMol.setFragment(decodeBits(1) == 1);
  
  		int[] aromaticSPBond = null;
  		int[] selectedHydrogenBits = null;
  
  		int offset = 0;
  		while (decodeBits(1) == 1) {
  			int dataType = offset + decodeBits(4);
  			int no;
  			switch (dataType) {
  			case 0:	//	datatype 'AtomQFNoMoreNeighbours'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomQueryFeature(atom, Molecule.cAtomQFNoMoreNeighbours, true);
  					}
  				break;
  			case 1:	//	datatype 'isotop'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					int mass = decodeBits(8);
  					mMol.setAtomMass(atom, mass);
  					}
  				break;
  			case 2:	//	datatype 'bond defined to be delocalized'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  // This used to be Molecule.cBondTypeDelocalized, which is redundant to the bond order encoding
  // Then it was wrongly fixed to cBondQFDelocalized, which is part of cBondQFBondTypes and encoded as type 10
  // We can take it out entirely without sacrifycing idcode compatibility
  //					mMol.setBondQueryFeature(bond, Molecule.cBondQFDelocalized, true);
  //System.out.println("wrong outdated 'delocalized bond'; idcode:"+new String(mDecodingBytes));
  					}
  				break;
  			case 3:	//	datatype 'AtomQFMoreNeighbours'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomQueryFeature(atom, Molecule.cAtomQFMoreNeighbours, true);
  					}
  				break;
  			case 4:	//	datatype 'AtomQFRingState'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long ringState = (long)decodeBits(Molecule.cAtomQFRingStateBits) << Molecule.cAtomQFRingStateShift;
  					mMol.setAtomQueryFeature(atom, ringState, true);
  					}
  				break;
  			case 5:	//	datatype 'AtomQFAromState'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long aromState = (long)decodeBits(Molecule.cAtomQFAromStateBits) << Molecule.cAtomQFAromStateShift;
  					mMol.setAtomQueryFeature(atom, aromState, true);
  					}
  				break;
  			case 6:	//	datatype 'AtomQFAny'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomQueryFeature(atom, Molecule.cAtomQFAny, true);
  					}
  				break;
  			case 7:	//	datatype 'AtomQFHydrogen'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long hydrogen = (long)decodeBits(Molecule.cAtomQFHydrogenBits) << Molecule.cAtomQFHydrogenShift;
  					mMol.setAtomQueryFeature(atom, hydrogen, true);
  					}
  				break;
  			case 8:	//	datatype 'AtomList'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					int atoms = decodeBits(4);
  					int[] atomList = new int[atoms];
  					for (int j=0; j<atoms; j++) {
  						int atomicNo = decodeBits(8);
  						atomList[j] = atomicNo;
  						}
  					mMol.setAtomList(atom, atomList);
  					}
  				break;
  			case 9:	//	datatype 'BondQFRingState'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					int ringState = decodeBits(Molecule.cBondQFRingStateBits) << Molecule.cBondQFRingStateShift;
  					mMol.setBondQueryFeature(bond, ringState, true);
  					}
  				break;
  			case 10://	datatype 'BondQFBondTypes'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					int bondTypes = decodeBits(Molecule.cBondQFBondTypesBits) << Molecule.cBondQFBondTypesShift;
  					mMol.setBondQueryFeature(bond, bondTypes, true);
  					}
  				break;
  			case 11:	//	datatype 'AtomQFMatchStereo'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomQueryFeature(atom, Molecule.cAtomQFMatchStereo, true);
  					}
  				break;
  			case 12:	//  datatype 'bond defined to be a bridge from n1 to n2 atoms'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					int bridgeData = decodeBits(Molecule.cBondQFBridgeBits) << Molecule.cBondQFBridgeShift;
  					mMol.setBondQueryFeature(bond, bridgeData, true);
  					}
  				break;
  			case 13: //  datatype 'AtomQFPiElectrons'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long piElectrons = (long)decodeBits(Molecule.cAtomQFPiElectronBits) << Molecule.cAtomQFPiElectronShift;
  					mMol.setAtomQueryFeature(atom, piElectrons, true);
  					}
  				break;
  			case 14: //  datatype 'AtomQFNeighbours'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long neighbours = (long)decodeBits(Molecule.cAtomQFNeighbourBits) << Molecule.cAtomQFNeighbourShift;
  					mMol.setAtomQueryFeature(atom, neighbours, true);
  					}
  				break;
  			case 15: //  datatype 'start next feature set'
  			case 31:
  				offset += 16;
  				break;
  			case 16: //  datatype 'AtomQFSmallRingSize'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long ringSize = (long)decodeBits(Molecule.cAtomQFSmallRingSizeBits) << Molecule.cAtomQFSmallRingSizeShift;
  					mMol.setAtomQueryFeature(atom, ringSize, true);
  					}
  				break;
  			case 17: //  datatype 'AtomAbnormalValence'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomAbnormalValence(atom, decodeBits(4));
  					}
  				break;
  			case 18: //  datatype 'AtomCustomLabel'
  				no = decodeBits(abits);
  				int lbits = decodeBits(4);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					int count = decodeBits(lbits);
  					byte[] label = new byte[count];
  					for (int j=0; j<count; j++)
  						label[j] = (byte)decodeBits(7);
  					mMol.setAtomCustomLabel(atom, new String(label, StandardCharsets.UTF_8));
  					}
  				break;
  			case 19: //  datatype 'AtomQFCharge'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long charge = (long)decodeBits(Molecule.cAtomQFChargeBits) << Molecule.cAtomQFChargeShift;
  					mMol.setAtomQueryFeature(atom, charge, true);
  					}
  				break;
  			case 20: //  datatype 'BondQFRingSize'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					int ringSize = decodeBits(Molecule.cBondQFRingSizeBits) << Molecule.cBondQFRingSizeShift;
  					mMol.setBondQueryFeature(bond, ringSize, true);
  					}
  				break;
  			case 21: //  datatype 'AtomRadicalState'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomRadical(atom, decodeBits(2) << Molecule.cAtomRadicalStateShift);
  					}
  				break;
  			case 22:	//	datatype 'flat nitrogen'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomQueryFeature(atom, Molecule.cAtomQFFlatNitrogen, true);
  					}
  				break;
  			case 23:	//	datatype 'BondQFMatchStereo'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					mMol.setBondQueryFeature(bond, Molecule.cBondQFMatchStereo, true);
  					}
  				break;
  			case 24:	//	datatype 'cBondQFAromState'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					int aromState = decodeBits(Molecule.cBondQFAromStateBits) << Molecule.cBondQFAromStateShift;
  					mMol.setBondQueryFeature(bond, aromState, true);
  					}
  				break;
  			case 25:	//	datatype 'atom selection'
  				for (int i=0; i<allAtoms; i++)
  					if (decodeBits(1) == 1)
  						mMol.setAtomSelection(i, true);
  				break;
  			case 26:	//	datatype 'delocalized high order bond'
  				no = decodeBits(bbits);
  				aromaticSPBond = new int[no];
  				for (int i=0; i<no; i++)
  					aromaticSPBond[i] = decodeBits(bbits);
  				break;
  			case 27:	//	datatype 'part of an exclude group'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomQueryFeature(atom, Molecule.cAtomQFExcludeGroup, true);
  					}
  				break;
  			case 28: //  datatype 'coordinate bond'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++)
  					mMol.setBondType(decodeBits(bbits), Molecule.cBondTypeMetalLigand);
  				break;
  			case 29:	//	datatype 'reaction parity hint'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long hint = (long)decodeBits(Molecule.cAtomQFRxnParityBits) << Molecule.cAtomQFRxnParityShift;
  					mMol.setAtomQueryFeature(atom, hint, true);
  					}
  				break;
  			case 30: //  datatype 'AtomQFNewRingSize'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long ringSize = (long)decodeBits(Molecule.cAtomQFNewRingSizeBits) << Molecule.cAtomQFNewRingSizeShift;
  					mMol.setAtomQueryFeature(atom, ringSize, true);
  					}
  				break;
  			case 32: //  datatype 'AtomQFNewRingSize'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long stereoState = (long)decodeBits(Molecule.cAtomQFStereoStateBits) << Molecule.cAtomQFStereoStateShift;
  					mMol.setAtomQueryFeature(atom, stereoState, true);
  					}
  				break;
  			case 33: //  datatype 'AtomQFENeighbours'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long eNeighbours = (long)decodeBits(Molecule.cAtomQFENeighbourBits) << Molecule.cAtomQFENeighbourShift;
  					mMol.setAtomQueryFeature(atom, eNeighbours, true);
  					}
  				break;
  			case 34:	//	datatype 'AtomQFHetereoAromatic'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					mMol.setAtomQueryFeature(atom, Molecule.cAtomQFHeteroAromatic, true);
  					}
  				break;
  			case 35:	//	datatype 'BondQFMatchFormalOrder'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					mMol.setBondQueryFeature(bond, Molecule.cBondQFMatchFormalOrder, true);
  					}
  				break;
  			case 36:	//	datatype 'cBondQFRareBondType'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					int bondType = decodeBits(Molecule.cBondQFRareBondTypesBits) << Molecule.cBondQFRareBondTypesShift;
  					mMol.setBondQueryFeature(bond, bondType, true);
  					}
  				break;
  			case 37:	//	datatype 'rare order bond'
  				no = decodeBits(bbits);
  				for (int i=0; i<no; i++) {
  					int bond = decodeBits(bbits);
  					int bondType = decodeBits(1) == 0 ? Molecule.cBondTypeQuadruple : Molecule.cBondTypeQuintuple;
  					mMol.setBondType(bond, bondType);
  					}
  				break;
  			case 38:	//	datatype 'selected hydrogen'
  				no = decodeBits(abits);
  				int connBits = decodeBits(3);
  				selectedHydrogenBits = new int[allAtoms];
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					int conns = decodeBits(connBits);
  					selectedHydrogenBits[atom] = conns;
  					}
  				break;
  			case 39: //  datatype 'AtomQFOxydationState'
  				no = decodeBits(abits);
  				for (int i=0; i<no; i++) {
  					int atom = decodeBits(abits);
  					long oxydationState = (long)decodeBits(Molecule.cAtomQFOxidationStateBits) << Molecule.cAtomQFOxidationStateShift;
  					mMol.setAtomQueryFeature(atom, oxydationState, true);
  				}
  				break;
  				}
  			}
  
  		new AromaticityResolver(mMol).locateDelocalizedDoubleBonds(isDelocalizedBond);
  
  		if (aromaticSPBond != null)
  			for (int bond:aromaticSPBond)
  				mMol.setBondType(bond, mMol.getBondType(bond) == Molecule.cBondTypeDouble ?
  						Molecule.cBondTypeTriple : Molecule.cBondTypeDouble);
  
  		if (coordinates == null
  		 && !mNeglectSpaceDelimitedCoordinates
  		 && idcode.length > mIDCodeBufferIndex+1
  		 && (idcode[mIDCodeBufferIndex+1] == ' ' || idcode[mIDCodeBufferIndex+1] == '\t')) {
  			coordinates = idcode;
  			coordsStart = mIDCodeBufferIndex+2;
  			}
  
  		if (coordinates != null) {
  			try {
  				if (coordinates[coordsStart] == '!' || coordinates[coordsStart] == '#') {    // new coordinate format
  					decodeBitsStart(coordinates, coordsStart + 1);
  					coordsAre3D = (decodeBits(1) == 1);
  					coordsAreAbsolute = (decodeBits(1) == 1);
  					int resolutionBits = 2 * decodeBits(4);
  					int binCount = (1 << resolutionBits);
  
  					double factor;
  					int from = 0;
  					int bond = 0;
  					for (int atom = 1; atom < allAtoms; atom++) {
  						if (bond < allBonds && mMol.getBondAtom(1, bond) == atom) {
  							from = mMol.getBondAtom(0, bond++);
  							factor = 1.0;
  							}
  						else {
  							from = 0;
  							factor = 8.0;
  							}
  
  						double decodedDX = factor * (decodeBits(resolutionBits) + 1 - (binCount >> 1));
  						double decodedDY = factor * (decodeBits(resolutionBits) + 1 - (binCount >> 1));
  						mMol.setAtomX(atom, mMol.getAtomX(from) + decodedDX);
  						mMol.setAtomY(atom, mMol.getAtomY(from) + decodedDY);
  						if (coordsAre3D)
  							mMol.setAtomZ(atom, mMol.getAtomZ(from) + factor * (decodeBits(resolutionBits) + 1 - (binCount >> 1)));
  						}
  
  					if (coordinates[coordsStart] == '#') {    // we have 3D-coordinates that include implicit hydrogen coordinates
  						int hydrogenCount = 0;
  
  						// we need to cache hCount, because otherwise getImplicitHydrogens() would create helper arrays with every call
  						int[] hCount = new int[allAtoms];
  						for (int atom = 0; atom < allAtoms; atom++)
  							hydrogenCount += (hCount[atom] = mMol.getImplicitHydrogens(atom));
  
  						for (int atom = 0; atom < allAtoms; atom++) {
  							for (int i = 0; i < hCount[atom]; i++) {
  								int hydrogen = mMol.addAtom(1);
  								mMol.addBond(atom, hydrogen, Molecule.cBondTypeSingle);
  
  								mMol.setAtomX(hydrogen, mMol.getAtomX(atom) + (decodeBits(resolutionBits) + 1 - (binCount >> 1)));
  								mMol.setAtomY(hydrogen, mMol.getAtomY(atom) + (decodeBits(resolutionBits) + 1 - (binCount >> 1)));
  								if (coordsAre3D)
  									mMol.setAtomZ(hydrogen, mMol.getAtomZ(atom) + (decodeBits(resolutionBits) + 1 - (binCount >> 1)));
  
  								if (selectedHydrogenBits != null && (selectedHydrogenBits[atom] & (1 << i)) != 0)
  									mMol.setAtomSelection(hydrogen, true);
  								}
  							}
  
  						allAtoms += hydrogenCount;
  						allBonds += hydrogenCount;
  						}
  
  					double avblDefault = coordsAre3D ? 1.5 : Molecule.getDefaultAverageBondLength();
  					double avbl = mMol.getAverageBondLength(allAtoms, allBonds, avblDefault);
  
  					if (coordsAreAbsolute) {
  						targetAVBL = decodeAVBL(decodeBits(resolutionBits), binCount);
  						xOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  						yOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  						if (coordsAre3D)
  							zOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  
  						factor = targetAVBL / avbl;
  						for (int atom = 0; atom < allAtoms; atom++) {
  							mMol.setAtomX(atom, xOffset + factor * mMol.getAtomX(atom));
  							mMol.setAtomY(atom, yOffset + factor * mMol.getAtomY(atom));
  							if (coordsAre3D)
  								mMol.setAtomZ(atom, zOffset + factor * mMol.getAtomZ(atom));
  							}
  						}
  					else {    // with new format 2D and 3D coordinates are scaled to average bond lengths of 1.5 Angstrom
  						targetAVBL = 1.5;
  						factor = targetAVBL / avbl;
  						for (int atom = 0; atom < allAtoms; atom++) {
  							mMol.setAtomX(atom, factor * mMol.getAtomX(atom));
  							mMol.setAtomY(atom, factor * mMol.getAtomY(atom));
  							if (coordsAre3D)
  								mMol.setAtomZ(atom, factor * mMol.getAtomZ(atom));
  							}
  						}
  					}
  				else {    // old coordinate format
  					if (coordsAre3D && !coordsAreAbsolute && targetAVBL == 0.0) // if no scaling factor is given, then scale to mean bond length = 1.5
  						targetAVBL = 1.5;
  
  					if (targetAVBL != 0.0 && mMol.getAllBonds() != 0) {
  						double avbl = 0.0;
  						for (int bond = 0; bond < mMol.getAllBonds(); bond++) {
  							double dx = mMol.getAtomX(mMol.getBondAtom(0, bond)) - mMol.getAtomX(mMol.getBondAtom(1, bond));
  							double dy = mMol.getAtomY(mMol.getBondAtom(0, bond)) - mMol.getAtomY(mMol.getBondAtom(1, bond));
  							double dz = coordsAre3D ? mMol.getAtomZ(mMol.getBondAtom(0, bond)) - mMol.getAtomZ(mMol.getBondAtom(1, bond)) : 0.0f;
  							avbl += Math.sqrt(dx * dx + dy * dy + dz * dz);
  							}
  						avbl /= mMol.getAllBonds();
  						double f = targetAVBL / avbl;
  						for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
  							mMol.setAtomX(atom, mMol.getAtomX(atom) * f + xOffset);
  							mMol.setAtomY(atom, mMol.getAtomY(atom) * f + yOffset);
  							if (coordsAre3D)
  								mMol.setAtomZ(atom, mMol.getAtomZ(atom) * f + zOffset);
  							}
  						}
  					}
  				}
  			catch (Exception e) {
  				e.printStackTrace();
  				System.err.println("Faulty id-coordinates:"+e+" "+new String(idcode, StandardCharsets.UTF_8)+" "+new String(coordinates, StandardCharsets.UTF_8));
  				coordinates = null;
  				coordsAre3D = false;
  				}
  			}
  
  		boolean coords2DAvailable = (coordinates != null && !coordsAre3D);
  
  		fixMultipleBondTypes();
  
  		// If we have or create 2D-coordinates, then we need to set all double bonds to a cross bond, which
  		// - have distinguishable substituents on both ends, i.e. is a stereo double bond
  		// - are not in a small ring
  		// Here we don't know whether a double bond without E/Z parity is a stereo bond with unknown
  		// configuration or not a stereo bond. Therefore, we need to set a flag, that causes the Canonizer
  		// during the next stereo recognition with atom coordinates to assign an unknown configuration rather
  		// than E or Z based on created or given coordinates.
  		// In a next step these double bonds are converted into cross bonds by
  		if (coords2DAvailable || ensure2DCoordinates()) {
  			mMol.ensureHelperArrays(Molecule.cHelperRings);
  			for (int bond=0; bond<mMol.getBonds(); bond++)
  				if (mMol.getBondOrder(bond) == 2
  				 && !mMol.isSmallRingBond(bond)
  				 && mMol.getBondParity(bond) == Molecule.cBondParityNone)
  					mMol.setBondParityUnknownOrNone(bond);
  			}
  
  		mMol.setParitiesValid(0);
  
  		if (!coords2DAvailable && ensure2DCoordinates()) {
  			try {
  				inventCoordinates(mMol);
  				coords2DAvailable = true;
  				}
  			catch (Exception e) {
  				e.printStackTrace();
  				System.err.println("2D-coordinate creation failed:"+e+" "+new String(idcode, StandardCharsets.UTF_8));
  				}
  			}
  
  		if (coords2DAvailable) {
  			mMol.setStereoBondsFromParity();
  			mMol.setUnknownParitiesToExplicitlyUnknown();
  			}
  		else if (!coordsAre3D) {
  			mMol.setParitiesValid(0);
  			}
  		}
  
  	/**
  	 * New convention is that in case of a substructure bond with multiple allowed bond types,
  	 * all allowed bond types are set as query feature and in addition the bond itself has to
  	 * be the lowest bond type of these.
  	 */
  	private void fixMultipleBondTypes() {
  		if (mMol.isFragment()) {
  			for (int bond=0; bond<mMol.getAllBonds(); bond++) {
  				int queryFeatures = mMol.getBondQueryFeatures(bond);
  				if ((queryFeatures & Molecule.cBondQFAllBondTypes) == 0)
  					continue;
  
  				int bondType = -1;
  				int selectionCount = 0;
  
  				if ((queryFeatures & Molecule.cBondTypeMetalLigand) != 0) {
  					bondType = Molecule.cBondTypeMetalLigand;
  					selectionCount++;
  				}
  				if ((queryFeatures & Molecule.cBondTypeQuintuple) != 0) {
  					bondType = Molecule.cBondTypeQuintuple;
  					selectionCount++;
  				}
  				if ((queryFeatures & Molecule.cBondTypeQuadruple) != 0) {
  					bondType = Molecule.cBondTypeQuadruple;
  					selectionCount++;
  				}
  				if ((queryFeatures & Molecule.cBondTypeTriple) != 0) {
  					bondType = Molecule.cBondTypeTriple;
  					selectionCount++;
  				}
  				if ((queryFeatures & Molecule.cBondTypeDouble) != 0) {
  					bondType = Molecule.cBondTypeDouble;
  					selectionCount++;
  				}
  				if ((queryFeatures & Molecule.cBondTypeDelocalized) != 0) {
  					bondType = Molecule.cBondTypeDelocalized;
  					selectionCount++;
  				}
  				if ((queryFeatures & Molecule.cBondTypeSingle) != 0) {
  					bondType = Molecule.cBondTypeSingle;
  					selectionCount++;
  				}
  
  				if (bondType != -1) {
  					mMol.setBondType(bond, bondType);    // set to the lowest bond order of query options
  					if (selectionCount == 1)
  						mMol.setBondQueryFeature(bond, Molecule.cBondQFAllBondTypes, false);
  				}
  			}
  		}
  	}
  
  	protected void inventCoordinates(StereoMolecule mol) throws Exception {
  		throw new Exception("Unexpected request to invent coordinates. Check source code logic!");
  		}
  
  	/**
  	 * This method parses an id-coordinate string (new format only) and writes the coordinates into a Coordinates array.
  	 * If the id-coordinates contain implicit hydrogen coordinates, then this method does not(!!!) add these hydrogen atoms
  	 * to the Molecule. Thus, for 3D-coordinates with implicit hydrogen coordinates, you need to make sure that all
  	 * the Molecule's hydrogen atoms are explicit and that the Coordinates array's size covers all hydrogens atoms.
  	 * For instance, if parsing idcodes and coordinates of a conformer set, you may parse the first conformer with one
  	 * of the getCompactMolecule() or parse() methods.
  	 * This adds all implicit hydrogens as explicit ones to the Molecule and conformer object. All subsequent conformers
  	 * may be generated by instantiating a new Conformer from the molecule and using this method to populate its coordinates.
  	 * @param encodedCoords id-coordinates string in new format
  	 * @param coordsStart offset
  	 * @param mol Molecule with all hydrogens being explicit
  	 * @param coords Coordinates array to be populated by coordinates taken from encoded string
  	 * @throws Exception
  	 */
  	public void parseCoordinates(byte[] encodedCoords, int coordsStart, StereoMolecule mol, Coordinates[] coords) throws Exception {
  		mol.ensureHelperArrays(Molecule.cHelperNeighbours);
  		int atomCount = mol.getAtoms();
  		int bondCount = mol.getBonds();
  
  		decodeBitsStart(encodedCoords, coordsStart + 1);
  		boolean coordsAre3D = (decodeBits(1) == 1);
  		boolean coordsAreAbsolute = (decodeBits(1) == 1);
  		int resolutionBits = 2 * decodeBits(4);
  		int binCount = (1 << resolutionBits);
  
  		double factor;
  		int from = 0;
  		int bond = 0;
  		for (int atom = 1; atom < atomCount; atom++) {
  			if (bond < bondCount && mol.getBondAtom(1, bond) == atom) {
  				from = mol.getBondAtom(0, bond++);
  				factor = 1.0;
  				}
  			else {
  				from = 0;
  				factor = 8.0;
  				}
  			coords[atom].x = coords[from].x + factor * (decodeBits(resolutionBits) - binCount / 2.0);
  			coords[atom].y = coords[from].y + factor * (decodeBits(resolutionBits) - binCount / 2.0);
  			if (coordsAre3D)
  				coords[atom].z = coords[from].z + factor * (decodeBits(resolutionBits) - binCount / 2.0);
  			}
  
  		double avbl = coordsAre3D ? 1.5 : Molecule.getDefaultAverageBondLength();
  		if (bondCount != 0)
  			for (int b=0; b<bondCount; b++)
  				avbl += coords[mol.getBondAtom(0, b)].distance(coords[mol.getBondAtom(1, b)]);
  		avbl /= bondCount;
  
  		if (encodedCoords[coordsStart] == '#') {    // we have 3D-coordinates that include implicit hydrogen coordinates
  			int hydrogenCount = 0;
  
  			int hydrogen = atomCount;
  			for (int atom = 0; atom < atomCount; atom++) {
  				int hCount = mol.getAllConnAtoms(atom) - mol.getConnAtoms(atom);
  				for (int i = 0; i < hCount; i++) {
  					coords[hydrogen].x = coords[atom].x + (decodeBits(resolutionBits) - binCount / 2.0);
  					coords[hydrogen].y = coords[atom].y + (decodeBits(resolutionBits) - binCount / 2.0);
  					if (coordsAre3D)
  						coords[hydrogen].z = coords[atom].z + (decodeBits(resolutionBits) - binCount / 2.0);
  
  					hydrogen++;
  					}
  				hydrogenCount += hCount;
  				}
  
  			atomCount += hydrogenCount;
  			bondCount += hydrogenCount;
  			}
  
  		if (coordsAreAbsolute) {
  			double targetAVBL = decodeAVBL(decodeBits(resolutionBits), binCount);
  			double xOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  			double yOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  			double zOffset = 0;
  			if (coordsAre3D)
  				zOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  
  			factor = targetAVBL / avbl;
  			for (int atom = 0; atom < atomCount; atom++) {
  				coords[atom].x = xOffset + factor * coords[atom].x;
  				coords[atom].y = yOffset + factor * coords[atom].y;
  				if (coordsAre3D)
  					coords[atom].z = zOffset + factor * coords[atom].z;
  				}
  			}
  		else {    // with new format 2D and 3D coordinates are scaled to average bond lengths of 1.5 Angstrom
  			double targetAVBL = 1.5;
  			factor = targetAVBL / avbl;
  			for (int atom = 0; atom < atomCount; atom++) {
  				coords[atom].x = factor * coords[atom].x;
  				coords[atom].y = factor * coords[atom].y;
  				if (coordsAre3D)
  					coords[atom].z = factor * coords[atom].z;
  				}
  			}
  		}
  
  	public void parseMapping(byte[] mapping) {
  		parseMapping(mapping, 0);
  		}
  
  	public void parseMapping(byte[] mapping, int mappingStart) {
  		if (mapping == null || mapping.length <= mappingStart || mapping[mappingStart] < 0x40)
  			return;
  
  		decodeBitsStart(mapping, mappingStart);
  		int nbits = decodeBits(4);
  		boolean autoMappingFound = (decodeBits(1) == 1);
  		boolean manualMappingFound = (decodeBits(1) == 1);
  		for (int atom=0; atom<mMol.getAtoms(); atom++) {
  			int mapNo = decodeBits(nbits);
  			boolean autoMapped = autoMappingFound;
  			if (autoMappingFound && manualMappingFound)
  				autoMapped = (decodeBits(1) == 1);
  			mMol.setAtomMapNo(atom, mapNo, autoMapped);
  			}
  		}
  
  	public boolean coordinatesAre3D(String idcode, String coordinates) {
  		return coordinates != null && coordinatesAre3D(idcode.getBytes(StandardCharsets.UTF_8), coordinates.getBytes(StandardCharsets.UTF_8));
  		}
  
  	public boolean coordinatesAre3D(byte[] idcode, byte[] coordinates) {
  		return coordinatesAre3D(idcode, coordinates, 0, 0);
  		}
  
  	public boolean coordinatesAre3D(byte[] idcode, byte[] coordinates, int idcodeStart, int coordsStart) {
  		if (coordinates == null || coordinates.length <= coordsStart)
  			return false;
  
  		if (coordinates[coordsStart] == '!' || coordinates[coordsStart] == '#') {
  			// current version starts with '!' (ASC 33) or '#' (ASC 35) (includs implicit hydrogen coordinates)
  			// further versions may start with ASC 36 to 38
  			decodeBitsStart(coordinates, coordsStart+1);
  			return (decodeBits(1) == 1);
  			}
  		else {	// old format uses ACSII 39 and higher
  			int allAtoms = getAtomCount(idcode, idcodeStart);
  			return (allAtoms != 0
  				 && coordinates.length >= coordsStart+3*allAtoms-3
  				 && coordinates[coordsStart+2*allAtoms-2] != '\'');
  			}
  		}
  
  	public boolean coordinatesAreAbsolute(String coordinates) {
  		return coordinates != null && coordinatesAreAbsolute(coordinates.getBytes(StandardCharsets.UTF_8));
  		}
  
  	public boolean coordinatesAreAbsolute(byte[] coordinates) {
  		return coordinatesAreAbsolute(coordinates, 0);
  		}
  
  	public boolean coordinatesAreAbsolute(byte[] coordinates, int coordStart) {
  		if (coordinates == null || coordinates.length <= coordStart)
  			return false;
  
  		if (coordinates[coordStart] >= '\'') {	// old format uses ACSII 39 and higher
  			for (int i=coordStart; i<coordinates.length; i++)
  				if (coordinates[i] == '\'' || coordinates[i] == '&')
  					return true;
  			}
  		else if (coordinates[coordStart] == '!' || coordinates[coordStart] == '#') {
  			// current version starts with '!' (ASC 33) or '#' (ASC 35) (includs implicit hydrogen coordinates)
  			// further versions may start with ASC 36 to 38
  			decodeBitsStart(coordinates, coordStart+1);
  			decodeBits(1);	// skip 3D information
  			return (decodeBits(1) == 1);
  			}
  
  		return false;
  		}
  
  	public int getIDCodeVersion(String idcode) {
  		if (idcode == null || idcode.isEmpty())
  			return -1;
  
  		return getIDCodeVersion(idcode.getBytes(StandardCharsets.UTF_8));
  		}
  
  	public int getIDCodeVersion(byte[] idcode) {
  		int version = Canonizer.cIDCodeVersion2;
  
  		decodeBitsStart(idcode, 0);
  		int abits = decodeBits(4);
  		if (abits > 8)	// abits is the version number
  			version = abits;
  
  		return version;
  		}
  
  	public int getAtomCount(String idcode) {
  		if (idcode == null || idcode.isEmpty())
  			return 0;
  
  		return getAtomCount(idcode.getBytes(StandardCharsets.UTF_8), 0);
  		}
  
  	public int getAtomCount(byte[] idcode, int offset) {
  		if (idcode == null || idcode.length <= offset)
  			return 0;
  
  		decodeBitsStart(idcode, offset);
  		int abits = decodeBits(4);
  		int bbits = decodeBits(4);
  
  		if (abits > 8)	// abits is the version number
  			abits = bbits;
  
  		if (abits == 0)
  			return 0;
  
  		return decodeBits(abits);
  		}
  
  	/**
  	 * Determines atom and bond counts of the given idcode
  	 * @param idcode
  	 * @param count null or int[2], which is filled and returned
  	 * @return int[] with atom and bond count as first and second values
  	 */
  	public int[] getAtomAndBondCounts(String idcode, int[] count) {
  		if (idcode == null || idcode.isEmpty())
  			return null;
  
  		return getAtomAndBondCounts(idcode.getBytes(StandardCharsets.UTF_8), 0, count);
  		}
  
  	/**
  	 * Determines atom and bond counts of the given idcode
  	 * @param idcode
  	 * @param offset
  	 * @param count null or int[2], which is filled and returned
       * @return int[] with atom and bond count as first and second values
       */
  	public int[] getAtomAndBondCounts(byte[] idcode, int offset, int[] count) {
  		if (idcode == null || idcode.length == 0)
  			return null;
  
  		decodeBitsStart(idcode, 0);
  		int abits = decodeBits(4);
  		int bbits = decodeBits(4);
  
  		if (abits > 8)   // abits is the version number
  			abits = bbits;
  
  		if (count == null)
  			count = new int[2];
  
  		if (abits == 0) {
  			count[0] = 0;
  			count[1] = 0;
  			}
  		else {
  			count[0] = decodeBits(abits);
  			count[1] = decodeBits(bbits);
  			}
  
  		return count;
  		}
  
  	private void decodeBitsStart(byte[] bytes, int offset) {
  		mIDCodeBitsAvail = 6;
  		mIDCodeBufferIndex = offset;
  		mDecodingBytes = bytes;
  		mIDCodeTempData = (bytes[mIDCodeBufferIndex] & 0x3F) << 11;
  		}
  
  	private int decodeBits(int bits) {
  		int allBits = bits;
  
  		int data = 0;
  		while (bits != 0) {
  			if (mIDCodeBitsAvail == 0) {
  				mIDCodeTempData = (mDecodingBytes[++mIDCodeBufferIndex] & 0x3F) << 11;
  				mIDCodeBitsAvail = 6;
  				}
  			data |= ((0x00010000 & mIDCodeTempData) >> (16 - allBits + bits));
  			mIDCodeTempData <<= 1;
  			bits--;
  			mIDCodeBitsAvail--;
  			}
  		return data;
  		}
  
  	private double decodeAVBL(int value, int binCount) {
  		return Math.pow(10, Math.log10(200/0.1) * value / (binCount - 1) - 1);
  		}
  
  	private double decodeShift(int value, int binCount) {
  		int halfBinCount = binCount / 2;
  		boolean isNegative = (value >= halfBinCount);
  		if (isNegative)
  			value -= halfBinCount;
  		double steepness = binCount/32.0;
  		double doubleValue = steepness * value / (halfBinCount - value);
  		return isNegative ? -doubleValue : doubleValue;
  		}
  
  	public void printContent(byte[] idcode, byte[] coordinates) {
  		try {
  			int version = Canonizer.cIDCodeVersion2;
  
  			if (idcode == null || idcode.length == 0)
  				return;
  
  			if (coordinates != null && coordinates.length == 0)
  				coordinates = null;
  
  			System.out.println("idcode: " + new String(idcode, StandardCharsets.UTF_8));
  			if (coordinates != null)
  				System.out.println("coords: " + new String(coordinates, StandardCharsets.UTF_8));
  
  			decodeBitsStart(idcode, 0);
  			int abits = decodeBits(4);
  			int bbits = decodeBits(4);
  
  			if (abits > 8) {    // abits is the version number
  				version = abits;
  				abits = bbits;
  			}
  
  			System.out.println("version:" + version);
  
  			int allAtoms = decodeBits(abits);
  			if (allAtoms == 0)
  				return;
  
  			int allBonds = decodeBits(bbits);
  			int nitrogens = decodeBits(abits);
  			int oxygens = decodeBits(abits);
  			int otherAtoms = decodeBits(abits);
  			int chargedAtoms = decodeBits(abits);
  
  			System.out.println("allAtoms:" + allAtoms + " allBonds:" + allBonds);
  			if (nitrogens != 0) {
  				System.out.print("nitrogens:");
  				for (int i = 0; i < nitrogens; i++)
  					System.out.print(" " + decodeBits(abits));
  				System.out.println();
  			}
  			if (oxygens != 0) {
  				System.out.print("oxygens:");
  				for (int i = 0; i < oxygens; i++)
  					System.out.print(" " + decodeBits(abits));
  				System.out.println();
  			}
  			if (otherAtoms != 0) {
  				System.out.print("otherAtoms:");
  				for (int i = 0; i < otherAtoms; i++)
  					System.out.print(" " + decodeBits(abits) + ":" + decodeBits(8));
  				System.out.println();
  			}
  			if (chargedAtoms != 0) {
  				System.out.print("chargedAtoms:");
  				for (int i = 0; i < chargedAtoms; i++)
  					System.out.print(" " + decodeBits(abits) + ":" + (decodeBits(4) - 8));
  				System.out.println();
  			}
  
  			int closureBonds = 1 + allBonds - allAtoms;
  			int dbits = decodeBits(4);
  			int base = 0;
  
  			int[][] bondAtom = new int[2][allBonds];
  			int bondCount = 0;
  			for (int i = 1; i < allAtoms; i++) {
  				int dif = decodeBits(dbits);
  				if (dif == 0) {
  					closureBonds++;
  					continue;
  				}
  				base += dif - 1;
  				bondAtom[0][bondCount] = base;
  				bondAtom[1][bondCount++] = i;
  			}
  
  			for (int i = 0; i < closureBonds; i++) {
  				bondAtom[0][bondCount] = decodeBits(abits);
  				bondAtom[1][bondCount++] = decodeBits(abits);
  			}
  
  			int[] bondOrder = new int[allBonds];
  			System.out.print("bonds:");
  			for (int bond = 0; bond < allBonds; bond++) {
  				System.out.print(" " + bondAtom[0][bond]);
  				bondOrder[bond] = decodeBits(2);
  				System.out.print(bondOrder[bond] == 0 ? "." : bondOrder[bond] == 1 ? "-" : bondOrder[bond] == 2 ? "=" : "#");
  				System.out.print("" + bondAtom[1][bond]);
  			}
  			System.out.println();
  
  			int THCount = decodeBits(abits);
  			if (THCount != 0) {
  				System.out.print("parities:");
  				for (int i = 0; i < THCount; i++) {
  					int atom = decodeBits(abits);
  					if (version == Canonizer.cIDCodeVersion2) {
  						int parity = decodeBits(2);
  						if (parity == 3) {
  							// this was the old discontinued Molecule.cAtomParityMix
  							// version2 idcodes had never more than one center with parityMix
  							System.out.print(" " + atom + ":1&0");
  						} else {
  							System.out.print(" " + atom + ":" + parity);
  						}
  					} else {
  						int parity = decodeBits(3);
  						switch (parity) {
  							case Canonizer.cParity1And:
  								System.out.print(" " + atom + ":1&" + decodeBits(3));
  								break;
  							case Canonizer.cParity2And:
  								System.out.print(" " + atom + ":2&" + decodeBits(3));
  								break;
  							case Canonizer.cParity1Or:
  								System.out.print(" " + atom + ":1|" + decodeBits(3));
  								break;
  							case Canonizer.cParity2Or:
  								System.out.print(" " + atom + ":2|" + decodeBits(3));
  								break;
  							default:
  								System.out.print(" " + atom + ":" + parity);
  						}
  					}
  				}
  				System.out.println();
  			}
  
  			if (version == Canonizer.cIDCodeVersion2)
  				if ((decodeBits(1) == 0))   // translate chiral flag
  					System.out.println("isRacemate");
  
  			int EZCount = decodeBits(bbits);
  			if (EZCount != 0) {
  				System.out.print("EZ:");
  				for (int i = 0; i < EZCount; i++) {
  					int bond = decodeBits(bbits);
  					if (bondOrder[bond] == 1) {    // BINAP type of axial chirality
  						int parity = decodeBits(3);
  						switch (parity) {
  							case Canonizer.cParity1And:
  								System.out.print(" " + bond + ":1&" + decodeBits(3));
  								break;
  							case Canonizer.cParity2And:
  								System.out.print(" " + bond + ":2&" + decodeBits(3));
  								break;
  							case Canonizer.cParity1Or:
  								System.out.print(" " + bond + ":1|" + decodeBits(3));
  								break;
  							case Canonizer.cParity2Or:
  								System.out.print(" " + bond + ":2|" + decodeBits(3));
  								break;
  							default:
  								System.out.print(" " + bond + ":" + parity);
  						}
  					} else
  						System.out.print(" " + bond + ":" + decodeBits(2));
  				}
  				System.out.println();
  			}
  
  			if (decodeBits(1) == 1)
  				System.out.println("isFragment = true");
  
  			int offset = 0;
  			while (decodeBits(1) == 1) {
  				int dataType = offset + decodeBits(4);
  				int no;
  				switch (dataType) {
  					case 0: //  datatype 'AtomQFNoMoreNeighbours'
  						no = decodeBits(abits);
  						System.out.print("noMoreNeighbours:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 1: //  datatype 'isotop'
  						no = decodeBits(abits);
  						System.out.print("mass:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(8));
  						System.out.println();
  						break;
  					case 2: //  datatype 'bond defined to be delocalized'
  						no = decodeBits(bbits);
  						System.out.print("delocalizedBonds (outdated, redundant and wrong):");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits));
  						System.out.println();
  						break;
  					case 3: //  datatype 'AtomQFMoreNeighbours'
  						no = decodeBits(abits);
  						System.out.print("moreNeighbours:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 4: //  datatype 'AtomQFRingState'
  						no = decodeBits(abits);
  						System.out.print("atomRingState:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFRingStateBits));
  						System.out.println();
  						break;
  					case 5: //  datatype 'AtomQFAromState'
  						no = decodeBits(abits);
  						System.out.print("atomAromState:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFAromStateBits));
  						System.out.println();
  						break;
  					case 6: //  datatype 'AtomQFAny'
  						no = decodeBits(abits);
  						System.out.print("atomAny:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 7: //  datatype 'AtomQFHydrogen'
  						no = decodeBits(abits);
  						System.out.print("atomHydrogen:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFHydrogenBits));
  						System.out.println();
  						break;
  					case 8: //  datatype 'AtomList'
  						no = decodeBits(abits);
  						System.out.print("atomList:");
  						for (int i = 0; i < no; i++) {
  							int atom = decodeBits(abits);
  							int atoms = decodeBits(4);
  							System.out.print(" " + atom);
  							for (int j = 0; j < atoms; j++) {
  								System.out.print(j == 0 ? ":" : ",");
  								System.out.print("" + decodeBits(8));
  							}
  						}
  						System.out.println();
  						break;
  					case 9: //  datatype 'BondQFRingState'
  						no = decodeBits(bbits);
  						System.out.print("bondRingState:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits) + ":" + decodeBits(Molecule.cBondQFRingStateBits));
  						System.out.println();
  						break;
  					case 10://  datatype 'BondQFBondTypes'
  						no = decodeBits(bbits);
  						System.out.print("bondTypes:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits) + ":" + decodeBits(Molecule.cBondQFBondTypesBits));
  						System.out.println();
  						break;
  					case 11:    //  datatype 'AtomQFMatchStereo'
  						no = decodeBits(abits);
  						System.out.print("atomMatchStereo:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 12:    //  datatype 'bond defined to be a bridge from n1 to n2 atoms'
  						no = decodeBits(bbits);
  						for (int i = 0; i < no; i++) {
  							System.out.print("bridgeBond:" + decodeBits(bbits));
  							int min = decodeBits(Molecule.cBondQFBridgeMinBits);
  							int max = min + decodeBits(Molecule.cBondQFBridgeSpanBits);
  							System.out.println("(" + min + "-" + max + ")");
  						}
  						break;
  					case 13: //  datatype 'AtomQFPiElectrons'
  						no = decodeBits(abits);
  						System.out.print("atomPiElectrons:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFPiElectronBits));
  						System.out.println();
  						break;
  					case 14: //  datatype 'AtomQFNeighbours'
  						no = decodeBits(abits);
  						System.out.print("AtomQFNeighbours:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFNeighbourBits));
  						System.out.println();
  						break;
  					case 15: //  datatype 'start second feature set'
  					case 31:
  						offset += 16;
  						System.out.println("<start next feature set>");
  						break;
  					case 16: //  datatype 'AtomQFSmallRingSize'
  						no = decodeBits(abits);
  						System.out.print("AtomQFSmallRingSize:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFSmallRingSizeBits));
  						System.out.println();
  						break;
  					case 17: //  datatype 'AtomAbnormalValence'
  						no = decodeBits(abits);
  						System.out.print("AtomAbnormalValence:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(4));
  						System.out.println();
  						break;
  					case 18: //  datatype 'AtomCustomLabel'
  						no = decodeBits(abits);
  						System.out.print("AtomCustomLabel:");
  						int lbits = decodeBits(4);
  						for (int i = 0; i < no; i++) {
  							int atom = decodeBits(abits);
  							int count = decodeBits(lbits);
  							byte[] label = new byte[count];
  							for (int j = 0; j < count; j++)
  								label[j] = (byte) decodeBits(7);
  							System.out.print(" " + atom + ":" + new String(label, StandardCharsets.UTF_8));
  						}
  						System.out.println();
  						break;
  					case 19: //  datatype 'AtomQFCharge'
  						no = decodeBits(abits);
  						System.out.print("AtomQFCharge:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFChargeBits));
  						System.out.println();
  						break;
  					case 20: //  datatype 'BondQFRingSize'
  						no = decodeBits(bbits);
  						System.out.print("BondQFRingSize:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits) + ":" + decodeBits(Molecule.cBondQFRingSizeBits));
  						System.out.println();
  						break;
  					case 21: //  datatype 'AtomRadicalState'
  						no = decodeBits(abits);
  						System.out.print("AtomRadicalState:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(2));
  						System.out.println();
  						break;
  					case 22:    //	datatype 'flat nitrogen'
  						no = decodeBits(abits);
  						System.out.print("AtomQFFlatNitrogen:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 23:    //	datatype 'cBondQFMatchStereo'
  						no = decodeBits(bbits);
  						System.out.print("cBondQFMatchStereo:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 24:    //	datatype 'cBondQFAromatic'
  						no = decodeBits(bbits);
  						System.out.print("BondQFAromState:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits) + ":" + decodeBits(Molecule.cBondQFAromStateBits));
  						System.out.println();
  						break;
  					case 25:    //	datatype 'atom selection'
  						System.out.print("AtomSelection:");
  						for (int i = 0; i < allAtoms; i++)
  							if (decodeBits(1) == 1)
  								System.out.print(" " + i);
  						System.out.println();
  						break;
  					case 26:    //	datatype 'delocalized high order bond'
  						System.out.print("DelocalizedHigherOrderBonds:");
  						no = decodeBits(bbits);
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits));
  						break;
  					case 27:    //	datatype 'part of an exclude group'
  						no = decodeBits(abits);
  						System.out.print("AtomQFExcludeGroup:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 28:    //	datatype 'coordinate bond'
  						no = decodeBits(bbits);
  						System.out.print("Coordinate Bonds:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits));
  						System.out.println();
  						break;
  					case 29:
  						no = decodeBits(abits);
  						System.out.print("ReactionParityHint:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFRxnParityBits));
  						System.out.println();
  						break;
  					case 30: //  datatype 'AtomQFNewRingSize'
  						no = decodeBits(abits);
  						System.out.print("AtomQFNewRingSize:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFNewRingSizeBits));
  						System.out.println();
  						break;
  					case 32: //  datatype 'AtomQFStereoStateBits'
  						no = decodeBits(abits);
  						System.out.print("AtomQFStereoState:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFStereoStateBits));
  						System.out.println();
  						break;
  					case 33: //  datatype 'AtomQFENeighbours'
  						no = decodeBits(abits);
  						System.out.print("AtomQFENeighbours:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFENeighbourBits));
  						System.out.println();
  						break;
  					case 34:    //	datatype 'in hetero aromatic ring'
  						no = decodeBits(abits);
  						System.out.print("AtomQFHeteroAromatic:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 35:    //	datatype 'cBondQFMatchFormalOrder'
  						no = decodeBits(bbits);
  						System.out.print("BondQFMatchFormalOrder:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(abits));
  						System.out.println();
  						break;
  					case 36:    //	datatype 'cBondQFRareBondType'
  						no = decodeBits(bbits);
  						System.out.print("BondQFRareBondType:");
  						for (int i = 0; i < no; i++)
  							System.out.print(" " + decodeBits(bbits) + ":" + decodeBits(Molecule.cBondQFRareBondTypesBits));
  						System.out.println();
  						break;
  					case 37:    // datatype 'rare bond type'
  						no = decodeBits(bbits);
  						System.out.print("Rare Bond Type:");
  						for (int i=0; i<no; i++)
  							System.out.print(" " + decodeBits(bbits) + ":" + (decodeBits(1) == 0 ? "quadruple" : "quintuple"));
  						System.out.println();
  						break;
  					case 38:	//	datatype 'selected hydrogen'
  						no = decodeBits(abits);
  						int connBits = decodeBits(3);
  						System.out.print("Selected hydrogen:");
  						for (int i=0; i<no; i++) {
  							int atom = decodeBits(abits);
  							int conns = decodeBits(connBits);
  							System.out.print(" " + atom + ":" + conns);
  						}
  						System.out.println();
  						break;
  					case 39: //  datatype 'AtomQFOxydationState'
  						no = decodeBits(abits);
  						System.out.print("AtomQFOxydationState:");
  						for (int i=0; i<no; i++)
  							System.out.print(" " + decodeBits(abits) + ":" + decodeBits(Molecule.cAtomQFOxidationStateBits));
  						System.out.println();
  						break;
  					}
  				}
  
  			if (coordinates != null) {
  				if (coordinates[0] == '!' || coordinates[0] == '#') {    // new coordinate format
  					decodeBitsStart(coordinates, 1);
  					boolean coordsAre3D = (decodeBits(1) == 1);
  					boolean coordsAreAbsolute = (decodeBits(1) == 1);
  					int resolutionBits = 2 * decodeBits(4);
  					int binCount = (1 << resolutionBits);
  
  					double factor;
  
  					int hydrogenCount = 0;
  					int[] hCount = null;
  					if (coordinates[0] == '#') {    // we have 3D-coordinates that include implicit hydrogen coordinates
  						StereoMolecule mol = new IDCodeParserWithoutCoordinateInvention().getCompactMolecule(idcode);
  
  						// we need to cache hCount, because otherwise getImplicitHydrogens() would create helper arrays with every call
  						hCount = new int[allAtoms];
  						for (int atom = 0; atom < allAtoms; atom++)
  							hydrogenCount += (hCount[atom] = mol.getImplicitHydrogens(atom));
  					}
  
  					double[][] coords = new double[coordsAre3D ? 3 : 2][allAtoms + hydrogenCount];
  					int from = 0;
  					int bond = 0;
  					System.out.print("Raw coords:");
  					for (int atom = 1; atom < allAtoms; atom++) {
  						if (bond < allBonds && bondAtom[1][bond] == atom) {
  							from = bondAtom[0][bond++];
  							factor = 1.0;
  						} else {
  							from = 0;
  							factor = 8.0;
  						}
  						System.out.print(atom + " (");
  						coords[0][atom] = coords[0][from] + factor * (decodeBits(resolutionBits) - binCount / 2.0);
  						System.out.print((int) coords[0][atom] + ",");
  						coords[1][atom] = coords[1][from] + factor * (decodeBits(resolutionBits) - binCount / 2.0);
  						System.out.print((int) coords[1][atom]);
  						if (coordsAre3D) {
  							coords[2][atom] = coords[2][from] + factor * (decodeBits(resolutionBits) - binCount / 2.0);
  							System.out.print("," + (int) coords[0][atom]);
  						}
  						System.out.print("), ");
  						if ((atom & 3) == 3 || atom == allAtoms - 1)
  							System.out.println();
  					}
  
  					// with new format 2D and 3D coordinates are scaled to average bond lengths of 1.5 Angstrom
  					double avbl = 0;
  					if (allBonds != 0) {
  						for (bond = 0; bond < allBonds; bond++)
  							avbl += getDistance(coords, bondAtom[0][bond], bondAtom[1][bond], coordsAre3D);
  						avbl /= allBonds;    // avbl without hydrogen atoms
  					} else {
  						double defaultAVBL = coordsAre3D ? 1.5 : Molecule.getDefaultAverageBondLength();
  						if (allAtoms < 2) {
  							avbl = defaultAVBL;
  						} else {
  							double lowDistance = Double.MAX_VALUE;
  							for (int atom1 = 1; atom1 < allAtoms; atom1++) {
  								for (int atom2 = 0; atom2 < atom1; atom2++) {
  									double distance = getDistance(coords, atom1, atom2, coordsAre3D);
  									if (distance > 0 && distance < lowDistance)
  										lowDistance = distance;
  								}
  							}
  							avbl = (lowDistance == Double.MAX_VALUE) ? defaultAVBL : lowDistance;
  						}
  					}
  
  					if (coordinates[0] == '#') {    // we have 3D-coordinates that include implicit hydrogen coordinates
  						System.out.print("hydrogen coords (" + hydrogenCount + " expected): ");
  						int hydrogen = allAtoms;
  						for (int atom = 0; atom < allAtoms; atom++) {
  							if (hCount[atom] != 0)
  								System.out.print(atom);
  							for (int i = 0; i < hCount[atom]; i++) {
  								System.out.print(" (");
  								coords[0][hydrogen] = coords[0][atom] + (decodeBits(resolutionBits) - binCount / 2.0);
  								System.out.print((int) coords[0][hydrogen] + ",");
  								coords[1][hydrogen] = coords[1][atom] + (decodeBits(resolutionBits) - binCount / 2.0);
  								System.out.print((int) coords[1][hydrogen]);
  								if (coordsAre3D) {
  									coords[2][hydrogen] = coords[2][atom] + (decodeBits(resolutionBits) - binCount / 2.0);
  									System.out.print("," + (int) coords[2][hydrogen]);
  								}
  								System.out.print("), ");
  								hydrogen++;
  							}
  						}
  						System.out.println();
  					}
  
  					System.out.print(coordsAreAbsolute ? "absolute coords:" : "relative coords:");
  					if (hydrogenCount != 0)
  						System.out.println("Coordinates contain " + hydrogenCount + " hydrogen atoms!");
  
  					if (coordsAreAbsolute) {
  						double targetAVBL = decodeAVBL(decodeBits(resolutionBits), binCount);
  						double xOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  						double yOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  						double zOffset = 0;
  						if (coordsAre3D)
  							zOffset = targetAVBL * decodeShift(decodeBits(resolutionBits), binCount);
  						System.out.println("Abs-coord transformation: targetAVBL:" + targetAVBL + " xOffset:" + xOffset + " yOffset:" + yOffset + " zOffset:" + zOffset);
  
  						factor = targetAVBL / avbl;
  						for (int atom = 0; atom < allAtoms; atom++) {
  							coords[0][atom] = xOffset + factor * coords[0][atom];
  							coords[1][atom] = xOffset + factor * coords[1][atom];
  							if (coordsAre3D)
  								coords[2][atom] = xOffset + factor * coords[2][atom];
  						}
  					} else {
  						double targetAVBL = 1.5;
  						factor = targetAVBL / avbl;
  						for (int atom = 0; atom < allAtoms; atom++) {
  							System.out.print(atom + " (");
  							coords[0][atom] = coords[0][atom] * factor;
  							System.out.print(DoubleFormat.toString(coords[0][atom]) + ",");
  							coords[1][atom] = coords[1][atom] * factor;
  							System.out.print(DoubleFormat.toString(coords[1][atom]));
  							if (coordsAre3D) {
  								coords[2][atom] = coords[2][atom] * factor;
  								System.out.print("," + DoubleFormat.toString(coords[2][atom]));
  							}
  							System.out.print("), ");
  							if ((atom & 3) == 3 || atom == allAtoms - 1)
  								System.out.println();
  						}
  					}
  				}
  			}
  			System.out.println();
  		}
  		catch (Exception e) {
  			e.printStackTrace();
  		}
  	}
  
  	private double getDistance(double[][] coords, int atom1, int atom2, boolean coordsAre3D) {
  		double dx = coords[0][atom1] - coords[0][atom2];
  		double dy = coords[1][atom1] - coords[1][atom2];
  		double dz = coordsAre3D ? coords[2][atom1] - coords[2][atom2] : 0;
  		return Math.sqrt(dx*dx + dy*dy + dz*dz);
  		}
  	}