/*
 * Copyright (c) 2026 University of Insubria <https://www.uninsubria.it>
 *
 * This library 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 library 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.
 * <https://fsf.org/>
 */

package qsardes.molecule;

import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtomType;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.interfaces.IChemObject;
import org.openscience.cdk.silent.SilentChemObjectBuilder;
import org.openscience.cdk.tools.AtomTypeTools;
import org.openscience.cdk.tools.CDKHydrogenAdder;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;

/**
 * This class builds a QSAR-ME Profiler compliant molecular representation.
 *
 * @author Nicola Chirico
 */
public final class MoleculeBuilderQsarME extends AbstractMoleculeBuilder
{
    /**
     * Constructs a molecular representation QSAR-ME Profiler compliant.
     *
     * @param smiles Molecular SMILES.
     *
     * @throws Exception
     */
    public MoleculeBuilderQsarME(String smiles) throws Exception
    {
        super(smiles);

        configure_atoms();
        standardize_molecule();
        build_graph();
    }
    /*
       Adapted from PaDEL-Descriptor version 2.21
       Author: Chun Wei Yap
       Class: PaDELStandardize
       Method: Standardize
       Availability: http://yapcwsoft.com/dd/padeldescriptor
    */
    private void standardize_molecule() throws Exception
    {
        adjust_nitrogen_formal_charge();

        perceive_atom_types();
        add_implicit_hydrogens();

        dearomatize();

        standardize_nitro_groups();

        assign_atom_types();
        add_implicit_hydrogens();

        convert_impl_to_expl_hydrogens();
        perceive_atom_types();

        remove_hydrogens();
        add_implicit_hydrogens();
    }

    private void assign_atom_types() throws Exception
    {
        new AtomTypeTools().assignAtomTypePropertiesToAtom(
                get_atom_container(), false);
    }

    private void remove_hydrogens()
    {
        set_atom_container(AtomContainerManipulator
                .removeHydrogens(get_atom_container()));
    }

    /*
       Adapted from PaDEL-Descriptor version 2.21
       Author: Chun Wei Yap
       Class: PaDELStandardize
       Method: CleanMolecule
       Availability: http://yapcwsoft.com/dd/padeldescriptor
    */
    private void adjust_nitrogen_formal_charge()
    {
        final var iac = get_atom_container();

        for (var atom : iac.atoms()) {
            if ((atom.getAtomicNumber() == 7)
                    && (iac.getConnectedBondsCount(atom) == 4)
                    && (atom.getFormalCharge() == 0)) {
                atom.setFormalCharge(1);
            }
        }
    }

    /*
       Adapted from PaDEL-Descriptor version 2.21
       Author: Chun Wei Yap
       Class: PaDELStandardize
       Method: PerceiveAtomTypes
       Availability: http://yapcwsoft.com/dd/padeldescriptor
    */
    private void perceive_atom_types() throws CDKException
    {
        final var iac = get_atom_container();

        AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(iac);

        boolean is_modified = false;

        do {
            is_modified = false;

            for (var bond : iac.bonds()) {
                if (bond.getFlag(IChemObject.AROMATIC)) {
                    for (var atom : bond.atoms()) {
                        if (!atom.getFlag(IChemObject.AROMATIC)) {
                            atom.setFlag(IChemObject.AROMATIC, true);
                            is_modified = true;
                        }
                    }
                }
            }

            for (var bond : iac.bonds()) {
                if (!bond.getFlag(IChemObject.AROMATIC)) {
                    boolean are_all_atoms_aromatic = true;

                    for (var atom : bond.atoms()) {
                        if (!atom.getFlag(IChemObject.AROMATIC)) {
                            are_all_atoms_aromatic = false;
                            break;
                        }
                    }

                    if (are_all_atoms_aromatic) {
                        if (!bond.getFlag(IChemObject.AROMATIC)) {
                            bond.setFlag(IChemObject.AROMATIC, true);
                            is_modified = true;
                        }
                    }
                }
            }
        } while (is_modified);

        /*
          chemodes addition: adjust for missing formal neighbour counts
        */
        set_formal_neighbour_count();
    }

    /*
       Adapted from PaDEL-Descriptor version 2.21
       Author: Chun Wei Yap
       Class: PaDELStandardize
       Method: Dearomatize
       Availability: http://yapcwsoft.com/dd/padeldescriptor
    */
    private void dearomatize() throws CDKException, Exception
    {
        remove_hydrogens();

        final var iac = get_atom_container();

        new Dearomatizer(iac).dearomatize();

        add_implicit_hydrogens();

        new AtomTypeTools().assignAtomTypePropertiesToAtom(iac, true);
    }

    /*
       Standardize nitro group to N+(=O)O-

       Adapted from PaDEL-Descriptor version 2.21
       Author: Chun Wei Yap
       Class: PaDELStandardize
       Method: StandardizeNitro2
       Availability: http://yapcwsoft.com/dd/padeldescriptor
    */
    private void standardize_nitro_groups()
    {
        final var iac = get_atom_container();

        for (var atom : iac.atoms()) {
            if (atom.getAtomicNumber() == 7) {
                var neighbours = iac.getConnectedAtomsList(atom);

                if (neighbours.size() != 3) {
                    continue;
                }

                int oxygens_count = 0;

                boolean is_OH = true;

                for (var neigh_atom : neighbours) {
                    if (neigh_atom.getAtomicNumber() == 8) {
                        oxygens_count++;

                        var neigh_list = iac.getConnectedAtomsList(neigh_atom);

                        for (var neigh_atom_1 : neigh_list) {
                            if (neigh_atom_1 != atom) {
                                if (neigh_atom_1.getAtomicNumber() != 1) {
                                    is_OH = false;
                                    break;
                                }
                            }
                        }
                    }
                }

                if ((oxygens_count != 2) || (!is_OH)) {
                    continue;
                }

                atom.setFlag(IChemObject.AROMATIC, false);
                atom.setFormalCharge(1);
                atom.setHybridization(IAtomType.Hybridization.SP2);
                atom.setImplicitHydrogenCount(0);
                atom.setAtomTypeName("N.plus.sp2");

                boolean is_O_minus = true;

                for (var neigh_atom : neighbours) {
                    if (neigh_atom.getAtomicNumber() == 8) {
                        neigh_atom.setImplicitHydrogenCount(0);

                        var neigh_list = iac.getConnectedAtomsList(neigh_atom);

                        for (var neigh_atom_1 : neigh_list) {
                            if (neigh_atom_1 != atom) {
                                iac.removeBond(neigh_atom, neigh_atom_1);
                                iac.removeAtom(neigh_atom_1);
                            }
                        }

                        var bond = iac.getBond(atom, neigh_atom);

                        if (is_O_minus) {
                            neigh_atom.setFlag(IChemObject.AROMATIC, false);
                            neigh_atom.setFormalCharge(-1);
                            neigh_atom.setHybridization(
                                    IAtomType.Hybridization.SP3);
                            neigh_atom.setAtomTypeName("O.minus");
                            bond.setOrder(IBond.Order.SINGLE);
                            bond.setFlag(IChemObject.AROMATIC, false);

                            is_O_minus = false;
                        } else {
                            neigh_atom.setFlag(IChemObject.AROMATIC, false);
                            neigh_atom.setFormalCharge(0);
                            neigh_atom.setHybridization(
                                    IAtomType.Hybridization.SP2);
                            neigh_atom.setAtomTypeName("O.sp2");
                            bond.setOrder(IBond.Order.DOUBLE);
                            bond.setFlag(IChemObject.AROMATIC, false);

                            is_O_minus = true;
                        }
                    } else if (neigh_atom.getFlag(IChemObject.AROMATIC)) {
                        iac.getBond(atom, neigh_atom)
                                .setFlag(IChemObject.AROMATIC, false);
                    }
                }
            }
        }
    }

    private void add_implicit_hydrogens() throws CDKException
    {
        CDKHydrogenAdder.getInstance(SilentChemObjectBuilder.getInstance())
                .addImplicitHydrogens(get_atom_container());
    }

    private void convert_impl_to_expl_hydrogens() throws CDKException
    {
        add_implicit_hydrogens();

        AtomContainerManipulator
                .convertImplicitToExplicitHydrogens(get_atom_container());

        set_formal_neighbour_count();
    }

    private void set_formal_neighbour_count()
    {
        final var iac = get_atom_container();

        for (var atom : iac.atoms()) {
            atom.setFormalNeighbourCount(
                    iac.getConnectedAtomsList(atom).size());
        }
    }
}
