/* Copyright (C) 1997-2007  The Chemistry Development Kit (CDK) project
 *
 * Contact: cdk-devel@list.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.
 *
 * 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.aromaticity;

import java.io.InputStream;
import java.util.Iterator;
import java.util.List;

import javax.vecmath.Point2d;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.openscience.cdk.Atom;
import org.openscience.cdk.AtomContainer;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.interfaces.IElement;
import org.openscience.cdk.test.CDKTestCase;
import org.openscience.cdk.DefaultChemObjectBuilder;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.graph.SpanningTree;
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.IBond.Order;
import org.openscience.cdk.interfaces.IChemObjectBuilder;
import org.openscience.cdk.interfaces.IRing;
import org.openscience.cdk.interfaces.IRingSet;
import org.openscience.cdk.io.MDLV2000Reader;
import org.openscience.cdk.ringsearch.AllRingsFinder;
import org.openscience.cdk.ringsearch.SSSRFinder;
import org.openscience.cdk.silent.SilentChemObjectBuilder;
import org.openscience.cdk.smiles.SmilesParser;
import org.openscience.cdk.templates.TestMoleculeFactory;
import org.openscience.cdk.tools.CDKHydrogenAdder;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
import org.openscience.cdk.tools.manipulator.RingSetManipulator;

/**
 * @author steinbeck
 * @author egonw
 * @cdk.module test-standard
 * @cdk.created 2002-10-06
 */
class CDKHueckelAromaticityDetectorTest extends CDKTestCase {

    CDKHueckelAromaticityDetectorTest() {
        super();
    }

    @Test
    void testDetectAromaticity_IAtomContainer() throws Exception {
        IAtomContainer mol = makeAromaticMolecule();

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        boolean isAromatic = Aromaticity.cdkLegacy().apply(mol);
        Assertions.assertTrue(isAromatic, "Molecule is expected to be marked aromatic!");

        int numberOfAromaticAtoms = 0;
        for (int i = 0; i < mol.getAtomCount(); i++) {
            if (mol.getAtom(i).getFlag(CDKConstants.ISAROMATIC)) numberOfAromaticAtoms++;
        }
        Assertions.assertEquals(6, numberOfAromaticAtoms);

        int numberOfAromaticBonds = 0;
        for (int i = 0; i < mol.getBondCount(); i++) {
            if (mol.getBond(i).getFlag(CDKConstants.ISAROMATIC)) numberOfAromaticBonds++;
        }
        Assertions.assertEquals(6, numberOfAromaticBonds);

    }

    @Test
    void testCDKHueckelAromaticityDetector() {
        // For autogenerated constructor
        CDKHueckelAromaticityDetector detector = new CDKHueckelAromaticityDetector();
        Assertions.assertNotNull(detector);
    }

    @Test
    void testNMethylPyrrol() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer mol = sp.parseSmiles("c1ccn(C)c1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol), "Expected the molecule to be aromatic.");

        IRingSet ringset = (new SSSRFinder(mol)).findSSSR();
        int numberOfAromaticRings = 0;
        RingSetManipulator.markAromaticRings(ringset);
        for (int i = 0; i < ringset.getAtomContainerCount(); i++) {
            if (ringset.getAtomContainer(i).getFlag(CDKConstants.ISAROMATIC)) numberOfAromaticRings++;
        }
        Assertions.assertEquals(1, numberOfAromaticRings);
    }

    @Test
    void testPyridine() throws Exception {
        IAtomContainer mol = new AtomContainer();
        mol.addAtom(new Atom("N"));
        mol.addAtom(new Atom("C"));
        mol.addBond(0, 1, IBond.Order.SINGLE);
        mol.addAtom(new Atom("C"));
        mol.addBond(1, 2, IBond.Order.DOUBLE);
        mol.addAtom(new Atom("C"));
        mol.addBond(2, 3, IBond.Order.SINGLE);
        mol.addAtom(new Atom("C"));
        mol.addBond(3, 4, IBond.Order.DOUBLE);
        mol.addAtom(new Atom("C"));
        mol.addBond(4, 5, IBond.Order.SINGLE);
        mol.addBond(0, 5, IBond.Order.DOUBLE);
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol), "Expected the molecule to be aromatic.");

        for (IAtom iAtom : mol.atoms()) {
            Assertions.assertTrue(iAtom.getFlag(CDKConstants.ISAROMATIC));
        }

        IRingSet ringset = (new SSSRFinder(mol)).findSSSR();
        int numberOfAromaticRings = 0;
        RingSetManipulator.markAromaticRings(ringset);
        for (int i = 0; i < ringset.getAtomContainerCount(); i++) {
            if (ringset.getAtomContainer(i).getFlag(CDKConstants.ISAROMATIC)) numberOfAromaticRings++;
        }
        Assertions.assertEquals(1, numberOfAromaticRings);
    }

    @Test
    void testCyclopentadienyl() throws Exception {
        IAtomContainer mol = new AtomContainer();
        mol.addAtom(new Atom("C"));
        mol.getAtom(0).setFormalCharge(-1);
        for (int i = 1; i < 5; i++) {
            mol.addAtom(new Atom("C"));
            mol.getAtom(i).setHybridization(IAtomType.Hybridization.SP2);
            mol.addBond(i - 1, i, IBond.Order.SINGLE);
        }
        mol.addBond(0, 4, IBond.Order.SINGLE);
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol), "Expected the molecule to be aromatic.");

        for (IAtom iAtom : mol.atoms()) {
            Assertions.assertTrue(iAtom.getFlag(CDKConstants.ISAROMATIC));
        }
    }

    @Test
    void testPyridineOxide() throws Exception {
        IAtomContainer molecule = TestMoleculeFactory.makePyridineOxide();
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(molecule));
    }

    @Test
    void testPyridineOxide_SP2() throws Exception {
        IAtomContainer molecule = TestMoleculeFactory.makePyridineOxide();
        for (IBond iBond : molecule.bonds()) iBond.setOrder(Order.SINGLE);
        for (int i = 0; i < 6; i++) {
            molecule.getAtom(i).setHybridization(IAtomType.Hybridization.SP2);
        }
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(molecule));
    }

    @Test
    void testFuran() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer mol = sp.parseSmiles("c1cocc1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol), "Molecule is not detected aromatic");

        IRingSet ringset = (new SSSRFinder(mol)).findSSSR();
        int numberOfAromaticRings = 0;
        RingSetManipulator.markAromaticRings(ringset);
        for (int i = 0; i < ringset.getAtomContainerCount(); i++) {
            if (ringset.getAtomContainer(i).getFlag(CDKConstants.ISAROMATIC)) numberOfAromaticRings++;
        }
        Assertions.assertEquals(1, numberOfAromaticRings);
    }

    /**
     * @cdk.bug 1294
     */
    @Test
    void testBug1294() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer mol = sp.parseSmiles("C2=C1C=CC=CC1=CN2");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol), "Molecule is not detected aromatic");
        for (IAtom atom : mol.atoms())
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC));
    }

    /**
     * @cdk.bug 1294
     */
    @Test
    void testBug1294_2() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer mol = sp.parseSmiles("c2c1ccccc1c[nH]2");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol), "Molecule is not detected aromatic");
        for (IAtom atom : mol.atoms())
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC));
    }

    /**
     * A unit test for JUnit The special difficulty with Azulene is that only the
     * outermost larger 10-ring is aromatic according to Hueckel rule.
     */
    @Test
    void testAzulene() throws Exception {
        boolean[] testResults = {true, true, true, true, true, true, true, true, true, true};
        IAtomContainer molecule = TestMoleculeFactory.makeAzulene();
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(molecule), "Expected the molecule to be aromatic.");
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC), "Atom " + f + " is not correctly marked");
        }
    }

    /**
     * A unit test for JUnit. The N has to be counted correctly.
     */
    @Test
    void testIndole() throws Exception {
        IAtomContainer molecule = TestMoleculeFactory.makeIndole();
        boolean[] testResults = {true, true, true, true, true, true, true, true, true};
        //boolean isAromatic = false;
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(molecule), "Expected the molecule to be aromatic.");
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC), "Atom " + f + " is not correctly marked");
        }
    }

    /**
     * A unit test for JUnit. The N has to be counted correctly.
     */
    @Test
    void testPyrrole() throws Exception {
        IAtomContainer molecule = TestMoleculeFactory.makePyrrole();
        boolean[] testResults = {true, true, true, true, true};
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(molecule), "Expected the molecule to be aromatic.");
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC), "Atom " + f + " is not correctly marked");
        }
    }

    /**
     * A unit test for JUnit
     */
    @Test
    void testThiazole() throws Exception {
        IAtomContainer molecule = TestMoleculeFactory.makeThiazole();
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(molecule), "Molecule is not detected as aromatic");

        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertTrue(molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC), "Atom " + f + " is not correctly marked");
        }
    }

    /**
     * A unit test for JUnit
     */
    @Test
    void testTetraDehydroDecaline() throws Exception {
        boolean isAromatic;
        //boolean testResults[] = {true, false, false};
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer mol = sp.parseSmiles("C1CCCc2c1cccc2");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol), "Expected the molecule to be aromatic.");
        IRingSet rs = (new AllRingsFinder()).findAllRings(mol);
        RingSetManipulator.markAromaticRings(rs);
        IRing r;
        int aromacount = 0;
        for (IAtomContainer container : rs.atomContainers()) {
            r = (IRing) container;
            isAromatic = r.getFlag(CDKConstants.ISAROMATIC);

            if (isAromatic) aromacount++;
        }
        Assertions.assertEquals(1, aromacount);
    }

    /**
     * This is a bug reported for JCP.
     *
     * @cdk.bug 956924
     */
    @Test
    void testSFBug956924() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer mol = sp.parseSmiles("[cH+]1cccccc1"); // tropylium cation
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertEquals(IAtomType.Hybridization.PLANAR3, mol.getAtom(0).getHybridization());
        for (int f = 1; f < mol.getAtomCount(); f++) {
            Assertions.assertEquals(IAtomType.Hybridization.SP2, mol.getAtom(f).getHybridization());
        }
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        Assertions.assertEquals(7, mol.getAtomCount());
        for (int f = 0; f < mol.getAtomCount(); f++) {
            Assertions.assertNotNull(mol.getAtom(f));
            Assertions.assertTrue(mol.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }
    }

    /**
     * This is a bug reported for JCP.
     *
     * @cdk.bug 956923
     */
    @Test
    void testSFBug956923() throws Exception {
        boolean[] testResults = {false, false, false, false, false, false, false, false};
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer mol = sp.parseSmiles("O=c1cccccc1"); // tropone
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertFalse(Aromaticity.cdkLegacy().apply(mol));
        Assertions.assertEquals(testResults.length, mol.getAtomCount());
        for (int f = 0; f < mol.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], mol.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }
    }

    @Test
    void testNoxide() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("C=1C=CC(=CC1)CNC2=CC=C(C=C2N(=O)=O)S(=O)(=O)C(Cl)(Cl)Br");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
    }

    /**
     * A unit test for JUnit
     */
    @Test
    void testPorphyrine() throws Exception {
        boolean isAromatic;
        boolean[] testResults = {false, false, false, false, false, true, true, true, true, true, false, true, true,
                true, false, true, true, false, false, true, true, false, false, false, true, true, false, false,
                false, true, true, false, false, false, false, true, true, true, true, false, false, false};

        String filename = "porphyrin.mol";
        InputStream ins = this.getClass().getResourceAsStream(filename);
        MDLV2000Reader reader = new MDLV2000Reader(ins);
        IAtomContainer molecule = reader.read(DefaultChemObjectBuilder.getInstance().newInstance(
                IAtomContainer.class));
        reader.close();

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        isAromatic = Aromaticity.cdkLegacy().apply(molecule);
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }
        Assertions.assertTrue(isAromatic);
    }

    /**
     * A unit test for JUnit
     *
     * @cdk.bug 698152
     */
    @Test
    void testBug698152() throws Exception {
        //boolean isAromatic = false;
        boolean[] testResults = {true, true, true, true, true, true, false, false, false, false, false, false, false,
                false, false, false, false, false};

        String filename = "bug698152.mol";
        InputStream ins = this.getClass().getResourceAsStream(filename);
        MDLV2000Reader reader = new MDLV2000Reader(ins);
        IAtomContainer molecule = reader.read(DefaultChemObjectBuilder.getInstance().newInstance(
                IAtomContainer.class));
        reader.close();

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Aromaticity.cdkLegacy().apply(molecule);
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }
    }

    /**
     * A test for the fix of bug #716259, where a quinone ring
     * was falsely detected as aromatic.
     *
     * @cdk.bug 716259
     */
    @Test
    void testBug716259() throws Exception {
        IAtomContainer molecule;
        //boolean isAromatic = false;
        boolean[] testResults = {true, true, true, true, true, true, true, true, true, true, false, false, false,
                false, false, false, false, false, false};

        String filename = "bug716259.mol";
        InputStream ins = this.getClass().getResourceAsStream(filename);
        MDLV2000Reader reader = new MDLV2000Reader(ins);
        molecule = reader.read(DefaultChemObjectBuilder.getInstance()
                                                       .newInstance(IAtomContainer.class));
        reader.close();

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Aromaticity.cdkLegacy().apply(molecule);
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }

    }

    /**
     * A unit test for JUnit
     */
    @Test
    void testQuinone() throws Exception {
        IAtomContainer molecule = TestMoleculeFactory.makeQuinone();
        boolean[] testResults = {false, false, false, false, false, false, false, false};

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Aromaticity.cdkLegacy().apply(molecule);
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertEquals(testResults[f], molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }

    }

    /**
     * @cdk.bug 1328739
     */
    @Test
    void testBug1328739() throws Exception {
        String filename = "bug1328739.mol";
        InputStream ins = this.getClass().getResourceAsStream(filename);
        MDLV2000Reader reader = new MDLV2000Reader(ins);
        IAtomContainer molecule = reader.read(DefaultChemObjectBuilder.getInstance().newInstance(
                IAtomContainer.class));
        reader.close();

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Aromaticity.cdkLegacy().apply(molecule);

        Assertions.assertEquals(15, molecule.getBondCount());
        Assertions.assertTrue(molecule.getBond(0).getFlag(CDKConstants.ISAROMATIC));
        Assertions.assertTrue(molecule.getBond(1).getFlag(CDKConstants.ISAROMATIC));
        Assertions.assertTrue(molecule.getBond(2).getFlag(CDKConstants.ISAROMATIC));
        Assertions.assertTrue(molecule.getBond(3).getFlag(CDKConstants.ISAROMATIC));
        Assertions.assertTrue(molecule.getBond(4).getFlag(CDKConstants.ISAROMATIC));
        Assertions.assertTrue(molecule.getBond(6).getFlag(CDKConstants.ISAROMATIC));
    }

    /**
     * A unit test for JUnit
     */
    @Test
    void testBenzene() throws Exception {
        IAtomContainer molecule = TestMoleculeFactory.makeBenzene();
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);
        Aromaticity.cdkLegacy().apply(molecule);
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertTrue(molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }
    }

    @Test
    void testCyclobutadiene() throws Exception {
        // anti-aromatic
        IAtomContainer molecule = TestMoleculeFactory.makeCyclobutadiene();
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(molecule);

        Assertions.assertFalse(Aromaticity.cdkLegacy().apply(molecule));
        for (int f = 0; f < molecule.getAtomCount(); f++) {
            Assertions.assertFalse(molecule.getAtom(f).getFlag(CDKConstants.ISAROMATIC));
        }
    }

    private IAtomContainer makeAromaticMolecule() {
        IAtomContainer mol = DefaultChemObjectBuilder.getInstance().newInstance(IAtomContainer.class);
        IAtom a1 = mol.getBuilder().newInstance(IAtom.class, "C");
        a1.setPoint2d(new Point2d(329.99999999999994, 971.0));
        mol.addAtom(a1);
        IAtom a2 = mol.getBuilder().newInstance(IAtom.class, "C");
        a2.setPoint2d(new Point2d(298.8230854637602, 989.0));
        mol.addAtom(a2);
        IAtom a3 = mol.getBuilder().newInstance(IAtom.class, "C");
        a3.setPoint2d(new Point2d(298.8230854637602, 1025.0));
        mol.addAtom(a3);
        IAtom a4 = mol.getBuilder().newInstance(IAtom.class, "C");
        a4.setPoint2d(new Point2d(330.0, 1043.0));
        mol.addAtom(a4);
        IAtom a5 = mol.getBuilder().newInstance(IAtom.class, "C");
        a5.setPoint2d(new Point2d(361.1769145362398, 1025.0));
        mol.addAtom(a5);
        IAtom a6 = mol.getBuilder().newInstance(IAtom.class, "C");
        a6.setPoint2d(new Point2d(361.1769145362398, 989.0));
        mol.addAtom(a6);
        IAtom a7 = mol.getBuilder().newInstance(IAtom.class, "C");
        a7.setPoint2d(new Point2d(392.3538290724796, 971.0));
        mol.addAtom(a7);
        IAtom a8 = mol.getBuilder().newInstance(IAtom.class, "C");
        a8.setPoint2d(new Point2d(423.5307436087194, 989.0));
        mol.addAtom(a8);
        IAtom a9 = mol.getBuilder().newInstance(IAtom.class, "C");
        a9.setPoint2d(new Point2d(423.5307436087194, 1025.0));
        mol.addAtom(a9);
        IAtom a10 = mol.getBuilder().newInstance(IAtom.class, "C");
        a10.setPoint2d(new Point2d(392.3538290724796, 1043.0));
        mol.addAtom(a10);
        IBond b1 = mol.getBuilder().newInstance(IBond.class, a1, a2, IBond.Order.DOUBLE);
        mol.addBond(b1);
        IBond b2 = mol.getBuilder().newInstance(IBond.class, a2, a3, IBond.Order.SINGLE);
        mol.addBond(b2);
        IBond b3 = mol.getBuilder().newInstance(IBond.class, a3, a4, IBond.Order.DOUBLE);
        mol.addBond(b3);
        IBond b4 = mol.getBuilder().newInstance(IBond.class, a4, a5, IBond.Order.SINGLE);
        mol.addBond(b4);
        IBond b5 = mol.getBuilder().newInstance(IBond.class, a5, a6, IBond.Order.DOUBLE);
        mol.addBond(b5);
        IBond b6 = mol.getBuilder().newInstance(IBond.class, a6, a1, IBond.Order.SINGLE);
        mol.addBond(b6);
        IBond b7 = mol.getBuilder().newInstance(IBond.class, a6, a7, IBond.Order.SINGLE);
        mol.addBond(b7);
        IBond b8 = mol.getBuilder().newInstance(IBond.class, a7, a8, IBond.Order.SINGLE);
        mol.addBond(b8);
        IBond b9 = mol.getBuilder().newInstance(IBond.class, a8, a9, IBond.Order.SINGLE);
        mol.addBond(b9);
        IBond b10 = mol.getBuilder().newInstance(IBond.class, a9, a10, IBond.Order.SINGLE);
        mol.addBond(b10);
        IBond b11 = mol.getBuilder().newInstance(IBond.class, a10, a5, IBond.Order.SINGLE);
        mol.addBond(b11);
        return mol;
    }

    /**
     * @cdk.bug 1957684
     */
    @Test
    void test3Amino2MethylPyridine() throws Exception {

        IAtomContainer mol = new AtomContainer();
        IAtom a1 = mol.getBuilder().newInstance(IAtom.class, "N");
        a1.setPoint2d(new Point2d(3.7321, 1.345));
        mol.addAtom(a1);
        IAtom a2 = mol.getBuilder().newInstance(IAtom.class, "N");
        a2.setPoint2d(new Point2d(4.5981, -1.155));
        mol.addAtom(a2);
        IAtom a3 = mol.getBuilder().newInstance(IAtom.class, "C");
        a3.setPoint2d(new Point2d(2.866, -0.155));
        mol.addAtom(a3);
        IAtom a4 = mol.getBuilder().newInstance(IAtom.class, "C");
        a4.setPoint2d(new Point2d(3.7321, 0.345));
        mol.addAtom(a4);
        IAtom a5 = mol.getBuilder().newInstance(IAtom.class, "C");
        a5.setPoint2d(new Point2d(2.866, -1.155));
        mol.addAtom(a5);
        IAtom a6 = mol.getBuilder().newInstance(IAtom.class, "C");
        a6.setPoint2d(new Point2d(2.0, 0.345));
        mol.addAtom(a6);
        IAtom a7 = mol.getBuilder().newInstance(IAtom.class, "C");
        a7.setPoint2d(new Point2d(4.5981, -0.155));
        mol.addAtom(a7);
        IAtom a8 = mol.getBuilder().newInstance(IAtom.class, "C");
        a8.setPoint2d(new Point2d(3.7321, -1.655));
        mol.addAtom(a8);
        IAtom a9 = mol.getBuilder().newInstance(IAtom.class, "H");
        a9.setPoint2d(new Point2d(2.3291, -1.465));
        mol.addAtom(a9);
        IAtom a10 = mol.getBuilder().newInstance(IAtom.class, "H");
        a10.setPoint2d(new Point2d(2.31, 0.8819));
        mol.addAtom(a10);
        IAtom a11 = mol.getBuilder().newInstance(IAtom.class, "H");
        a11.setPoint2d(new Point2d(1.4631, 0.655));
        mol.addAtom(a11);
        IAtom a12 = mol.getBuilder().newInstance(IAtom.class, "H");
        a12.setPoint2d(new Point2d(1.69, -0.1919));
        mol.addAtom(a12);
        IAtom a13 = mol.getBuilder().newInstance(IAtom.class, "H");
        a13.setPoint2d(new Point2d(5.135, 0.155));
        mol.addAtom(a13);
        IAtom a14 = mol.getBuilder().newInstance(IAtom.class, "H");
        a14.setPoint2d(new Point2d(3.7321, -2.275));
        mol.addAtom(a14);
        IAtom a15 = mol.getBuilder().newInstance(IAtom.class, "H");
        a15.setPoint2d(new Point2d(4.269, 1.655));
        mol.addAtom(a15);
        IAtom a16 = mol.getBuilder().newInstance(IAtom.class, "H");
        a16.setPoint2d(new Point2d(3.1951, 1.655));
        mol.addAtom(a16);
        IBond b1 = mol.getBuilder().newInstance(IBond.class, a1, a4, IBond.Order.SINGLE);
        mol.addBond(b1);
        IBond b2 = mol.getBuilder().newInstance(IBond.class, a1, a15, IBond.Order.SINGLE);
        mol.addBond(b2);
        IBond b3 = mol.getBuilder().newInstance(IBond.class, a1, a16, IBond.Order.SINGLE);
        mol.addBond(b3);
        IBond b4 = mol.getBuilder().newInstance(IBond.class, a2, a7, IBond.Order.DOUBLE);
        mol.addBond(b4);
        IBond b5 = mol.getBuilder().newInstance(IBond.class, a2, a8, IBond.Order.SINGLE);
        mol.addBond(b5);
        IBond b6 = mol.getBuilder().newInstance(IBond.class, a3, a4, IBond.Order.DOUBLE);
        mol.addBond(b6);
        IBond b7 = mol.getBuilder().newInstance(IBond.class, a3, a5, IBond.Order.SINGLE);
        mol.addBond(b7);
        IBond b8 = mol.getBuilder().newInstance(IBond.class, a3, a6, IBond.Order.SINGLE);
        mol.addBond(b8);
        IBond b9 = mol.getBuilder().newInstance(IBond.class, a4, a7, IBond.Order.SINGLE);
        mol.addBond(b9);
        IBond b10 = mol.getBuilder().newInstance(IBond.class, a5, a8, IBond.Order.DOUBLE);
        mol.addBond(b10);
        IBond b11 = mol.getBuilder().newInstance(IBond.class, a5, a9, IBond.Order.SINGLE);
        mol.addBond(b11);
        IBond b12 = mol.getBuilder().newInstance(IBond.class, a6, a10, IBond.Order.SINGLE);
        mol.addBond(b12);
        IBond b13 = mol.getBuilder().newInstance(IBond.class, a6, a11, IBond.Order.SINGLE);
        mol.addBond(b13);
        IBond b14 = mol.getBuilder().newInstance(IBond.class, a6, a12, IBond.Order.SINGLE);
        mol.addBond(b14);
        IBond b15 = mol.getBuilder().newInstance(IBond.class, a7, a13, IBond.Order.SINGLE);
        mol.addBond(b15);
        IBond b16 = mol.getBuilder().newInstance(IBond.class, a8, a14, IBond.Order.SINGLE);
        mol.addBond(b16);

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        boolean isAromatic = Aromaticity.cdkLegacy().apply(mol);
        Assertions.assertTrue(isAromatic);

        Iterator<IAtom> atoms = mol.atoms().iterator();
        int nCarom = 0;
        int nCalip = 0;
        int nNarom = 0;
        int nNaliph = 0;
        while (atoms.hasNext()) {
            IAtom atom = atoms.next();
            if (atom.getAtomicNumber() == IElement.C) {
                if (atom.getFlag(CDKConstants.ISAROMATIC))
                    nCarom++;
                else
                    nCalip++;
            } else if (atom.getAtomicNumber() == IElement.N) {
                if (atom.getFlag(CDKConstants.ISAROMATIC))
                    nNarom++;
                else
                    nNaliph++;
            }

        }
        Assertions.assertEquals(5, nCarom);
        Assertions.assertEquals(1, nCalip);
        Assertions.assertEquals(1, nNarom);
        Assertions.assertEquals(1, nNaliph);
    }

    /**
     * @cdk.bug 2185255
     * @throws CDKException
     */
    @Test
    void testPolyCyclicSystem() throws Exception {
        CDKHydrogenAdder adder = CDKHydrogenAdder.getInstance(DefaultChemObjectBuilder.getInstance());
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());

        IAtomContainer kekuleForm = sp.parseSmiles("C1=CC2=CC3=CC4=C(C=CC=C4)C=C3C=C2C=C1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(kekuleForm);
        adder.addImplicitHydrogens(kekuleForm);
        AtomContainerManipulator.convertImplicitToExplicitHydrogens(kekuleForm);

        IAtomContainer aromaticForm = sp.parseSmiles("c1ccc2cc3cc4ccccc4cc3cc2c1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(aromaticForm);
        adder.addImplicitHydrogens(aromaticForm);
        AtomContainerManipulator.convertImplicitToExplicitHydrogens(aromaticForm);

        boolean isAromatic = Aromaticity.cdkLegacy().apply(kekuleForm);
        Assertions.assertTrue(isAromatic);

        isAromatic = Aromaticity.cdkLegacy().apply(aromaticForm);
        Assertions.assertTrue(isAromatic);

        // double bond locations may alter. So, we can expect things like in a 'diff': "BondDiff{order:SINGLE/DOUBLE}"
        // but, we should not see aromaticity differences
        for (IAtom atom : aromaticForm.atoms()) {
            if (atom.getAtomicNumber() == IElement.C) {
                Assertions.assertEquals("C.sp2", atom.getAtomTypeName());
                Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC));
            }
        }
        for (IAtom atom : kekuleForm.atoms()) {
            if (atom.getAtomicNumber() == IElement.C) {
                Assertions.assertEquals("C.sp2", atom.getAtomTypeName());
                Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC));
            }
        }
    }

    /**
     * @cdk.bug 1579235
     */
    @Test
    void testIndolizine() throws CDKException {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer aromaticForm = sp.parseSmiles("c2cc1cccn1cc2");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(aromaticForm);
        boolean isAromatic = Aromaticity.cdkLegacy().apply(aromaticForm);
        Assertions.assertTrue(isAromatic);

        // all atoms are supposed to be aromatic
        for (IAtom atom : aromaticForm.atoms()) {
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC), atom.toString() + " should be aromatic");
        }
    }

    /**
     * @cdk.bug 2976054
     */
    @Test
    void testAnotherNitrogen_SP2() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("c1cnc2s[cH][cH]n12");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (IAtom atom : mol.atoms())
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC));
    }

    /**
     * Tests to check for aromaticity issues in SMARTS matches
     *
     * @throws Exception
     */
    @Test
    void testAromaticNOxide() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("O=n1ccccc1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (IAtom atom : mol.atoms()) {
            if (atom.getAtomicNumber() == IElement.O) continue;
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC), atom.getSymbol() + " was not aromatic but should have been");
        }
    }

    /**
     * Tests to check for aromaticity issues in SMARTS matches
     *
     * @throws Exception
     */
    @Test
    void testAromaticNOxideCharged() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("[O-][n+]1ccccc1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (IAtom atom : mol.atoms()) {
            if (atom.getAtomicNumber() == IElement.O) continue;
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC), atom.getSymbol() + " was not aromatic but should have been");
        }
    }

    @Test
    void testSMILES() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("C=1N=CNC=1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (IAtom atom : mol.atoms())
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC), "Atom is expected to be aromatic: " + atom);
    }

    @Test
    void testSMILES2() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("OCN1C=CN=C1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (int i = 2; i <= 6; i++) {
            IAtom atom = mol.getAtom(i);
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC), "Atom is expected to be aromatic: " + atom);
        }
    }

    @Test
    void testSMILES3() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("OC(=O)N1C=CN=C1");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (int i = 3; i <= 7; i++) {
            IAtom atom = mol.getAtom(i);
            Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC), "Atom is expected to be aromatic: " + atom);
        }
    }

    /**
     * @cdk.bug 3001616
     */
    @Test
    void test3001616() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("OC(=O)N1C=NC2=CC=CC=C12");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (IAtom atom : mol.atoms()) {
            if (atom.getAtomicNumber() == IElement.N) {
                Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC));
                List<IBond> conbonds = mol.getConnectedBondsList(atom);
                if (conbonds.size() == 2) {
                    Assertions.assertTrue(conbonds.get(0).getFlag(CDKConstants.ISAROMATIC));
                    Assertions.assertTrue(conbonds.get(1).getFlag(CDKConstants.ISAROMATIC));
                } else if (conbonds.size() == 3) {
                    for (IBond bond : conbonds) {
                        if (bond.getOrder().equals(IBond.Order.SINGLE)) continue;
                        Assertions.assertTrue(bond.getFlag(CDKConstants.ISAROMATIC));
                    }
                }
            }
        }
        SpanningTree st = new SpanningTree(mol);
        IRingSet ringSet = st.getAllRings();
        for (IAtomContainer ring : ringSet.atomContainers()) {
            for (IBond bond : ring.bonds()) {
                Assertions.assertTrue(bond.getFlag(CDKConstants.ISAROMATIC));
            }
        }
    }

    /**
     * @cdk.bug 2853035
     */
    @Test
    void testBug2853035() throws Exception {
        SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance());
        IAtomContainer mol = sp.parseSmiles("C(=O)c1cnn2ccccc12");
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol));
        for (IAtom atom : mol.atoms()) {
            if (atom.getAtomicNumber() == IElement.N) {
                Assertions.assertTrue(atom.getFlag(CDKConstants.ISAROMATIC));
                List<IBond> conbonds = mol.getConnectedBondsList(atom);
                for (IBond bond : conbonds) {
                    if (bond.getOrder().equals(IBond.Order.SINGLE)) continue;
                    Assertions.assertTrue(bond.getFlag(CDKConstants.ISAROMATIC));
                }
            }
        }
        SpanningTree st = new SpanningTree(mol);
        IRingSet ringSet = st.getAllRings();
        for (IAtomContainer ring : ringSet.atomContainers()) {
            for (IBond bond : ring.bonds()) {
                Assertions.assertTrue(bond.getFlag(CDKConstants.ISAROMATIC));
            }
        }
    }

    /**
     * Due to using iterators some Sp3 atoms in the oxaspirodeadiene example
     * would not be removed and the molcule would incorrectly be found to be
     * aromatic.
     *
     * @cdk.bug 1313
     */
    @Test
    void ensureAtomsRemoved() throws Exception {
        IAtomContainer mol = oxaspirodeadiene();
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol);
        Assertions.assertFalse(Aromaticity.cdkLegacy().apply(mol));
    }

    /**
     * Ensures atoms/bonds are marked as cyclic before being removed. Otherwise
     * this means changing an atom outside of an aromatic system can alter
     * perception (not good). It results in finding an aromatic ring in
     * 'OC1=C2C=CC=CC2=CNC1=C' but not in 'OS1(=O)=C2C=CC=CC2=CNC1=C'. Whether
     * a pi bond is exocyclic should be invariant to the connected atom.
     *
     * @cdk.bug 1313
     */
    @Test
    void markCyclicBefore() throws Exception {
        SmilesParser sp = new SmilesParser(SilentChemObjectBuilder.getInstance());

        IAtomContainer mol1 = sp.parseSmiles("OC1=C2C=CC=CC2=CNC1=C");
        IAtomContainer mol2 = sp.parseSmiles("OS1(=O)=C2C=CC=CC2=CNC1=C");

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol1);
        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(mol2);

        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol1));
        Assertions.assertTrue(Aromaticity.cdkLegacy().apply(mol2));
    }

    /**
     * 8-oxaspiro[4.5]deca-6,9-diene
     * C1CCC2(C1)C=COC=C2
     * @cdk.inchi InChI=1/C9H12O/c1-2-4-9(3-1)5-7-10-8-6-9/h5-8H,1-4H2
     */
    static IAtomContainer oxaspirodeadiene() throws Exception {
        IChemObjectBuilder builder = DefaultChemObjectBuilder.getInstance();
        IAtomContainer mol = builder.newInstance(IAtomContainer.class);
        IAtom a1 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a1);
        IAtom a2 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a2);
        IAtom a3 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a3);
        IAtom a4 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a4);
        IAtom a5 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a5);
        IAtom a6 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a6);
        IAtom a7 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a7);
        IAtom a8 = builder.newInstance(IAtom.class, "O");
        mol.addAtom(a8);
        IAtom a9 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a9);
        IAtom a10 = builder.newInstance(IAtom.class, "C");
        mol.addAtom(a10);
        IBond b1 = builder.newInstance(IBond.class, a1, a2, IBond.Order.SINGLE);
        mol.addBond(b1);
        IBond b2 = builder.newInstance(IBond.class, a2, a3, IBond.Order.SINGLE);
        mol.addBond(b2);
        IBond b3 = builder.newInstance(IBond.class, a3, a4, IBond.Order.SINGLE);
        mol.addBond(b3);
        IBond b4 = builder.newInstance(IBond.class, a4, a5, IBond.Order.SINGLE);
        mol.addBond(b4);
        IBond b5 = builder.newInstance(IBond.class, a1, a5, IBond.Order.SINGLE);
        mol.addBond(b5);
        IBond b6 = builder.newInstance(IBond.class, a4, a6, IBond.Order.SINGLE);
        mol.addBond(b6);
        IBond b7 = builder.newInstance(IBond.class, a6, a7, IBond.Order.DOUBLE);
        mol.addBond(b7);
        IBond b8 = builder.newInstance(IBond.class, a7, a8, IBond.Order.SINGLE);
        mol.addBond(b8);
        IBond b9 = builder.newInstance(IBond.class, a8, a9, IBond.Order.SINGLE);
        mol.addBond(b9);
        IBond b10 = builder.newInstance(IBond.class, a9, a10, IBond.Order.DOUBLE);
        mol.addBond(b10);
        IBond b11 = builder.newInstance(IBond.class, a4, a10, IBond.Order.SINGLE);
        mol.addBond(b11);
        return mol;
    }

}
