class TPSADescriptor extends AbstractMolecularDescriptor implements IMolecularDescriptor {

    @Override
    public DescriptorValue calculate(IAtomContainer atomContainer) {
        IAtomContainer ac;
        try {
            ac = (IAtomContainer) atomContainer.clone();
        } catch (CloneNotSupportedException e) {
            return getDummyDescriptorValue(e);
        }
        List<String> profiles = new ArrayList<String>();

        // calculate the set of all rings
        IRingSet rs;
        try {
            rs = (new AllRingsFinder()).findAllRings(ac);
        } catch (CDKException e) {
            return getDummyDescriptorValue(e);
        }
        // check aromaticity if the descriptor parameter is set to true
        if (checkAromaticity) {
            try {
                AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(ac);
                Aromaticity.cdkLegacy().apply(ac);
            } catch (CDKException e) {
                return getDummyDescriptorValue(e);
            }
        }

        // iterate over all atoms of ac
        for (IAtom atom : ac.atoms()) {
            if (atom.getAtomicNumber() == IElement.N || atom.getAtomicNumber() == IElement.O || atom.getAtomicNumber() == IElement.S
                    || atom.getAtomicNumber() == IElement.P) {
                int singleBondCount = 0;
                int doubleBondCount = 0;
                int tripleBondCount = 0;
                int aromaticBondCount = 0;
                double maxBondOrder = 0;
                double bondOrderSum = 0;
                int hCount = 0;
                int isIn3MemberRing = 0;

                // counting the number of single/double/triple/aromatic bonds
                List<IBond> connectedBonds = ac.getConnectedBondsList(atom);
                for (IBond connectedBond : connectedBonds) {
                    if (connectedBond.getFlag(CDKConstants.ISAROMATIC))
                        aromaticBondCount++;
                    else if (connectedBond.getOrder() == Order.SINGLE)
                        singleBondCount++;
                    else if (connectedBond.getOrder() == Order.DOUBLE)
                        doubleBondCount++;
                    else if (connectedBond.getOrder() == Order.TRIPLE) tripleBondCount++;
                }
                int formalCharge = atom.getFormalCharge();
                List<IAtom> connectedAtoms = ac.getConnectedAtomsList(atom);
                int numberOfNeighbours = connectedAtoms.size();

                // EXPLICIT hydrogens: count the number of hydrogen atoms
                for (int neighbourIndex = 0; neighbourIndex < numberOfNeighbours; neighbourIndex++)
                    if (((IAtom) connectedAtoms.get(neighbourIndex)).getAtomicNumber() == IElement.H) hCount++;
                // IMPLICIT hydrogens: count the number of hydrogen atoms and adjust other atom profile properties
                Integer implicitHAtoms = atom.getImplicitHydrogenCount();
                if (implicitHAtoms == CDKConstants.UNSET) {
                    implicitHAtoms = 0;
                }

                for (int hydrogenIndex = 0; hydrogenIndex < implicitHAtoms; hydrogenIndex++) {
                    hCount++;
                    numberOfNeighbours++;
                    singleBondCount++;
                }
                // Calculate bond order sum using the counters of single/double/triple/aromatic bonds
                bondOrderSum += singleBondCount * 1.0;
                bondOrderSum += doubleBondCount * 2.0;
                bondOrderSum += tripleBondCount * 3.0;
                bondOrderSum += aromaticBondCount * 1.5;
                // setting maxBondOrder
                if (singleBondCount > 0) maxBondOrder = 1.0;
                if (aromaticBondCount > 0) maxBondOrder = 1.5;
                if (doubleBondCount > 0) maxBondOrder = 2.0;
                if (tripleBondCount > 0) maxBondOrder = 3.0;

                // isIn3MemberRing checker
                if (rs.contains(atom)) {
                    IRingSet rsAtom = rs.getRings(atom);
                    for (int ringSetIndex = 0; ringSetIndex < rsAtom.getAtomContainerCount(); ringSetIndex++) {
                        IRing ring = (IRing) rsAtom.getAtomContainer(ringSetIndex);
                        if (ring.getRingSize() == 3) isIn3MemberRing = 1;
                    }
                }
                // create a profile of the current atom (atoms[atomIndex]) according to the profile definition in the constructor
                String profile = atom.getSymbol() + "+" + maxBondOrder + "+" + bondOrderSum + "+" + numberOfNeighbours
                        + "+" + hCount + "+" + formalCharge + "+" + aromaticBondCount + "+" + isIn3MemberRing + "+"
                        + singleBondCount + "+" + doubleBondCount + "+" + tripleBondCount;
                //logger.debug("tpsa profile: "+ profile);
                profiles.add(profile);
            }
        }
        // END OF ATOM LOOP
        // calculate the tpsa for the AtomContainer ac
        double tpsa = 0;
        for (int profileIndex = 0; profileIndex < profiles.size(); profileIndex++) {
            if (map.containsKey(profiles.get(profileIndex))) {
                tpsa += (Double) map.get(profiles.get(profileIndex));
                //logger.debug("tpsa contribs: " + profiles.elementAt(profileIndex) + "\t" + ((Double)map.get(profiles.elementAt(profileIndex))).doubleValue());
            }
        }
        profiles.clear(); // remove all profiles from the profiles-Vector
        //logger.debug("tpsa: " + tpsa);

        return new DescriptorValue(getSpecification(), getParameterNames(), getParameters(), new DoubleResult(tpsa),
                getDescriptorNames());

    }

    public  TPSADescriptor();

}