/*
 * 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 Jama.Matrix;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import qsardes.molecule.Molecule;
import qsardes.util.UtilsCalc;
import qsardes.atomconst.AtomConstNum;
import qsardes.atomconst.AtomConstFirstIonizPot;
import qsardes.atomconst.AtomConstIntrStateH;
import qsardes.atomconst.AtomConstMass;
import qsardes.atomconst.AtomConstPolariz;
import qsardes.atomconst.AtomConstSandElectrNeg;
import qsardes.atomconst.AtomConstVanWVol;
import java.util.List;
import java.util.Map;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.graph.AllPairsShortestPaths;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import static org.openscience.cdk.interfaces.IBond.Order.DOUBLE;
import static org.openscience.cdk.interfaces.IBond.Order.SINGLE;
import static org.openscience.cdk.interfaces.IBond.Order.TRIPLE;
import qsardes.atomconst.AtomConstAllRochElectrNeg;
import qsardes.atomconst.AtomConstFunctionCall;
import qsardes.atomconst.AtomConstPaulElectrNeg;
import qsardes.molecule.AbstractMoleculeBuilder;

/**
 * Barysz matrix descriptors abstract class.
 *
 * @author Nicola Chirico
 */
public abstract class DescriptorBaryszMatrix implements Descriptor
{
    protected DescriptorBaryszMatrix()
    {
    }

    @Override
    public String toString()
    {
        return "Barysz matrix";
    }

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

    @Override
    public List<String> get_descriptor_names()
    {
        List<String> list = new ArrayList<>();

        var name_list = Arrays.asList(
                "SpAbs", "SpMax", "SpDiam", "SpAD", "SpMAD",
                "EE",    "SM1",   "VE1",    "VE2",  "VE3",
                "VR1",   "VR2",   "VR3");

        var weights_list = get_weights_list();

        for (var w_name_map : w_name_map_list) {
            String w_name = w_name_map.entrySet().iterator().next().getKey();

            if (weights_list.contains(w_name)) {
                name_list.forEach(item -> list.add(build_name(item, w_name)));
            }
        }

        return list;
    }

    @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;
    }

    protected abstract List<String> get_weights_list();

    protected abstract double calculate_sp_ve1_sum(UtilsCalc utils_calc,
            double[][] eig_vec_array);

    protected abstract double calculate_sp_vr1_sum(IAtomContainer iac,
            UtilsCalc utils_calc, double[][] eig_vec_array);

    protected abstract double get_sp_ve1_sum(double sp_ve1_sum);

    protected abstract double get_ve3(double sp_ve1_sum, int atom_count);

    protected abstract double get_vr3(double vr1_sum, int atom_count);

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

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

    private void calculate_descr() throws CDKException
    {
        final int ATOM_COUNT = mol.get_atom_container().getAtomCount();

        var weights_list = get_weights_list();

        for (var w_name_map : w_name_map_list) {
            var matr = new Matrix(build_barysz_matrix(w_name_map));

            if (utils_calc.are_all_matrix_values_valid(matr.getArray())) {
                String w_name = w_name_map.entrySet().iterator()
                        .next().getKey();

                if (!weights_list.contains(w_name)) {
                    continue;
                }

                var eig_decomp = matr.eig();

                double[] eig_values = eig_decomp.getRealEigenvalues();
                double[][] eig_vec_array = eig_decomp.getV().getArray();

                double eig_val_min = utils_calc.get_min(eig_values);
                double eig_val_max = utils_calc.get_max(eig_values);

                double sp_abs_sum = utils_calc.calc_abs_sum(eig_values);
                double sp_ad_sum = utils_calc.calc_ad_sum(eig_values);

                double sp_sm1_sum = utils_calc.calc_sum(eig_values);
                double sp_ve1_sum = calculate_sp_ve1_sum(utils_calc,
                        eig_vec_array);

                double ee_lse_exp_sum = utils_calc.calc_ee_lse_exp_sum(
                        eig_values);

                double vr1_sum = calculate_sp_vr1_sum(mol.get_atom_container(),
                        utils_calc, eig_vec_array);

                descr_map.put(build_name("SpAbs", w_name),
                        String.valueOf(sp_abs_sum));

                descr_map.put(build_name("SpMax", w_name),
                        String.valueOf(eig_val_max));

                descr_map.put(build_name("SpDiam", w_name),
                        String.valueOf(eig_val_max - eig_val_min));

                descr_map.put(build_name("SpAD", w_name),
                        String.valueOf(sp_ad_sum));

                descr_map.put(build_name("SpMAD", w_name),
                        String.valueOf(sp_ad_sum / (double) ATOM_COUNT));

                // log(1 + b) -> log(a + b) = log(exp(log(a)) + exp(log(b)))
                // eig_val_max reassembles the final part of the Log-Sum-Exp
                // function.
                try {
                    descr_map.put(build_name("EE", w_name), String.valueOf(
                            StrictMath.log(StrictMath.exp(StrictMath.log(1.0))
                                    + StrictMath.exp(eig_val_max
                                        + StrictMath.log(ee_lse_exp_sum)))));
                } catch (Exception ex) {
                    descr_map.put(build_name("EE", w_name),
                            String.valueOf(Double.NaN));
                }

                descr_map.put(build_name("SM1", w_name),
                        String.valueOf(sp_sm1_sum));

                descr_map.put(build_name("VE1", w_name),
                        String.valueOf(get_sp_ve1_sum(sp_ve1_sum)));

                descr_map.put(build_name("VE2", w_name),
                        String.valueOf(get_sp_ve1_sum(sp_ve1_sum)
                                / (double) ATOM_COUNT));

                descr_map.put(build_name("VE3", w_name),
                        String.valueOf(get_ve3(sp_ve1_sum, ATOM_COUNT)));

                descr_map.put(build_name("VR1", w_name), String.valueOf(
                        vr1_sum));

                descr_map.put(build_name("VR2", w_name),
                        String.valueOf(vr1_sum / (double) ATOM_COUNT));

                descr_map.put(build_name("VR3", w_name),
                        String.valueOf(get_vr3(vr1_sum, ATOM_COUNT)));
            }
        }
    }

    private double[][] build_barysz_matrix(
            Map<String, AtomConstFunctionCall> w_name_map) throws CDKException
    {
        IAtomContainer iac = mol.get_atom_container();

        final int ATOM_COUNT = iac.getAtomCount();

        double[][] brz_matr = new double[ATOM_COUNT][ATOM_COUNT];

        var apsp = new AllPairsShortestPaths(iac);

        var weight_res = w_name_map.entrySet().iterator()
                .next().getValue().call(mol);

        Map<IAtom, Double> weight_map = weight_res.const_map();

        double weight_C = weight_res.carbon();

        for (int i = 0; i < ATOM_COUNT; i++) {
            for (int j = i; j < ATOM_COUNT; j++) {
                if (i == j) {
                    brz_matr[i][j] = 1.0 - (weight_C
                            / weight_map.get(iac.getAtom(i)));
                } else {
                    double sum = 0.0;

                    IAtom[] atom_sp = apsp.from(i).atomsTo(j);

                    for (int k = 0, n = atom_sp.length; k < (n - 1); k++) {
                        var atom_1 = atom_sp[k];
                        var atom_2 = atom_sp[k + 1];

                        var bond = atom_1.getBond(atom_2);

                        double pi_b = 0.0;

                        if (bond.isAromatic()) {
                            pi_b = 1.5;
                        } else {
                            switch (bond.getOrder()) {
                                case SINGLE ->
                                    pi_b = 1.0;
                                case DOUBLE ->
                                    pi_b = 2.0;
                                case TRIPLE ->
                                    pi_b = 3.0;
                            }
                        }

                        sum += (1.0 / pi_b) * ((weight_C * weight_C)
                                / (weight_map.get(atom_1)
                                * weight_map.get(atom_2)));
                    }

                    brz_matr[i][j] = sum;
                    brz_matr[j][i] = sum;
                }
            }
        }

        return brz_matr;
    }

    private String build_name(String prefix, String postfix)
    {
        return prefix + "_" + postfix;
    }

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

    private final List<Map<String, AtomConstFunctionCall>> w_name_map_list
            = List.of(Map.of("DzZ", new AtomConstNum()),
                    Map.of("Dzm", new AtomConstMass()),
                    Map.of("Dzv", new AtomConstVanWVol()),
                    Map.of("Dze", new AtomConstSandElectrNeg()),
                    Map.of("Dzp", new AtomConstPolariz()),
                    Map.of("Dzi", new AtomConstFirstIonizPot()),
                    Map.of("Dzs", new AtomConstIntrStateH()),
                    Map.of("Dzpe", new AtomConstPaulElectrNeg()),
                    Map.of("Dzare", new AtomConstAllRochElectrNeg()));

    private Molecule mol;

    private final UtilsCalc utils_calc = new UtilsCalc();
}
