/*
 * 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.ArrayList;
import qsardes.molecule.Molecule;
import qsardes.molecule.AbstractMoleculeBuilder;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.exception.Intractable;
import org.openscience.cdk.graph.Cycles;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IRingSet;

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

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

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

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

        for (String name : name_list) {
            for (String count : count_list) {
                if (is_nring_4(name, count)) {
                    list.add(build_name(name, "3"));
                }

                list.add(build_name(name, count));
            }
        }

        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 IRingSet get_t_ring_set(IAtomContainer iac)
            throws CDKException;

    private void prepare_descr_map()
    {
        get_descriptor_names()
                .forEach(name -> descr_map.put(name, "0"));
    }

    private void calculate_descr()
    {
        calculate_n_ring();
        calculate_t_ring();
    }

    private void calculate_n_ring() throws NumberFormatException
    {
        try {
            var ring_set = Cycles.essential()
                    .find(mol.get_atom_container()).toRingSet();

            Arrays.fill(ring_count, 0);
            Arrays.fill(h_ring_count, 0);

            for (var ring : ring_set) {
                int a_count = ring.getAtomCount();

                if ((a_count > 2) && (a_count <= 12)) {
                    ring_count[a_count]++;

                    if (contains_hetero_atom(ring.atoms())) {
                        h_ring_count[a_count]++;
                    }
                } else if (a_count > 12) {
                    ring_count[ring_count.length - 1]++;

                    if (contains_hetero_atom(ring.atoms())) {
                        h_ring_count[ring_count.length - 1]++;
                    }
                }
            }

            gen_descr_map("n_Ring", ring_count);
            gen_descr_map("n_HeteroRing", h_ring_count);
        } catch (Intractable ex) {
            Logger.getLogger(DescriptorRingCount.class.getName())
                    .log(Level.SEVERE, null, ex);
        }
    }

    private void calculate_t_ring()
    {
        try {
            var ring_set = get_t_ring_set(mol.get_atom_container());

            Arrays.fill(t_ring_count, 0);
            Arrays.fill(t_h_ring_count, 0);

            for (int i = 0, n = ring_set.getAtomContainerCount(); i < n; i++) {
                var ring_iac = ring_set.getAtomContainer(i);

                var a_count = ring_iac.getAtomCount();

                if ((a_count > 2) && (a_count <= 12)) {
                    t_ring_count[a_count]++;

                    if (contains_hetero_atom(ring_iac.atoms())) {
                        t_h_ring_count[a_count]++;
                    }
                } else if (a_count > 12) {
                    t_ring_count[t_ring_count.length - 1]++;

                    if (contains_hetero_atom(ring_iac.atoms())) {
                        t_h_ring_count[t_ring_count.length - 1]++;
                    }
                }
            }

            gen_descr_map("nT_Ring", t_ring_count);
            gen_descr_map("nT_HeteroRing", t_h_ring_count);

            for (int i = 0; i < t_ring_count.length; i++) {
                t_ring_count[i] -= ring_count[i];
            }

            for (int i = 0; i < t_h_ring_count.length; i++) {
                t_h_ring_count[i] -= h_ring_count[i];
            }

            gen_descr_map("nF_Ring", t_ring_count);
            gen_descr_map("nF_HeteroRing", t_h_ring_count);
        } catch (Exception ex) {
            System.getLogger(DescriptorRingCount.class.getName())
                    .log(System.Logger.Level.ERROR, (String) null, ex);
        }
    }

    private boolean contains_hetero_atom(Iterable<IAtom> atoms)
    {
        for (var atom : atoms) {
            switch (atom.getSymbol()) {
                case "N", "O", "S", "F", "Cl", "Br", "I", "At" -> {
                    return true;
                }
            }
        }

        return false;
    }

    private void gen_descr_map(String name, int[] ring_count)
    {
        int tot_ring = 0;

        for (int i = 0; i < ring_count.length; i++) {
            int count = ring_count[i];

            if (count > 0) {
                if (i < (ring_count.length - 1)) {
                    write_descr(build_name(name, String.valueOf(i)), count);
                } else {
                    write_descr(build_name(name, ADD_COUNT), count);
                }

                tot_ring += count;
            }
        }

        write_descr(build_name(name, ""), tot_ring);
    }

    private void write_descr(String full_name, int count)
    {
        if (descr_map.containsKey(full_name)) {
            descr_map.put(full_name, String.valueOf(count));
        }
    }

    private boolean is_nring_4(String name, String count)
    {
        return name.equals("n_Ring") && count.equals("4")
                || name.equals("n_HeteroRing") && count.equals("4");
    }

    private String build_name(String name, String count)
    {
        int idx = name.indexOf("_");

        return name.substring(0, idx) + count + name.substring(idx + 1);
    }

    private final List<String> name_list = List.of(
            "n_Ring",       "nF_Ring",       "nT_Ring",
            "n_HeteroRing", "nF_HeteroRing", "nT_HeteroRing");

    private final String MAX_COUNT = "12";
    private final String ADD_COUNT = "12G";

    private final List<String> count_list = List.of(
            "", "4", "5", "6", "7", "8", "9", "10", "11",
            MAX_COUNT, ADD_COUNT);

    private final int[] ring_count
            = new int[Integer.parseInt(MAX_COUNT) + 2];

    private final int[] h_ring_count
            = new int[Integer.parseInt(MAX_COUNT) + 2];

    private final int[] t_ring_count
            = new int[Integer.parseInt(MAX_COUNT) + 2];

    private final int[] t_h_ring_count
            = new int[Integer.parseInt(MAX_COUNT) + 2];

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

    private Molecule mol;
}
