/*
 * 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.jgrapht.graph.DefaultDirectedGraph;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.qsar.AtomValenceTool;
import org.openscience.cdk.silent.SilentChemObjectBuilder;
import org.openscience.cdk.tools.CDKHydrogenAdder;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
import org.openscience.cdk.tools.periodictable.PeriodicTable;

/**
 * Clones the internal molecular representation of a MoleculeBuilder object
 * and adds manipulators and calculators.
 *
 * @author Nicola Chirico
 */
public final class Molecule
{
    /**
     * Constructs a molecular representation.
     *
     * @param mol_build MolecularBuilder object.
     *
     * @throws java.lang.CloneNotSupportedException
     */
    public Molecule(AbstractMoleculeBuilder mol_build) throws CloneNotSupportedException
    {
        this.iac = mol_build.clone_atom_container();
        this.graph = mol_build.get_graph();
    }

    /**
     * Gets the molecular CDK atom container.
     *
     * @return CDK atom container.
     */
    public IAtomContainer get_atom_container()
    {
        return iac;
    }

    /**
     * Gets the directed graph of the molecular representation.
     *
     * @return directed graph.
     */
    public DefaultDirectedGraph<Integer, CustomEdge> get_graph()
    {
        return graph;
    }

    /**
     * Converts the hydrogens of the internal CDK atom container representation
     * from implicit to explicit.
     *
     * @throws org.openscience.cdk.exception.CDKException
     */
    public void convert_impl_to_expl_hydrogens() throws CDKException
    {
        add_implicit_hydrogens();

        AtomContainerManipulator
                .convertImplicitToExplicitHydrogens(iac);

        set_formal_neighbour_count();
    }

    /**
     * Removes the hydrogens of the internal CDK atom container representation.
     */
    public void remove_hydrogens()
    {
        iac = AtomContainerManipulator.removeHydrogens(iac);
    }

    /**
     * Gets atom Kier-Hall intrinsic state value and electrons valence counts.
     *
     * @param atom CDK atom
     *
     * @return
     */
    public final IntrState calc_intrinsic_state_kier_hall(IAtom atom)
    {
        if (atom.getAtomicNumber() == 1) {
            throw new IllegalArgumentException("Chemical structure is not H"
                    + " depleted");
        }

        int valence = AtomValenceTool.getValence(atom);

        var delta_pair = get_delta_delta_v(atom, valence);

        double delta = delta_pair.delta();

        double delta_v = delta_pair.delta_v();

        double quant_num = (double) PeriodicTable.getPeriod(atom.getSymbol());

        double i_state = (((4.0 / (quant_num * quant_num)) * delta_v) + 1.0)
                / delta;

        return new IntrState(delta, delta_v, i_state);
    }

    /**
     * Gets atom intrinsic state based on Kier-Hall relative electronegativity
     * value, as defined in Molconn-Z, and electrons valence counts.
     *
     * @param atom CDK atom
     *
     * @return
     */
    public final IntrState calc_intrinsic_state_kier_hall_elneg(IAtom atom)
    {
        int atomic_number = atom.getAtomicNumber();

        if (atomic_number == 1) {
            throw new IllegalArgumentException("Chemical structure is not H"
                    + " depleted");
        }

        int valence = AtomValenceTool.getValence(atom);

        var delta_pair = get_delta_delta_v(atom, valence);

        double delta = delta_pair.delta();

        double delta_v = delta_pair.delta_v()
                / (double) (atomic_number - valence - 1);

        double quant_num = (double) PeriodicTable.getPeriod(atom.getSymbol());

        double i_state = (delta_v - delta) / (quant_num * quant_num);

        return new IntrState(delta, delta_v, i_state);
    }

    /**
     * Gets atom intrinsic state value calculated according to the Molconn-Z
     * valence electrons count definition, and gets electrons valence counts.
     *
     * @param atom CDK atom
     *
     * @return
     */
    public final IntrState calc_intrinsic_state_molconn(IAtom atom)
    {
        int atomic_number = atom.getAtomicNumber();

        if (atomic_number == 1) {
            throw new IllegalArgumentException("Chemical structure is not H"
                    + " depleted");
        }

        int valence = AtomValenceTool.getValence(atom);

        var delta_pair = get_delta_delta_v(atom, valence);

        double delta = delta_pair.delta();

        double delta_v = delta_pair.delta_v()
                / (double) (atomic_number - valence - 1);

        double quant_num = (double) PeriodicTable.getPeriod(atom.getSymbol());

        double i_state = (((4.0 / (quant_num * quant_num)) * delta_v) + 1.0)
                / delta;

        return new IntrState(delta, delta_v, i_state);
    }

    /**
     * Stores the atomic intrinsic state, sigma and valence electrons count.
     *
     * @param delta Sigma electrons count.
     * @param delta_v Valence electrons count.
     * @param i_state Intrinsic state.
     */
    public final record IntrState(double delta, double delta_v, double i_state)
    {
    }

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

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

    private Delta get_delta_delta_v(IAtom atom, double valence)
    {
        int h_count = AtomContainerManipulator.countHydrogens(iac, atom);

        return new Delta((double) (iac.getConnectedBondsCount(atom) - h_count),
                (double) (valence - h_count));
    }

    private record Delta(double delta, double delta_v)
    {
    }

    private IAtomContainer iac;

    private final DefaultDirectedGraph<Integer, CustomEdge> graph;
}
