/*
 * 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 qsardes.molecule.Molecule;
import qsardes.molecule.AbstractMoleculeBuilder;
import qsardes.util.UtilsCalc;
import qsardes.atomconst.AtomConstFirstIonizPot;
import qsardes.atomconst.AtomConstIntrState;
import qsardes.atomconst.AtomConstMass;
import qsardes.atomconst.AtomConstPolariz;
import qsardes.atomconst.AtomConstSandElectrNeg;
import qsardes.atomconst.AtomConstVanWVol;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtomContainer;
import qsardes.atomconst.AtomConstFunctionCall;

/**
 * Burden modified eigenvalues descriptors abstract class.
 *
 * @author Nicola Chirico
 */
public abstract class DescriptorBurdenModifiedEigenvalues implements Descriptor
{
    protected DescriptorBurdenModifiedEigenvalues()
    {
    }

    @Override
    public String toString()
    {
        return "Burden modified eigenvalues";
    }

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

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

        list.addAll(get_single_descriptor_names_list("SpMin"));
        list.addAll(get_single_descriptor_names_list("SpMax"));

        return list;
    }

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

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

        return build_output_maps();
    }

    private List<String> get_single_descriptor_names_list(String name)
    {
        List<String> list = new ArrayList<>();

        for (var w_item : WeightEnum.values()) {
            final String W_EXT = w_item.get_ext_str();

            for (String eig : eig_list) {
                list.add(build_name(name, eig, W_EXT));
            }
        }

        return list;
    }


    private Map<String, String> build_output_maps()
    {
        Map<String, String> out_map = new HashMap<>();

        maps_list.forEach(src_map -> out_map.putAll(src_map));

        return out_map;
    }

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

        get_descriptor_names().forEach(name -> {
            if (name.startsWith("SpMin")) sp_min_map.put(name, NAN);
            if (name.startsWith("SpMax")) sp_max_map.put(name, NAN);
        });
    }

    private void calculate_descr() throws CDKException
    {
        for (var w_item : WeightEnum.values()) {
            final String W_EXT = w_item.get_ext_str();

            var matr = new Matrix(build_burden_matrix(w_item));

            if (!new UtilsCalc().are_all_matrix_values_valid(matr.getArray())) {
                continue;
            }

            var eig_decomp = matr.eig();

            double[] eig_values = eig_decomp.getRealEigenvalues();

            for (String eig : eig_list) {
                double min = Double.NaN;
                double max = Double.NaN;

                int i = Integer.parseInt(eig);

                if (i < eig_values.length) {
                    min = StrictMath.abs(eig_values[i - 1]);
                    max = StrictMath.abs(eig_values[eig_values.length - i]);
                }

                sp_min_map.put(build_name("SpMin", eig, W_EXT),
                        String.valueOf(min));

                sp_max_map.put(build_name("SpMax", eig, W_EXT),
                        String.valueOf(max));
            }
        }
    }

    private double[][] build_burden_matrix(WeightEnum w_item)
            throws CDKException
    {
        IAtomContainer iac = mol.get_atom_container();

        final int ATOM_COUNT = iac.getAtomCount();

        double[][] bur_matr = new double[ATOM_COUNT][ATOM_COUNT];

        var weight_res = w_item.get_func_call().call(mol);

        double[] weight = weight_res.const_vect();
        double weight_C = weight_res.carbon();

        final double PI_AROM = StrictMath.sqrt(1.5);
        final double PI_ORDER_1 = 1.0;
        final double PI_ORDER_2 = StrictMath.sqrt(2.0);
        final double PI_ORDER_3 = StrictMath.sqrt(3.0);

        for (int i = 0; i < ATOM_COUNT; i++) {
            Arrays.fill(bur_matr[i], 0.0/*01*/);
            bur_matr[i][i] = weight[i] / weight_C;
        }

        for (var bond : iac.bonds()) {
            for (int idx = 0; idx < bond.getAtomCount() - 1; idx++) {
                var atom_i = bond.getAtom(idx);
                var atom_j = bond.getAtom(idx + 1);

                int i = iac.indexOf(atom_i);
                int j = iac.indexOf(atom_j);

                double pi_b = 0.0;

                if (bond.isAromatic()) {
                    pi_b = PI_AROM;
                } else {
                    switch (bond.getOrder()) {
                        case SINGLE ->
                            pi_b = PI_ORDER_1;
                        case DOUBLE ->
                            pi_b = PI_ORDER_2;
                        case TRIPLE ->
                            pi_b = PI_ORDER_3;
                    }
                }

                if ((atom_i.getAtomicNumber() > 1)
                        && (atom_j.getAtomicNumber() > 1)) {
                    if ((atom_i.getBondCount() == 1)
                            || (atom_j.getBondCount() == 1)) {
                        pi_b += 0.1;
                    }
                }

                bur_matr[i][j] = pi_b;
                bur_matr[j][i] = pi_b;
            }
        }

        return bur_matr;
    }

    private String build_name(String prefix, String eig, String w_ext)
    {
        return prefix + eig + "_Bh" + w_ext;
    }

    private enum WeightEnum
    {
        E("e", new AtomConstSandElectrNeg()),
        I("i", new AtomConstFirstIonizPot()),
        M("m", new AtomConstMass()),
        P("p", new AtomConstPolariz()),
        S("s", new AtomConstIntrState()),
        V("v", new AtomConstVanWVol());

        private WeightEnum(String ext_str, AtomConstFunctionCall func_call)
        {
            this.ext_str = ext_str;
            this.func_call = func_call;
        }

        public String get_ext_str()
        {
            return ext_str;
        }

        public AtomConstFunctionCall get_func_call()
        {
            return func_call;
        }

        private final String ext_str;
        private final AtomConstFunctionCall func_call;
    }

    private final List<String> eig_list = List.of(
            "1", "2", "3", "4", "5", "6", "7", "8");

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

    private final List<Map<String, String>> maps_list = List.of(
            sp_min_map, sp_max_map);

    private Molecule mol;
}
