/*  $RCSfile$
 *  $Author$
 *  $Date$
 *  $Revision$
 *
 *  Copyright (C) 2001-2007  The Chemistry Development Kit (CDK) project
 *
 *  Contact: cdk-devel@lists.sourceforge.net
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *  All we ask is that proper credit is given for our work, which includes
 *  - but is not limited to - adding the above copyright notice to the beginning
 *  of your source code files, and to any copyright notice that you may distribute
 *  with programs based on this work.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
package org.openscience.cdk.tools;

import java.util.List;

import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.config.AtomTypeFactory;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IAtomType;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemObjectBuilder;
import org.openscience.cdk.interfaces.IPseudoAtom;
import org.openscience.cdk.interfaces.IRingSet;
import org.openscience.cdk.ringsearch.RingPartitioner;
import org.openscience.cdk.ringsearch.SSSRFinder;
import org.openscience.cdk.tools.manipulator.BondManipulator;
import org.openscience.cdk.tools.manipulator.RingSetManipulator;

/**
 * Provides methods for checking whether an atoms valences are saturated with
 * respect to a particular atom type.
 *
 * <p>Important: this class does not deal with hybridization states, which makes
 * it fail, for example, for situations where bonds are marked as aromatic (either
 * 1.5 or single an AROMATIC).
 *
 * @author     steinbeck
 * @author  Egon Willighagen
 * @cdk.created    2001-09-04
 *
 * @cdk.keyword    saturation
 * @cdk.keyword    atom, valency
 * 
 * @cdk.module     valencycheck
 * @cdk.githash
 */
@TestClass("org.openscience.cdk.tools.SaturationCheckerTest")
public class SaturationChecker implements IValencyChecker, IDeduceBondOrderTool {

	AtomTypeFactory structgenATF;

	private LoggingTool logger;

	public SaturationChecker() {
		logger = new LoggingTool(this);
	}

    /**
     * @param builder the ChemObjectBuilder implementation used to construct the AtomType's.
     */
    protected AtomTypeFactory getAtomTypeFactory(IChemObjectBuilder builder) throws CDKException {
        if (structgenATF == null) {
            try {
                structgenATF = AtomTypeFactory.getInstance(
                    "org/openscience/cdk/config/data/structgen_atomtypes.xml", 
                    builder
                );
            } catch (Exception exception) {
                logger.debug(exception);
                throw new CDKException("Could not instantiate AtomTypeFactory!", exception);
            }
        }
        return structgenATF;
    }

	public boolean hasPerfectConfiguration(IAtom atom, IAtomContainer ac) throws CDKException
	{
		double bondOrderSum = ac.getBondOrderSum(atom);
		IBond.Order maxBondOrder = ac.getMaximumBondOrder(atom);
		IAtomType[] atomTypes = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(atom.getSymbol());
    if(atomTypes.length==0)
      return true;
		logger.debug("*** Checking for perfect configuration ***");
		try
		{
			logger.debug("Checking configuration of atom " + ac.getAtomNumber(atom));
			logger.debug("Atom has bondOrderSum = " + bondOrderSum);
			logger.debug("Atom has max = " + bondOrderSum);
		} catch (Exception exc)
		{
		}
		for (int f = 0; f < atomTypes.length; f++)
		{
			if (bondOrderSum == atomTypes[f].getBondOrderSum() && 
                maxBondOrder == atomTypes[f].getMaxBondOrder())
			{
				try
				{
					logger.debug("Atom " + ac.getAtomNumber(atom) + " has perfect configuration");
				} catch (Exception exc)
				{
				}
				return true;
			}
		}
		try
		{
			logger.debug("*** Atom " + ac.getAtomNumber(atom) + " has imperfect configuration ***");
		} catch (Exception exc)
		{
		}
		return false;
	}

    /**
     * Determines of all atoms on the AtomContainer are saturated.
     */
	public boolean isSaturated(IAtomContainer container) throws CDKException {
        return allSaturated(container);
    }
	public boolean allSaturated(IAtomContainer ac) throws CDKException
	{
        logger.debug("Are all atoms saturated?");
        for (int f = 0; f < ac.getAtomCount(); f++) {
            if (!isSaturated(ac.getAtom(f), ac)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns whether a bond is unsaturated. A bond is unsaturated if 
     * <b>both</b> Atoms in the bond are unsaturated.
     */
    public boolean isUnsaturated(IBond bond, IAtomContainer atomContainer) throws CDKException {

    	IAtom[] atoms = BondManipulator.getAtomArray(bond);
        boolean isUnsaturated = true;
        for (int i=0; i<atoms.length; i++) {
            isUnsaturated = isUnsaturated && !isSaturated(atoms[i], atomContainer);
        }
        return isUnsaturated;
    }
    
    /**
     * Returns whether a bond is saturated. A bond is saturated if 
     * <b>both</b> Atoms in the bond are saturated.
     */
    public boolean isSaturated(IBond bond, IAtomContainer atomContainer) throws CDKException {
    	IAtom[] atoms = BondManipulator.getAtomArray(bond);
        boolean isSaturated = true;
        for (int i=0; i<atoms.length; i++) {
            isSaturated = isSaturated && isSaturated(atoms[i], atomContainer);
        }
        return isSaturated;
    }
    
    /**
     * Checks whether an Atom is saturated by comparing it with known AtomTypes.
     */
	public boolean isSaturated(IAtom atom, IAtomContainer ac) throws CDKException {
		IAtomType[] atomTypes = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(atom.getSymbol());
        if(atomTypes.length==0)
          return true;
        double bondOrderSum = ac.getBondOrderSum(atom);
        IBond.Order maxBondOrder = ac.getMaximumBondOrder(atom);
        Integer hcount = atom.getHydrogenCount() == CDKConstants.UNSET ?  0 : atom.getHydrogenCount();
        Integer charge = atom.getFormalCharge() == CDKConstants.UNSET ? 0 : atom.getFormalCharge();
        try {
            logger.debug("*** Checking saturation of atom ", atom.getSymbol(), "" + ac.getAtomNumber(atom) + " ***");
            logger.debug("bondOrderSum: " + bondOrderSum);
            logger.debug("maxBondOrder: " + maxBondOrder);
            logger.debug("hcount: " + hcount);
        } catch (Exception exc) {
            logger.debug(exc);
        }
        for (int f = 0; f < atomTypes.length; f++) {
            if (bondOrderSum - charge + hcount == atomTypes[f].getBondOrderSum() && 
                !BondManipulator.isHigherOrder(maxBondOrder, atomTypes[f].getMaxBondOrder())) {
                    logger.debug("*** Good ! ***");
                    return true;
                }
        }
        logger.debug("*** Bad ! ***");
        return false;
    }

	/**
	 * Checks if the current atom has exceeded its bond order sum value.
	 *
	 * @param  atom The Atom to check
	 * @param  ac   The atomcontainer context
	 * @return      oversaturated or not
	 */
	public boolean isOverSaturated(IAtom atom, IAtomContainer ac) throws CDKException
	{
		IAtomType[] atomTypes = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(atom.getSymbol());
    if(atomTypes.length==0)
      return false;
		double bondOrderSum = ac.getBondOrderSum(atom);
		IBond.Order maxBondOrder = ac.getMaximumBondOrder(atom);
        Integer hcount = atom.getHydrogenCount() == CDKConstants.UNSET ?  0 : atom.getHydrogenCount();
        Integer charge = atom.getFormalCharge() == CDKConstants.UNSET ? 0 : atom.getFormalCharge();
		try
		{
			logger.debug("*** Checking saturation of atom " + ac.getAtomNumber(atom) + " ***");
			logger.debug("bondOrderSum: " + bondOrderSum);
			logger.debug("maxBondOrder: " + maxBondOrder);
			logger.debug("hcount: " + hcount);
		} catch (Exception exc)
		{
		}
		for (int f = 0; f < atomTypes.length; f++)
		{
			if (bondOrderSum - charge + hcount > atomTypes[f].getBondOrderSum())
			{
				logger.debug("*** Good ! ***");
				return true;
			}
		}
		logger.debug("*** Bad ! ***");
		return false;
	}
    
	/**
	 * Returns the currently maximum formable bond order for this atom.
	 *
	 * @param  atom  The atom to be checked
	 * @param  ac    The AtomContainer that provides the context
	 * @return       the currently maximum formable bond order for this atom
	 */
	public double getCurrentMaxBondOrder(IAtom atom, IAtomContainer ac) throws CDKException
	{
		IAtomType[] atomTypes = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(atom.getSymbol());
    if(atomTypes.length==0)
      return 0;
		double bondOrderSum = ac.getBondOrderSum(atom);
        Integer hcount = atom.getHydrogenCount() == CDKConstants.UNSET ?  0 : atom.getHydrogenCount();
		double max = 0;
		double current = 0;
		for (int f = 0; f < atomTypes.length; f++)
		{
			current = hcount + bondOrderSum;
			if (atomTypes[f].getBondOrderSum() - current > max)
			{
				max = atomTypes[f].getBondOrderSum() - current;
			}
		}
		return max;
	}


    /**
     * Resets the bond orders of all atoms to 1.0.
     */
    public void unsaturate(IAtomContainer atomContainer) {
        for (IBond bond : atomContainer.bonds()) bond.setOrder(CDKConstants.BONDORDER_SINGLE);
    }
    
    /**
     * Resets the bond order of the Bond to 1.0.
     */
    public void unsaturateBonds(IAtomContainer container) {
        for (IBond bond : container.bonds()) bond.setOrder(CDKConstants.BONDORDER_SINGLE);        
    }

	/**
	 * Saturates a molecule by setting appropriate bond orders.
	 * This method is known to fail, especially on pyrolle-like compounts.
	 * Consider using import org.openscience.cdk.smiles.DeduceBondSystemTool, which should work better
	 *
	 * @cdk.keyword bond order, calculation
     * @cdk.created 2003-10-03
	 */
    public void newSaturate(IAtomContainer atomContainer) throws CDKException {
        logger.info("Saturating atomContainer by adjusting bond orders...");
        boolean allSaturated = allSaturated(atomContainer);
        if (!allSaturated) {
        	IBond[] bonds = new IBond[atomContainer.getBondCount()];
        	for (int i=0; i<bonds.length; i++) bonds[i] = atomContainer.getBond(i);
            boolean succeeded = newSaturate(bonds, atomContainer);
            for(int i=0;i<bonds.length;i++){
              if(bonds[i].getOrder() == IBond.Order.DOUBLE && bonds[i].getFlag(CDKConstants.ISAROMATIC) && (bonds[i].getAtom(0).getSymbol().equals("N") && bonds[i].getAtom(1).getSymbol().equals("N"))){
                int atomtohandle=0;
                if(bonds[i].getAtom(0).getSymbol().equals("N"))
                  atomtohandle=1;
                java.util.List bondstohandle=atomContainer.getConnectedBondsList(bonds[i].getAtom(atomtohandle));
                for(int k=0;k<bondstohandle.size();k++){
                	IBond bond = (IBond)bondstohandle.get(k);
                  if(bond.getOrder() == IBond.Order.SINGLE && bond.getFlag(CDKConstants.ISAROMATIC)){
                    bond.setOrder(IBond.Order.DOUBLE);
                    bonds[i].setOrder(IBond.Order.SINGLE);
                    break;
                  }
                }
              }
            }
            if (!succeeded) {
                throw new CDKException("Could not saturate this atomContainer!");
            }
        }
    }

    /**
     * Saturates a set of Bonds in an AtomContainer.
	 * This method is known to fail, especially on pyrolle-like compounts.
	 * Consider using import org.openscience.cdk.smiles.DeduceBondSystemTool, which should work better
     */
    public boolean newSaturate(IBond[] bonds, IAtomContainer atomContainer) throws CDKException {
        logger.debug("Saturating bond set of size: " + bonds.length);
        boolean bondsAreFullySaturated = true;
        if (bonds.length > 0) {
        	IBond bond = bonds[0];

            // determine bonds left
            int leftBondCount = bonds.length-1;
            IBond[] leftBonds = new IBond[leftBondCount];
            System.arraycopy(bonds, 1, leftBonds, 0, leftBondCount);

            // examine this bond
            if (isUnsaturated(bond, atomContainer)) {
                // either this bonds should be saturated or not
                
                // try to leave this bond unsaturated and saturate the left bondssaturate this bond
                if (leftBondCount > 0) {
                    logger.debug("Recursing with unsaturated bond with #bonds: " + leftBondCount);
                    bondsAreFullySaturated = newSaturate(leftBonds, atomContainer) 
                                             && !isUnsaturated(bond, atomContainer);
                } else {
                    bondsAreFullySaturated = false;
                }

                // ok, did it work? if not, saturate this bond, and recurse
                if (!bondsAreFullySaturated) {
                    logger.debug("First try did not work...");
                    // ok, revert saturating this bond, and recurse again
                    boolean couldSaturate = newSaturate(bond, atomContainer);
                    if (couldSaturate) {
                        if (leftBondCount > 0) {
                            logger.debug("Recursing with saturated bond with #bonds: " + leftBondCount);
                            bondsAreFullySaturated = newSaturate(leftBonds, atomContainer);
                        } else {
                            bondsAreFullySaturated = true;
                        }
                    } else {
                        bondsAreFullySaturated = false;
                        // no need to recurse, because we already know that this bond
                        // unsaturated does not work
                    }
                }
            } else if (isSaturated(bond, atomContainer)) {
                logger.debug("This bond is already saturated.");
                if (leftBondCount > 0) {
                    logger.debug("Recursing with #bonds: " + leftBondCount);
                    bondsAreFullySaturated = newSaturate(leftBonds, atomContainer);
                } else {
                    bondsAreFullySaturated = true;
                }
            } else {
                logger.debug("Cannot saturate this bond");
                // but, still recurse (if possible)
                if (leftBondCount > 0) {
                    logger.debug("Recursing with saturated bond with #bonds: " + leftBondCount);
                    bondsAreFullySaturated = newSaturate(leftBonds, atomContainer) 
                                             && !isUnsaturated(bond, atomContainer);
                } else {
                    bondsAreFullySaturated = !isUnsaturated(bond, atomContainer);
                }
            }
        }
        logger.debug("Is bond set fully saturated?: " + bondsAreFullySaturated);
        logger.debug("Returning to level: " + (bonds.length + 1));
        return bondsAreFullySaturated;
    }
    
    /**
     * Saturate atom by adjusting its bond orders.
	 * This method is known to fail, especially on pyrolle-like compounts.
	 * Consider using import org.openscience.cdk.smiles.DeduceBondSystemTool, which should work better
     */
    public boolean newSaturate(IBond bond, IAtomContainer atomContainer) throws CDKException {
    	IAtom[] atoms = BondManipulator.getAtomArray(bond);
    	IAtom atom = atoms[0];
    	IAtom partner = atoms[1];
        logger.debug("  saturating bond: ", atom.getSymbol(), "-", partner.getSymbol());
        IAtomType[] atomTypes1 = getAtomTypeFactory(bond.getBuilder()).getAtomTypes(atom.getSymbol());
        IAtomType[] atomTypes2 = getAtomTypeFactory(bond.getBuilder()).getAtomTypes(partner.getSymbol());
        boolean bondOrderIncreased = true;
        while (bondOrderIncreased && !isSaturated(bond, atomContainer)) {
            logger.debug("Can increase bond order");
            bondOrderIncreased = false;
            for (int atCounter1=0; atCounter1<atomTypes1.length&& !bondOrderIncreased; atCounter1++) {
                IAtomType aType1 = atomTypes1[atCounter1];
                logger.debug("  condidering atom type: ", aType1);
                if (couldMatchAtomType(atomContainer, atom, aType1)) {
                    logger.debug("  trying atom type: ", aType1);
                    for (int atCounter2=0; atCounter2<atomTypes2.length && !bondOrderIncreased; atCounter2++) {
                        IAtomType aType2 = atomTypes2[atCounter2];
                        logger.debug("  condidering partner type: ", aType1);
                        if (couldMatchAtomType(atomContainer, partner, atomTypes2[atCounter2])) {
                            logger.debug("    with atom type: ", aType2);
                            if (!BondManipulator.isLowerOrder(bond.getOrder(), aType2.getMaxBondOrder()) || 
                                !BondManipulator.isLowerOrder(bond.getOrder(), aType1.getMaxBondOrder())) {
                                logger.debug("Bond order not increased: atoms has reached (or exceeded) maximum bond order for this atom type");
                            } else if (BondManipulator.isLowerOrder(bond.getOrder(), aType2.getMaxBondOrder()) &&
                                       BondManipulator.isLowerOrder(bond.getOrder(), aType1.getMaxBondOrder())) {
                                BondManipulator.increaseBondOrder(bond);
                                logger.debug("Bond order now " + bond.getOrder());
                                bondOrderIncreased = true;
                            }
                        }
                    }
                }
            }
        }
        return isSaturated(bond, atomContainer);
    }

    /**
     * Determines if the atom can be of type AtomType.
     */
    public boolean couldMatchAtomType(IAtomContainer atomContainer, IAtom atom, IAtomType atomType) {
        logger.debug("   ... matching atom ", atom.getSymbol(), " vs ", atomType);
        if (atomContainer.getBondOrderSum(atom) + atom.getHydrogenCount() < atomType.getBondOrderSum()) {
           logger.debug("    Match!");
           return true;
        }
        logger.debug("    No Match");
        return false;
    }

    /**
     * The method is known to fail for certain compounds. For more information, see
     * cdk.test.limitations package.
	 * This method is known to fail, especially on pyrolle-like compounts.
	 * Consider using import org.openscience.cdk.smiles.DeduceBondSystemTool, which should work better
     *
     */
    public void saturate(IAtomContainer atomContainer) throws CDKException {
        /* newSaturate(atomContainer);
    }
    public void oldSaturate(AtomContainer atomContainer) throws CDKException { */
    	IAtom partner = null;
		IAtom atom = null;
		java.util.List partners = null;
		IAtomType[] atomTypes1 = null;
		IAtomType[] atomTypes2 = null;
		IBond bond = null;
		for (int i = 1; i < 4; i++)
		{
			// handle atoms with degree 1 first and then proceed to higher order
			for (int f = 0; f < atomContainer.getAtomCount(); f++)
			{
				atom = atomContainer.getAtom(f);
				logger.debug("symbol: ", atom.getSymbol());
				atomTypes1 = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(atom.getSymbol());
        if(atomTypes1.length>0){
          logger.debug("first atom type: ", atomTypes1[0]);
          if (atomContainer.getConnectedBondsCount(atom) == i)
          {
              Integer hcount = atom.getHydrogenCount() == CDKConstants.UNSET ? 0 : atom.getHydrogenCount();
            if (atom.getFlag(CDKConstants.ISAROMATIC) && atomContainer.getBondOrderSum(atom) < atomTypes1[0].getBondOrderSum() - hcount){
              partners = atomContainer.getConnectedAtomsList(atom);
              for (int g = 0; g < partners.size(); g++)
              {
                partner = (IAtom)partners.get(g);
                logger.debug("Atom has " + partners.size() + " partners");
                atomTypes2 = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(partner.getSymbol());
                if(atomTypes2.length==0)
                  return;

                  hcount = partner.getHydrogenCount() == CDKConstants.UNSET ? 0: partner.getHydrogenCount();
                if (atomContainer.getBond(partner,atom).getFlag(CDKConstants.ISAROMATIC) &&
                        atomContainer.getBondOrderSum(partner) < atomTypes2[0].getBondOrderSum() - hcount)
                {
                  logger.debug("Partner has " + atomContainer.getBondOrderSum(partner) + ", may have: " + atomTypes2[0].getBondOrderSum());
                  bond = atomContainer.getBond(atom, partner);
                  logger.debug("Bond order was " + bond.getOrder());
                  BondManipulator.increaseBondOrder(bond);
                  logger.debug("Bond order now " + bond.getOrder());
                  break;
                }
              }
            }

              Double bondOrderSum = atomTypes1[0].getBondOrderSum() == CDKConstants.UNSET ? 0.0 :
                      atomTypes1[0].getBondOrderSum();
              Integer hydrogenCount = atom.getHydrogenCount() == CDKConstants.UNSET ? 0 : atom.getHydrogenCount();
              Double atomContainerBondOrderSum = atomContainer.getBondOrderSum(atom);   
              if (atomContainerBondOrderSum == CDKConstants.UNSET) atomContainerBondOrderSum = 0.0;

              if (atomContainerBondOrderSum < bondOrderSum - hydrogenCount) {
                  logger.debug("Atom has " + atomContainerBondOrderSum + ", may have: " + bondOrderSum);
                  partners = atomContainer.getConnectedAtomsList(atom);
                  for (int g = 0; g < partners.size(); g++) {
                      partner = (IAtom) partners.get(g);
                      logger.debug("Atom has " + partners.size() + " partners");
                      atomTypes2 = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(partner.getSymbol());
                      if (atomTypes2.length == 0)
                          return;

                      Double bos2 = atomTypes2[0].getBondOrderSum();
                      Integer hc2 = partner.getHydrogenCount();
                      Double acbos2 = atomContainer.getBondOrderSum(partner);
                      if (bos2 == CDKConstants.UNSET) bos2= 0.0;
                      if (hc2 == CDKConstants.UNSET) hc2 = 0;
                      if (acbos2 == CDKConstants.UNSET) acbos2 = 0.0;

                      if (acbos2 < bos2 - hc2) {
                          logger.debug("Partner has " + acbos2 + ", may have: " + bos2);
                          bond = atomContainer.getBond(atom, partner);
                          logger.debug("Bond order was " + bond.getOrder());
                          BondManipulator.increaseBondOrder(bond);
                          logger.debug("Bond order now " + bond.getOrder());
                          break;
                      }
                  }
            }
          }
				}
			}
		}
    }
    
	public void saturateRingSystems(IAtomContainer atomContainer) throws CDKException
	{
		IRingSet rs = new SSSRFinder(atomContainer.getBuilder().newMolecule(atomContainer)).findSSSR();
		List ringSets = RingPartitioner.partitionRings(rs);
		IAtomContainer ac = null;
		IAtom atom = null;
		int temp[];
		for (int f = 0; f < ringSets.size(); f++)
		{
			rs = (IRingSet)ringSets.get(f);
			List containers = RingSetManipulator.getAllAtomContainers(rs);
			for (int counter=0; counter<containers.size(); counter++) {
				ac = (IAtomContainer)containers.get(counter);
				temp = new int[ac.getAtomCount()];
				for (int g = 0; g < ac.getAtomCount(); g++)
				{
					atom = ac.getAtom(g);
					temp[g] = atom.getHydrogenCount();
					atom.setHydrogenCount(atomContainer.getConnectedBondsCount(atom) - ac.getConnectedBondsCount(atom) - temp[g]);
				}
				saturate(ac);
				for (int g = 0; g < ac.getAtomCount(); g++)
				{
					atom = ac.getAtom(g);
					atom.setHydrogenCount(temp[g]);
				}
			}
		}
	}
	
	/*
	 * Recursivly fixes bond orders in a molecule for 
	 * which only connectivities but no bond orders are know.
	 *
	 *@ param  molecule  The molecule to fix the bond orders for
	 *@ param  bond      The number of the bond to treat in this recursion step
	 *@ return           true if the bond order which was implemented was ok.
	 */
	/*private boolean recursiveBondOrderFix(Molecule molecule, int bondNumber)
	{	

		Atom partner = null;
		Atom atom = null;
		Atom[] partners = null;
		AtomType[] atomTypes1 = null;
		AtomType[] atomTypes2 = null;
		int maxBondOrder = 0;
		int oldBondOrder = 0;
		if (bondNumber < molecule.getBondCount())
		{	
			Bond bond = molecule.getBondAt(f);
		}
		else 
		{
			return true;
		}
		atom = bond.getAtomAt(0);
		partner = bond.getAtomAt(1);
		atomTypes1 = atf.getAtomTypes(atom.getSymbol(), atf.ATOMTYPE_ID_STRUCTGEN);
		atomTypes2 = atf.getAtomTypes(partner.getSymbol(), atf.ATOMTYPE_ID_STRUCTGEN);
		maxBondOrder = Math.min(atomTypes1[0].getMaxBondOrder(), atomTypes2[0].getMaxBondOrder());
		for (int f = 1; f <= maxBondOrder; f++)
		{
			oldBondOrder = bond.getOrder()
			bond.setOrder(f);
			if (!isOverSaturated(atom, molecule) && !isOverSaturated(partner, molecule))
			{
				if (!recursiveBondOrderFix(molecule, bondNumber + 1)) break;
					
			}
			else
			{
				bond.setOrder(oldBondOrder);
				return false;	
			}
		}
		return true;
	}*/

	/**
	 * Calculate the number of missing hydrogens by substracting the number of
	 * bonds for the atom from the expected number of bonds. Charges are included
	 * in the calculation. The number of expected bonds is defined by the AtomType
	 * generated with the AtomTypeFactory.
	 *
	 * @param  atom      Description of the Parameter
	 * @param  container Description of the Parameter
	 * @return           Description of the Return Value
	 * @see              AtomTypeFactory
	 */
	public int calculateNumberOfImplicitHydrogens(IAtom atom, IAtomContainer container) throws CDKException {
        return this.calculateNumberOfImplicitHydrogens(atom, container, false);
    }
    
	public int calculateNumberOfImplicitHydrogens(IAtom atom) throws CDKException {
        java.util.List bonds = new java.util.ArrayList();
        return this.calculateNumberOfImplicitHydrogens(atom, 0, 0, bonds, false);
    }

	public int calculateNumberOfImplicitHydrogens(IAtom atom, IAtomContainer container, boolean throwExceptionForUnknowAtom) throws CDKException {
        return this.calculateNumberOfImplicitHydrogens(atom, 
            container.getBondOrderSum(atom),
            container.getConnectedSingleElectronsCount(atom),
            container.getConnectedBondsList(atom),
            throwExceptionForUnknowAtom
        );
    }
    
    /**
	 * Calculate the number of missing hydrogens by substracting the number of
	 * bonds for the atom from the expected number of bonds. Charges are included
	 * in the calculation. The number of expected bonds is defined by the AtomType
	 * generated with the AtomTypeFactory.
	 *
	 * @param  atom      Description of the Parameter
	 * @param  throwExceptionForUnknowAtom  Should an exception be thrown if an unknown atomtype is found or 0 returned ?
	 * @return           Description of the Return Value
	 * @see              AtomTypeFactory
	 */
	public int calculateNumberOfImplicitHydrogens(IAtom atom, double bondOrderSum, double singleElectronSum, java.util.List connectedBonds, boolean throwExceptionForUnknowAtom) 
        throws CDKException {
        int missingHydrogen = 0;
        if (atom instanceof IPseudoAtom) {
            // don't figure it out... it simply does not lack H's
        } else if (atom.getAtomicNumber() != null &&
                   atom.getAtomicNumber() == 1 || atom.getSymbol().equals("H")) {
            missingHydrogen = (int) (1 - bondOrderSum - singleElectronSum - atom.getFormalCharge());
        } else {
            logger.info("Calculating number of missing hydrogen atoms");
            // get default atom
            IAtomType[] atomTypes = getAtomTypeFactory(atom.getBuilder()).getAtomTypes(atom.getSymbol());
            if(atomTypes.length==0 && throwExceptionForUnknowAtom)
              return 0;
            logger.debug("Found atomtypes: " + atomTypes.length);
            if (atomTypes.length > 0) {
                IAtomType defaultAtom = atomTypes[0];
                logger.debug("DefAtom: ", defaultAtom);

                Integer formalCharge = atom.getFormalCharge();
                if (formalCharge == null) formalCharge = 0;

                Double tmpBondOrderSum = defaultAtom.getBondOrderSum();
                if (tmpBondOrderSum == null) tmpBondOrderSum = 0.0;

                 missingHydrogen = (int) (tmpBondOrderSum -
                    bondOrderSum - singleElectronSum + formalCharge);

                if (atom.getFlag(CDKConstants.ISAROMATIC)){
                    boolean subtractOne=true;
                    for(int i=0;i<connectedBonds.size();i++){
                    	IBond conBond = (IBond)connectedBonds.get(i);
                        if(conBond.getOrder() == IBond.Order.DOUBLE || conBond.getFlag(CDKConstants.ISAROMATIC))
                            subtractOne=false;
                    }
                    if(subtractOne)
                        missingHydrogen--;
                }
                logger.debug("Atom: ", atom.getSymbol());
                logger.debug("  max bond order: " + tmpBondOrderSum);
                logger.debug("  bond order sum: " + bondOrderSum);
                logger.debug("  charge        : " + formalCharge);
            } else {
                logger.warn("Could not find atom type for ", atom.getSymbol());
            }
        }
        return missingHydrogen;
    }

}

