/*
 * 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.UtilsConv;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openscience.cdk.graph.matrix.AdjacencyMatrix;
import org.openscience.cdk.interfaces.IAtomContainer;

/**
 * Walk count descriptors abstract class.
 *
 * @author Nicola Chirico
 */
public abstract class DescriptorWalkCount implements Descriptor
{
    protected DescriptorWalkCount()
    {
    }

    @Override
    public String toString()
    {
        return "Walk count";
    }

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

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

        final int ORDER_FINAL = Integer.parseInt(
                order_list.get(order_list.size() - 1));

        for (int order = 2; order <= ORDER_FINAL; order++) {
            list.add("MWC" + String.valueOf(order));
            list.add("SRW" + String.valueOf(order));
        }

        list.addAll(Arrays.asList("TWC", "TSRW"));

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

    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()
    {
        var iac = mol.get_atom_container();

        var adj_matr_comput = get_adj_matr_comput(iac);

        int[][] row_sum = adj_matr_comput.row_sum_matr();
        int[] diag = adj_matr_comput.diag_vect();

        double twc = (double) iac.getAtomCount();
        double tsrw = (double) iac.getAtomCount();

        final int ORDER_FINAL = Integer.parseInt(
                order_list.get(order_list.size() - 1));

        for (int order = 1; order <= ORDER_FINAL; order++) {
            int mwc = 0;

            for (var item : row_sum) {
                mwc += item[order];
            }

            double l_mwc = order > 1
                    ? StrictMath.log(1.0 + (double) mwc) // To match Dragon des.
                    : (double) mwc / 2.0;

            twc += l_mwc;

            // To match Dragon desc.
            double l_trs = StrictMath.log(1.0 + (double) diag[order]);

            if (order > 1) {
                descr_map.put("MWC" + String.valueOf(order),
                        String.valueOf(l_mwc));

                descr_map.put("SRW" + String.valueOf(order),
                        String.valueOf(l_trs));
            }

            tsrw += calculate_tsrw(diag[order]);
        }

        descr_map.put("TWC", String.valueOf(twc));
        descr_map.put("TSRW",String.valueOf(get_tsrw(tsrw)));
    }

    protected abstract double calculate_tsrw(int diag_value);
    protected abstract double get_tsrw(double tsrw);

    private AdjMatrComput get_adj_matr_comput(IAtomContainer iac)
    {
        var adj_matr = AdjacencyMatrix.getMatrix(iac);

        var pow_matr = Arrays.stream(adj_matr).map(int[]::clone)
                .toArray(int[][]::new);

        var utils_conv = new UtilsConv();

        var m_adj_matr = new Matrix(utils_conv.convert_to_double(adj_matr));
        var m_pow_matr = new Matrix(utils_conv.convert_to_double(pow_matr));

        final int ORDER_FINAL = Integer.parseInt(
                order_list.get(order_list.size() - 1));

        int[][] row_sum_matr = new int[adj_matr.length][ORDER_FINAL + 1];
        int[] diag_vect = new int[ORDER_FINAL + 1];

        for (int order = 1; order <= ORDER_FINAL; order++) {
            int dia_sum = 0;

            for (int i = 0, n = m_adj_matr.getRowDimension();
                    i < n; i++) {
                int row_sum = 0;

                for (int j = 0, m = m_adj_matr.getColumnDimension();
                        j < m; j++) {
                    row_sum += (int) m_pow_matr.get(i, j);
                }

                row_sum_matr[i][order] += row_sum;

                dia_sum += (int) m_pow_matr.get(i, i);
            }

            diag_vect[order] = dia_sum;

            m_pow_matr = m_pow_matr.times(m_adj_matr);
        }

        return new AdjMatrComput(row_sum_matr, diag_vect);
    }

    record AdjMatrComput(int[][] row_sum_matr, int[] diag_vect)
    {
    }

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

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

    private Molecule mol;
}
