/*
 * 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.cdk;

import qsardes.molecule.Molecule;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.qsar.DescriptorValue;
import org.openscience.cdk.qsar.IMolecularDescriptor;
import org.openscience.cdk.qsar.result.BooleanResult;
import org.openscience.cdk.qsar.result.BooleanResultType;
import org.openscience.cdk.qsar.result.DoubleArrayResult;
import org.openscience.cdk.qsar.result.DoubleArrayResultType;
import org.openscience.cdk.qsar.result.DoubleResult;
import org.openscience.cdk.qsar.result.DoubleResultType;
import org.openscience.cdk.qsar.result.IntegerArrayResult;
import org.openscience.cdk.qsar.result.IntegerArrayResultType;
import org.openscience.cdk.qsar.result.IntegerResult;
import org.openscience.cdk.qsar.result.IntegerResultType;
import org.openscience.cdk.silent.SilentChemObjectBuilder;
import qsardes.molecule.AbstractMoleculeBuilder;

/**
 * This class runs CDK descriptors calculation and harmonize the returned
 * values as String maps.
 *
 * @author Nicola Chirico
 */
public final class CdkBuilder
{
    /**
     * Constructs CDK descriptors calculator.
     *
     * @param descr CDK descriptor class.
     * @param param CDK descriptor parameters.
     * @param mol_build Molecular builder representation.
     *
     */
    public CdkBuilder(IMolecularDescriptor descr, Object[] param,
            AbstractMoleculeBuilder mol_build)
    {
        this.descr = descr;
        this.param = param;
        this.mol_build = mol_build;
    }

    /**
     * Calculates CDK descriptor values using implicit hydrogens.
     *
     * @return Descriptor values mapped as name:value.
     */
    public Map<String, String> calc_descr_impl_hydrogens()
    {
        return calc_descr(HydrogensType.IMPLICIT);
    }

    /**
     * Calculates CDK descriptor values using explicit hydrogens.
     *
     * @return Descriptor values mapped as name:value.
     */
    public Map<String, String> calc_descr_expl_hydrogens()
    {
        return calc_descr(HydrogensType.EXPLICIT);
    }

    private Map<String, String> calc_descr(HydrogensType h_type)
    {
        try {
            set_param(param, descr);

            var molecule = new Molecule(mol_build);

            if (h_type == HydrogensType.EXPLICIT) {
                molecule.convert_impl_to_expl_hydrogens();
            }

            return generate_descriptor(descr, molecule).map();
        } catch (Exception ex) {
            System.err.println(ex);
            return new CdkTools().get_empty_map(descr);
        }
    }

    private void set_param(Object[] param, IMolecularDescriptor descr)
            throws CDKException
    {
        if (param.length > 0) {
            descr.setParameters(param);
        }
    }

    private record DescrResult(Map<String, String> map, Molecule mol)
    {
    }

    private DescrResult generate_descriptor(IMolecularDescriptor descr,
            Molecule mol)
    {
        IAtomContainer iac = mol.get_atom_container();

        if (iac == null) {
            return new DescrResult(new LinkedHashMap<>(), mol);
        }

        descr.initialise(SilentChemObjectBuilder.getInstance());

        var res_type = descr.getDescriptorResultType();

        if ((res_type instanceof BooleanResult)
                || (res_type instanceof BooleanResultType)
                || (res_type instanceof IntegerResult)
                || (res_type instanceof IntegerResultType)
                || (res_type instanceof DoubleResult)
                || (res_type instanceof DoubleResultType)) {
            return new DescrResult(get_value_descr(descr.calculate(iac)), mol);
        }

        if ((res_type instanceof IntegerArrayResult)
                || (res_type instanceof IntegerArrayResultType)
                || (res_type instanceof DoubleArrayResult)
                || (res_type instanceof DoubleArrayResultType)) {
            return new DescrResult(get_array_descr(descr.calculate(iac)), mol);
        }

        return new DescrResult(new LinkedHashMap<>(), mol);
    }

    private Map<String, String> get_value_descr(DescriptorValue value)
    {
        Map<String, String> map = new LinkedHashMap<>();

        map.put(value.getNames()[0], value.getValue().toString());

        return map;
    }

    private Map<String, String> get_array_descr(DescriptorValue value)
    {
        Map<String, String> map = new LinkedHashMap<>();

        String[] names = value.getNames();
        String[] values = get_descr_values(value.getValue().toString(),
                names.length);

        for (int i = 0, n = value.getNames().length; i < n; i++) {
            map.put(names[i], values[i]);
        }

        return map;
    }

    private String[] get_descr_values(String values, int size)
    {
        String[] array = new String[size];

        Scanner scanner = new Scanner(values);
        scanner.useDelimiter(",");

        int i = 0;

        while (scanner.hasNext()) {
            array[i] = scanner.next();
            i++;
        }

        return array;
    }

    private enum HydrogensType { IMPLICIT, EXPLICIT }

    private final IMolecularDescriptor descr;
    private final Object[] param;
    private final AbstractMoleculeBuilder mol_build;
}
