/*
 * 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.Collections;
import java.util.HashMap;
import qsardes.molecule.Molecule;
import qsardes.molecule.AbstractMoleculeBuilder;
import qsardes.util.UtilsConv;
import java.util.List;
import java.util.Map;
import org.openscience.cdk.graph.matrix.AdjacencyMatrix;
import org.openscience.cdk.graph.matrix.TopologicalMatrix;
import org.openscience.cdk.interfaces.IAtomContainer;

/**
 * Topological charge descriptors abstract class.
 *
 * @author Nicola Chirico
 */
public abstract class DescriptorTopologicalCharge implements Descriptor
{
    protected DescriptorTopologicalCharge()
    {
    }

    @Override
    public String toString()
    {
        return "Topological charge";
    }

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

    @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 build_output_map();
    }
    @Override
    public List<String> get_descriptor_names()
    {
        List<String> list = new ArrayList<>();

        for (var order : order_list) {
            list.add(build_name("GGI", order));
            list.add(build_name("JGI", order));
        }

        list.add("JGT");

        return list;
    }

    protected abstract double[] get_topol_charge_index(
            double[][] charge_term_matr, int[][] topo_matr);

    protected abstract void calc_mean_topol_charge_index(
            double[] topol_charge_index, int[][] topo_matr);

    protected abstract double[] get_vert_num(int[][] topo_matr)
            throws NumberFormatException;

    protected Map<String, String> get_ggi_map()
    {
        return ggi_map;
    }

    protected Map<String, String> get_jgi_map()
    {
        return jgi_map;
    }

    protected Map<String, String> get_jgt_map()
    {
        return jgt_map;
    }

    protected List<String> get_order_list()
    {
        return Collections.unmodifiableList(order_list);
    }

    protected String build_name(String name, String order)
    {
        return name + order;
    }

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

        get_descriptor_names().forEach(name -> {
            if (name.startsWith("GGI")) ggi_map.put(name, NAN);
            if (name.startsWith("JGI")) jgi_map.put(name, NAN);
            if (name.startsWith("JGT")) jgt_map.put(name, NAN);
        });
    }

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

        var topo_matr = TopologicalMatrix.getMatrix​(iac);

        var galvez_matr = get_galvez_matr(iac, topo_matr);

        var charge_term_matr = get_charge_term_matr(iac, galvez_matr);

        var topol_charge_index = get_topol_charge_index(charge_term_matr,
                topo_matr);

        calc_mean_topol_charge_index(topol_charge_index, topo_matr);
    }

    private double[][] get_galvez_matr(IAtomContainer iac, int[][] topo_matr)
    {
        var rec_dist_matr = new Matrix(get_recipr_dist_matr(topo_matr));

        var adj_matr = new Matrix(new UtilsConv().convert_to_double(
                AdjacencyMatrix.getMatrix(iac)));

        return adj_matr.times(rec_dist_matr).getArray();
    }

    private double[][] get_charge_term_matr(IAtomContainer iac,
            double[][] galvez_matr)
    {
        double[][] matr = new double[galvez_matr.length][galvez_matr[0].length];

        for (int i = 0; i < galvez_matr.length; i++) {
            for (int j = 0; j < galvez_matr[0].length; j++) {
                if (i == j) {
                    matr[i][j] = iac.getConnectedBondsCount​(iac.getAtom(i));
                } else {
                    matr[i][j] = galvez_matr[i][j] - galvez_matr[j][i];
                }
            }
        }

        return matr;
    }

    private double[][] get_recipr_dist_matr(int[][] topo_matr)
    {
        double[][] matr = new double[topo_matr.length][topo_matr[0].length];

        for (int i = 0; i < topo_matr.length; i++) {
            for (int j = 0; j < topo_matr[0].length; j++) {
                if (i == j) {
                    matr[i][j] = 0.0;
                } else {
                    matr[i][j] = 1.0 / (topo_matr[i][j] * topo_matr[i][j]);
                }
            }
        }

        return matr;
    }

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

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

        return out_map;
    }

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

    private final Map<String, String> ggi_map = new HashMap<>();
    private final Map<String, String> jgi_map = new HashMap<>();
    private final Map<String, String> jgt_map = new HashMap<>();

    private final List<Map<String, String>> maps_list = List.of(
            ggi_map, jgi_map, jgt_map);

    private Molecule mol;
}
