class Abbreviations implements Iterable<String> {

    public List<Sgroup> generate(final IAtomContainer mol) {

        // mark which atoms have already been abbreviated or are
        // part of an existing Sgroup
        Set<IAtom> usedAtoms = new HashSet<>();
        List<Sgroup> sgroups = mol.getProperty(CDKConstants.CTAB_SGROUPS);
        if (sgroups != null) {
            for (Sgroup sgroup : sgroups)
                usedAtoms.addAll(sgroup.getAtoms());
        }

        final List<Sgroup> newSgroups = new ArrayList<>();

        // disconnected abbreviations, salts, common reagents, large compounds
        if (usedAtoms.isEmpty()) {
            try {
                IAtomContainer copy = AtomContainerManipulator.copyAndSuppressedHydrogens(mol);
                String cansmi = usmigen.create(copy);
                String label = disconnectedAbbreviations.get(cansmi);

                if (label != null && !disabled.contains(label) && contractSingleFragments) {
                    Sgroup sgroup = new Sgroup();
                    sgroup.setType(SgroupType.CtabAbbreviation);
                    sgroup.setSubscript(label);
                    for (IAtom atom : mol.atoms())
                        sgroup.addAtom(atom);
                    return Collections.singletonList(sgroup);
                } else if (cansmi.contains(".")) {
                    IAtomContainerSet parts = ConnectivityChecker.partitionIntoMolecules(mol);


                    // leave one out
                    Sgroup best = null;
                    for (int i = 0; i < parts.getAtomContainerCount(); i++) {
                        IAtomContainer a = parts.getAtomContainer(i);
                        IAtomContainer b = a.getBuilder().newAtomContainer();
                        for (int j = 0; j < parts.getAtomContainerCount(); j++)
                            if (j != i)
                                b.add(parts.getAtomContainer(j));
                        Sgroup sgroup1 = getAbbr(a);
                        Sgroup sgroup2 = getAbbr(b);
                        if (sgroup1 != null && sgroup2 != null && contractSingleFragments) {
                            Sgroup combined = new Sgroup();
                            label = null;
                            for (IAtom atom : sgroup1.getAtoms())
                                combined.addAtom(atom);
                            for (IAtom atom : sgroup2.getAtoms())
                                combined.addAtom(atom);
                            if (sgroup1.getSubscript().length() > sgroup2.getSubscript().length())
                                combined.setSubscript(sgroup1.getSubscript() + INTERPUNCT + sgroup2.getSubscript());
                            else
                                combined.setSubscript(sgroup2.getSubscript() + INTERPUNCT + sgroup1.getSubscript());
                            combined.setType(SgroupType.CtabAbbreviation);
                            return Collections.singletonList(combined);
                        }
                        if (sgroup1 != null && (best == null || sgroup1.getAtoms().size() > best.getAtoms().size()))
                            best = sgroup1;
                        if (sgroup2 != null && (best == null || sgroup2.getAtoms().size() < best.getAtoms().size()))
                            best = sgroup2;
                    }

                    if (best != null) {
                        newSgroups.add(best);
                        usedAtoms.addAll(best.getAtoms());
                    }
                }

            } catch (CDKException ignored) {
            }
        }

        List<IAtomContainer> fragments = generateFragments(mol);
        Multimap<IAtom, Sgroup> sgroupAdjs = ArrayListMultimap.create();

        for (IAtomContainer frag : fragments) {
            try {
                final String smi = usmigen.create(AtomContainerManipulator.copyAndSuppressedHydrogens(frag));
                final String label = connectedAbbreviations.get(smi);

                if (label == null || disabled.contains(label))
                    continue;

                boolean overlap = false;

                // note: first atom is '*'
                int numAtoms = frag.getAtomCount();
                int numBonds = frag.getBondCount();
                for (int i = 1; i < numAtoms; i++) {
                    if (usedAtoms.contains(frag.getAtom(i))) {
                        overlap = true;
                        break;
                    }
                }

                // overlaps with previous assignment
                if (overlap)
                    continue;

                // create new abbreviation SGroup
                Sgroup sgroup = new Sgroup();
                sgroup.setType(SgroupType.CtabAbbreviation);
                sgroup.setSubscript(label);

                IBond attachBond = frag.getBond(0).getProperty(CUT_BOND, IBond.class);
                IAtom attachAtom = null;
                sgroup.addBond(attachBond);
                for (int i = 1; i < numAtoms; i++) {
                    IAtom atom = frag.getAtom(i);
                    usedAtoms.add(atom);
                    sgroup.addAtom(atom);
                    if (attachBond.getBegin().equals(atom))
                        attachAtom = attachBond.getEnd();
                    else if (attachBond.getEnd().equals(atom))
                        attachAtom = attachBond.getBegin();
                }

                if (attachAtom != null)
                    sgroupAdjs.put(attachAtom, sgroup);
                newSgroups.add(sgroup);

             } catch (CDKException e) {
                // ignore
            }
        }

        if (!contractOnHetero)
            return newSgroups;

        // now collapse
        collapse:
        for (IAtom attach : mol.atoms()) {
            if (usedAtoms.contains(attach))
                continue;

            // skip charged or isotopic labelled, C or R/*, H, He
            if ((attach.getFormalCharge() != null && attach.getFormalCharge() != 0)
                || attach.getMassNumber() != null
                || attach.getAtomicNumber() == 6
                || attach.getAtomicNumber() < 2)
                continue;

            int hcount = attach.getImplicitHydrogenCount();
            Set<IAtom> xatoms   = new HashSet<>();
            Set<IBond> xbonds   = new HashSet<>();
            Set<IBond> newbonds = new HashSet<>();
            xatoms.add(attach);

            List<String> nbrSymbols = new ArrayList<>();
            Set<Sgroup> todelete = new HashSet<>();
            for (Sgroup sgroup : sgroupAdjs.get(attach)) {
                if (containsChargeChar(sgroup.getSubscript()))
                    continue;
                if (sgroup.getBonds().size() != 1)
                    continue;
                IBond xbond = sgroup.getBonds().iterator().next();
                xbonds.add(xbond);
                xatoms.addAll(sgroup.getAtoms());
                if (attach.getSymbol().length() == 1 &&
                    Character.isLowerCase(sgroup.getSubscript().charAt(0))) {
                    if (Elements.ofString(attach.getSymbol() + sgroup.getSubscript().charAt(0)) != Elements.Unknown)
                        continue collapse;
                }
                nbrSymbols.add(sgroup.getSubscript());
                todelete.add(sgroup);
            }
            int numSGrpNbrs = nbrSymbols.size();
            for (IBond bond : mol.getConnectedBondsList(attach)) {
                if (!xbonds.contains(bond)) {
                    IAtom nbr = bond.getOther(attach);
                    // contract terminal bonds
                    if (mol.getConnectedBondsCount(nbr) == 1) {
                        if (nbr.getMassNumber() != null ||
                            (nbr.getFormalCharge() != null && nbr.getFormalCharge() != 0)) {
                            newbonds.add(bond);
                        } else if (nbr.getAtomicNumber() == 1) {
                            hcount++;
                            xatoms.add(nbr);
                        } else if (nbr.getAtomicNumber() > 0){
                            nbrSymbols.add(newSymbol(nbr.getAtomicNumber(), nbr.getImplicitHydrogenCount(), false));
                            xatoms.add(nbr);
                        }
                    } else {
                        newbonds.add(bond);
                    }
                }
            }

            // reject if no symbols
            // reject if no bonds (<1), except if all symbols are identical... (HashSet.size==1)
            // reject if more that 2 bonds
            if (nbrSymbols.isEmpty() ||
                newbonds.size() < 1 && (new HashSet<>(nbrSymbols).size() != 1) ||
                newbonds.size() > 2)
                continue;

            // create the symbol
            StringBuilder sb = new StringBuilder();
            sb.append(newSymbol(attach.getAtomicNumber(), hcount, newbonds.size() == 0));
            String prev  = null;
            int    count = 0;
            Collections.sort(nbrSymbols, new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    int cmp = Integer.compare(o1.length(), o2.length());
                    if (cmp != 0) return cmp;
                    return o1.compareTo(o2);
                }
            });
            for (String nbrSymbol : nbrSymbols) {
                if (nbrSymbol.equals(prev)) {
                    count++;
                } else {
                    boolean useParen = count == 0 || countUpper(prev) > 1 || (prev != null && nbrSymbol.startsWith(prev));
                    appendGroup(sb, prev, count, useParen);
                    prev = nbrSymbol;
                    count = 1;
                }
            }
            appendGroup(sb, prev, count, false);

            // remove existing
            newSgroups.removeAll(todelete);

            // create new
            Sgroup newSgroup = new Sgroup();
            newSgroup.setType(SgroupType.CtabAbbreviation);
            newSgroup.setSubscript(sb.toString());
            for (IBond bond : newbonds)
                newSgroup.addBond(bond);
            for (IAtom atom : xatoms)
                newSgroup.addAtom(atom);

            newSgroups.add(newSgroup);
            usedAtoms.addAll(xatoms);
        }

        return newSgroups;
    }

    public  Abbreviations();

}