/*
 * 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.descriptor;

import java.util.Arrays;
import java.util.HashMap;
import qsardes.molecule.Molecule;
import qsardes.molecule.AbstractMoleculeBuilder;
import java.util.List;
import java.util.Map;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;
import static org.openscience.cdk.interfaces.IBond.Order.DOUBLE;
import static org.openscience.cdk.interfaces.IBond.Order.QUADRUPLE;
import static org.openscience.cdk.interfaces.IBond.Order.SINGLE;
import static org.openscience.cdk.interfaces.IBond.Order.TRIPLE;

/**
 * Hydrogen bond acceptors count descriptors abstract class.
 *
 * @author Nicola Chirico
 */
public abstract class DescriptorHBondAcceptorsCount implements Descriptor
{
    protected DescriptorHBondAcceptorsCount()
    {
    }

    @Override
    public String toString()
    {
        return "Hydrogen bond acceptors count";
    }

    @Override
    public DescriptorType get_descriptor_type()
    {
        return DescriptorType.valueOf("DESCRIPTOR");
    }

    @Override
    public List<String> get_descriptor_names()
    {
        return Arrays.asList(
                "nHBAcc", "nHBAcc2", "nHBAcc3", "nHBAcc_Lipinski");
    }

    @Override
    public Map<String, String> get_descriptor_values(
            AbstractMoleculeBuilder mol_build)
    {
        prepare_descr_map();

        try {
            mol = new Molecule(mol_build);
            calculate_descr();
        } catch (Exception ex) {
            System.err.println(ex);
        }

        return descr_map;
    }

    private void prepare_descr_map()
    {
        final String NAN = String.valueOf(Double.NaN);

        get_descriptor_names().forEach(item -> descr_map.put(item, NAN));
    }

    private void calculate_descr()
    {
        var iac = mol.get_atom_container();

        int acc_1_count = 0;
        int acc_2_count = 0;
        int acc_3_count = 0;
        int lipin_count = 0;

        for (var atom : iac.atoms()) {
            if (atom.getSymbol().equals("N")) {
                if (atom.getFormalCharge() <= 0) {
                    var c_atoms = iac.getConnectedAtomsList(atom);

                    if (!is_conn_to_oxygen(c_atoms)) {
                        acc_1_count++;
                    }

                    int impl_h_count = 0;
                    int neigh_num = 0;

                    if (atom.isAromatic() && atom.isInRing()) {
                        impl_h_count = atom.getImplicitHydrogenCount();
                        neigh_num = c_atoms.size();
                    }

                    boolean is_arom_o = (!atom.isAromatic())
                            && is_arom_oxigen_bond(c_atoms);

                    int bond_order_sum = get_order_sum(atom, c_atoms);

                    if (!(is_arom_o || (impl_h_count > 0) || (neigh_num > 2)
                            || (bond_order_sum > 3))) {
                        acc_2_count++;
                    }

                    boolean is_amide_bond = is_amide_bond(iac, c_atoms);

                    if (!(is_arom_o || (impl_h_count > 0) || (neigh_num > 2)
                            || (bond_order_sum > 3) || is_amide_bond)) {
                        acc_3_count++;
                    }
                }

                lipin_count++;
            }

            if (atom.getSymbol().equals("O")) {
                if (atom.getFormalCharge() <= 0) {
                    var c_atoms = iac.getConnectedAtomsList(atom);

                    if (!(is_conn_to_nitrogen(c_atoms)
                            || is_arom_carbon_bond(c_atoms))) {
                        acc_1_count++;
                    }
                }

                acc_2_count++;
                acc_3_count++;
                lipin_count++;
            }

            if (atom.getSymbol().equals("F")) {
                acc_2_count++;
                acc_3_count++;
            }
        }

        descr_map.put("nHBAcc", String.valueOf(acc_1_count));
        descr_map.put("nHBAcc2", String.valueOf(acc_2_count));
        descr_map.put("nHBAcc3", String.valueOf(acc_3_count));
        descr_map.put("nHBAcc_Lipinski", String.valueOf(lipin_count));
    }

    private int get_order_sum(IAtom atom, List<IAtom> c_atoms)
    {
        int order_sum = 0;

        for (var c_atom : c_atoms) {
            var bond = atom.getBond(c_atom);

            switch (bond.getOrder()) {
                case SINGLE ->
                    order_sum++;
                case DOUBLE ->
                    order_sum += 2;
                case TRIPLE ->
                    order_sum += 3;
                case QUADRUPLE ->
                    order_sum += 4;
            }
        }

        return order_sum;
    }

    private boolean is_conn_to_oxygen(List<IAtom> c_atoms)
    {
        for (var c_atom : c_atoms) {
            if (c_atom.getSymbol().equals("O")) {
                return true;
            }
        }

        return false;
    }

    private boolean is_conn_to_nitrogen(List<IAtom> c_atoms)
    {
        for (var c_atom : c_atoms) {
            if (c_atom.getSymbol().equals("N")) {
                return true;
            }
        }

        return false;
    }

    private boolean is_arom_oxigen_bond(List<IAtom> c_atoms)
    {
        for (var c_atom : c_atoms) {
            if (c_atom.getSymbol().equals("O") && c_atom.isAromatic()) {
                return true;
            }
        }

        return false;
    }

    private boolean is_arom_carbon_bond(List<IAtom> c_atoms)
    {
        for (var c_atom : c_atoms) {
            if (c_atom.getSymbol().equals("C") && c_atom.isAromatic()) {
                return true;
            }
        }

        return false;
    }

    private boolean is_amide_bond(IAtomContainer iac, List<IAtom> c_atoms)
    {
        for (var c_atom : c_atoms) {
            if (c_atom.getSymbol().equals("C")) {
                var c2_atoms = iac.getConnectedAtomsList(c_atom);

                for (var c2_atom : c2_atoms) {
                    if (c2_atom.getSymbol().equals("O")) {
                        if (c_atom.getBond(c2_atom).getOrder()
                                == IBond.Order.DOUBLE) {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }

    private final Map<String, String> descr_map = new HashMap<>();

    private Molecule mol;
}
