
/**
 * Módulo validación, utilizado realizar la validación de los resultados obtenidos en el anísis de nicho y comunidad ecológica.
 *
 * @namespace validation_module
 */
var validation_module = (function(verbose) {

    // ************ variables publicas y privadas ( denotadas por _ ) ************

    var _VERBOSE = verbose;

    var _histogram_module_nicho;

    var _nameMap = d3.map([]);

    var _NUM_DECIL = 10, _NUM_ITERATIONS;


    /**
     * Método setter del módulo histograma
     *
     * @function set_histogram_module
     * @public
     * @memberof! validation_module
     * 
     * @param {object} histogram_module - Módulo histograma
     */
    function set_histogram_module(histogram_module) {
        _histogram_module_nicho = histogram_module;
    }


    /**
     * Éste método inicializa variables y componentes que son necesarios en el proceso de validación.
     *
     * @function _initilizeElementsForValidationModule
     * @private
     * @memberof! validation_module
     * 
     */
    function _initilizeElementsForValidationModule() {
        _VERBOSE ? console.log("_initilizeElementsForValidationModule") : _VERBOSE;
    }


    /**
     * Éste método obtiene los valores de recall de los cálculos realizados a través del conteo de verdaderos positivos, falsos negativos y falsos positivos de un conjunto de datos.
     *
     * @function validationProcess
     * @public
     * @memberof! validation_module
     * 
     * @param {array} validationData - Array con los resultados obtenidos en las iteraciones hechas del anáslisis de nicho ecológico
     * @param {array} sp_gridids - Array con los id de las celdas que componen la malla 
     * @param {integer} n_iterations - Número de iteraciones realizadas
     * @param {String} id_chartscr_decil - Id del contenedor del histograma donde se despliegan los resultados de validación
     */
    function validationProcess(validationData, sp_gridids, n_iterations, id_chartscr_decil) {

        _VERBOSE ? console.log("validationProcess") : _VERBOSE;
        _VERBOSE ? console.log(validationData) : _VERBOSE;

        _NUM_ITERATIONS = n_iterations;
        _nameMap = d3.map([]);

        // va concatenando lso valores para el line chart
        var item_recall = [];
        // recolecta el ultimo valor de la ultima iteracion para hacer la gráfica de barras
        var item_chart = [];

        for (l = 0; l < validationData.length; l++) {

            var iter_recall = [];

            // var array_recall_groups = [];
            item_chart = validationData[l].data_chart;
            test_set = validationData[l].test_set;


            // obtiene los limites de los deciles por cada iteración
            decilBoundaries = _getLimitBoudaries(item_chart);

            // obtiene las especies con sus scores por grupo de variables
            mapScoreSp = _getMapSpecies(item_chart);

            // obtiene la intersección entre los gridids de la especie objetivo con los del conjunto de prueba
            // este proceso disminuye el proceso tomando solo los gridids que son necesario analizar
            sp_grid_temp = [];
            $.each(sp_gridids, function(index, item) {
                if ($.inArray(item.gridid, test_set) != -1) {
                    sp_grid_temp.push(item);
                }
            });


            // verifica que al menos existio una intersección entre el conjunto de prueba y la especie objetivo
            _VERBOSE ? console.log((sp_grid_temp.length > 0) ? "Intersección" : "Sin intersección de especies") : _VERBOSE;

            // obtiene los scores por celda de las celdas con intersección entre el conjunto de prueba y la especie objetivo
            scoresByGroup = _getTestDataScore(sp_grid_temp, mapScoreSp);

            // obtiene los VP, FN y Recall y vetifica si el recall es nulo en algun conjunto de datos
            iter_recall = _getMeasure(decilBoundaries, scoresByGroup);
            item_recall = item_recall.concat(iter_recall);

        }

        recall_crs = crossfilter(item_recall),
                all = recall_crs.groupAll(),
                dim_name = recall_crs.dimension(function(d) {
                    return d.group_name;
                }),
                dim_decil = recall_crs.dimension(function(d) {
                    return d.decil;
                });

        // obtener el arreglo de los nombres de la colecicón y deciles, hacer funcion para sumar y promediar valores!!
        arg_decil = d3.range(1, _NUM_DECIL + 1, 1).reverse();

        // var prom_item_recall = [];
        var array_recall_groups = [];

        $.each(_nameMap.values(), function(i, group_name) {

            dim_name.filter(group_name.name);

            prom_item_recall = [];

            $.each(arg_decil, function(j, decil) {

                var tp_tot = 0;
                var fn_tot = 0;
                var recall_tot = 0;
                var iter_tot = 0;

                dim_decil.filter(decil.toString());
                // _VERBOSE ? console.log(dim_decil.top(Infinity)) : _VERBOSE;

                // Se agrupan por decil y se recorren los resultados de las n iteraciones para obtener el total de fn, vp y recall
                $.each(dim_decil.top(Infinity), function(k, item) {

                    // descarta las iteraciones donde el recall esta indefinido
                    if (!item.recall_nulo) {
                        tp_tot += item.vp;
                        fn_tot += item.fn;
                        recall_tot += item.recall;
                    }
                    else {
                        iter_tot++;
                    }
                });

                // descarta las iteraciones que no se obtuvieron valores
                var iteraciones_validas = _NUM_ITERATIONS - iter_tot;
                if (iteraciones_validas > 0) {

                    tp_tot = parseFloat(tp_tot) / iteraciones_validas;
                    fn_tot = parseFloat(fn_tot) / iteraciones_validas;
                    recall_tot = parseFloat(recall_tot) / iteraciones_validas;
                    // prom_item_recall.push({"group_name":group_name, "recall": recall_tot, "vp": tp_tot, "fn": fn_tot, "decil":decil});

                }
                // se asigna valor de cero en recall al line chart del decil N si no tiene valores
                prom_item_recall.push({"group_name": group_name, "recall": recall_tot, "vp": tp_tot, "fn": fn_tot, "decil": decil});

                dim_decil.filterAll();

            })

            array_recall_groups.push(prom_item_recall);
            dim_name.filterAll();


        });

        // se envia el ultimo item del grupo conjuntado en las iteraciones (item_chart)
        _histogram_module_nicho.createMultipleBarChart(item_chart, array_recall_groups, id_chartscr_decil, _nameMap);


    }


    /**
     * Éste método recalcula los límites superiores e inferiores de los deciles que son enviados por el conjunto de prueba con el fin de eliminar rangos intermedios entre los deciles, el promedio del limite inferior de decil N con el limite superior del decil N-1.
     *
     * @function _getLimitBoudaries
     * @private
     * @memberof! validation_module
     * 
     * @param {array} item_chart - Array con el resultado de cada una de las iteraciones realizadas del análisis de nicho ecológico
     */
    function _getLimitBoudaries(item_chart) {

        _VERBOSE ? console.log("getLimitBoudaries") : _VERBOSE;
        var decilBoundaries = [];
        var num_deciles_values;

        // _VERBOSE ? console.log(item_chart) : _VERBOSE;

        // obtiene el numero de grupos creados (incluye totales)
        for (i = 0; i < _NUM_DECIL; i++) {
            try {
                num_groups = item_chart[i].gridids.length;
                num_deciles_values = i;
                break;
            } catch (e) {
                _VERBOSE ? console.log("sin valores decil: " + i) : _VERBOSE;
            }
        }

        // _VERBOSE ? console.log(num_groups) : _VERBOSE;
        // _VERBOSE ? console.log(num_deciles_values) : _VERBOSE;


        for (j = 0; j < num_groups; j++) {

            var decilGroupBoundaries = [];

            for (i = num_deciles_values; i < item_chart.length - 1; i++) {

                current_decil_item = item_chart[i];
                // _VERBOSE ? console.log(current_decil_item) : _VERBOSE;	


                c_linf = parseFloat(current_decil_item.linf[j].p);
                c_lsup = parseFloat(current_decil_item.lsup[j].p);

                next_decil_item = item_chart[i + 1];
                // _VERBOSE ? console.log(next_decil_item) : _VERBOSE;

                n_linf = parseFloat(next_decil_item.linf[j].p);
                n_lsup = parseFloat(next_decil_item.lsup[j].p);

                g_name = current_decil_item.names[j].p;

                // _VERBOSE ? console.log(c_linf) : _VERBOSE;
                // _VERBOSE ? console.log(c_lsup) : _VERBOSE;
                // _VERBOSE ? console.log(n_linf) : _VERBOSE;
                // _VERBOSE ? console.log(n_lsup) : _VERBOSE;


                if (i == num_deciles_values) {
                    // nota: (parseFloat((c_linf+n_lsup)/2).toFixed(2))/1 -- se divide entre 1 para que el valor de retorno no sea un strind si no un float
                    decilGroupBoundaries.push({group_name: g_name, decil: current_decil_item.decil, lsup: Number.POSITIVE_INFINITY, linf: (parseFloat((c_linf + n_lsup) / 2).toFixed(2)) / 1});
                }
                else if (i == item_chart.length - 2) {

                    decilGroupBoundaries.push({group_name: g_name, decil: current_decil_item.decil, lsup: decilGroupBoundaries[decilGroupBoundaries.length - 1].linf, linf: (parseFloat((c_linf + n_lsup) / 2).toFixed(2)) / 1});

                    decilGroupBoundaries.push({group_name: g_name, decil: next_decil_item.decil, lsup: decilGroupBoundaries[decilGroupBoundaries.length - 1].linf, linf: Number.NEGATIVE_INFINITY});

                    break;
                }
                else {
                    decilGroupBoundaries.push({group_name: g_name, decil: current_decil_item.decil, lsup: decilGroupBoundaries[decilGroupBoundaries.length - 1].linf, linf: (parseFloat((c_linf + n_lsup) / 2).toFixed(2)) / 1});
                }

            }

            decilBoundaries.push(decilGroupBoundaries);

        }

        _VERBOSE ? console.log(decilBoundaries) : _VERBOSE;
        return decilBoundaries;
    }

    /**
     * Éste método obtiene el score de las especies que resultaron del conjunto de entrenamiento.
     *
     * @function _getMapSpecies
     * @private
     * @memberof! validation_module
     * 
     * @param {array} item_chart - Array con el resultado de cada una de las iteraciones realizadas del análisis de nicho ecológico
     */
    function _getMapSpecies(item_chart) {

        _VERBOSE ? console.log("getMapSpecies") : _VERBOSE;
        var scoreSpeciesTotal = [];
        var num_deciles_values;


        // obtiene el numero de grupos creados (incluye totales)
        for (i = 0; i < _NUM_DECIL; i++) {
            try {
                num_groups = item_chart[i].gridids.length;
                num_deciles_values = i;
                break;
            } catch (e) {
                _VERBOSE ? console.log("sin valores decil: " + i) : _VERBOSE;
            }
        }

        // obtiene el numero de grupos creados (incluye totales)
        // num_groups = item_chart[0].gridids.length;

        // for para recorrer por grupos, (grupo bio1, grupo bio2, etc...)
        for (j = 0; j < num_groups; j++) {

            var scoreSpecies = d3.map([]);


            // se recorren todos los deciles del grupo 0 al N
            for (i = num_deciles_values; i < item_chart.length - 1; i++) {

                current_decil_item = item_chart[i];

                strArgSpecies = current_decil_item.species[j].p;
                strArgName = current_decil_item.names[j].p;


                $.each(strArgSpecies, function(index, strItem) {

                    // index 0 = spid y index 3 = score
                    scoreSpecies.set(parseInt(strItem.split("|")[0]), {score: parseFloat(strItem.split("|")[3]), group_name: strArgName});

                });

            }

            scoreSpeciesTotal.push(scoreSpecies);

        }

        _VERBOSE ? console.log(scoreSpeciesTotal) : _VERBOSE;
        return scoreSpeciesTotal;

    }


    /**
     * Éste método obtiene los scores de las especies que se encuentran en el conjunto de prueba basado en los resultados del conjunto de entrenamiento.
     *
     * @function _getMapSpecies
     * @private
     * @memberof! validation_module
     * 
     * @param {array} scoreSpecies - Mapa de score de las especies por grupo de variables
     * @param {array} spGridid - Celdas con intersección entre el conjunto de prueba y la especie objetivo
     */
    function _getTestDataScore(spGridid, scoreSpecies) {

        _VERBOSE ? console.log("getTestDataScore") : _VERBOSE;
        var scoresByGroup = [];

        $.each(scoreSpecies, function(index, scoreSpecies_item) {

            // scoreSpecies_item: mapa de socre de las especies por grupo de variables
            // console.log(scoreSpecies_item);

            var scoresGroup = [];

            $.each(spGridid, function(index, item_gridid) {

                // console.log(item_gridid);

                var gridSum = 0;
                var anyRecords = false;
                var group_name = "";

                $.each(item_gridid.spids, function(index, spid) {

                    // console.log(spid);

                    if (scoreSpecies_item.has(spid)) {

                        gridSum += scoreSpecies_item.get(spid).score;
                        group_name = scoreSpecies_item.get(spid).group_name;
                        anyRecords = true;

                    }

                });

                scoresGroup.push({gridid: item_gridid.gridid, score: (anyRecords ? gridSum : null), group_name: group_name});

            });

            scoresByGroup.push(scoresGroup);

        });

        _VERBOSE ? console.log(scoresByGroup) : _VERBOSE;
        return scoresByGroup;

    }


    /**
     * Éste método obtiene los verdaderos positivos, falsos negativos, falsos positivos y recall por cada iteración.
     *
     * @function _getMeasure
     * @private
     * @memberof! validation_module
     * 
     * @param {array} decilBoundaries - Array con los límites superiores e inferiores de cada decil
     * @param {array} scoresByGroup - Array con los scores por celda de las celdas con intersección entre el conjunto de prueba y la especie objetivo
     */
    function _getMeasure(decilBoundaries, scoresByGroup) {

        _VERBOSE ? console.log("getMeasure") : _VERBOSE;

        var iter_recall = [];

        for (i = 0; i < decilBoundaries.length; i++) {


            groupBoudaries = decilBoundaries[i];
            groupScores = scoresByGroup[i];
            _VERBOSE ? console.log(groupBoudaries) : _VERBOSE;
            _VERBOSE ? console.log(groupScores) : _VERBOSE;

            $.each(groupBoudaries, function(index, decil) {
                // _VERBOSE ? console.log("boundary: " + decil.group_name) : _VERBOSE;
                var vp = 0;
                var fn = 0;
                var recall = 0;
                var null_values = 0;
                var name = "";
                var recall_nulo = false;

                $.each(groupScores, function(index, gridid) {
                    // _VERBOSE ? console.log("groupScores: " + gridid.group_name) : _VERBOSE;
                    // la comparación null >= Number.NEGATIVE_INFINITY es verdadera, XD
                    if (gridid.score == null) {
                        // if(decil.linf == Number.NEGATIVE_INFINITY && gridid.score == null){
                        null_values++;
                        // _VERBOSE ? console.log("valor nulo") : _VERBOSE;
                    }
                    else if (gridid.score >= decil.linf) {
                        vp++;
                    }
                    name = (gridid.group_name != "") ? gridid.group_name : name;
                });



                fn = groupScores.length - null_values - vp;
                if (fn == 0 && vp == 0) {
                    recall = 0;
                    recall_nulo = true;
                }
                else {
                    recall = vp / (vp + fn);
                    recall_nulo = false;
                }

                _VERBOSE ? console.log("boundary: " + decil.group_name + " groupScores: " + name + " decil: " + decil.decil + " lon: " + groupScores.length + " nulos: " + null_values + " vp: " + vp + " fn: " + fn + " recall: " + recall + " recall nulo: " + recall_nulo) : _VERBOSE;

                if (_nameMap.has(decil.group_name)) {

                    var ar_temp = _nameMap.get(decil.group_name).nulos;
                    ar_temp.push(null_values);

                    var arg_nulo = _nameMap.get(decil.group_name).recall_nulo;
                    arg_nulo.push(recall_nulo);
                    // nulo = (nulo == true) ? nulo : recall_nulo;
                    // _VERBOSE ? console.log("nulo: " + nulo) : _VERBOSE;

                    _nameMap.set(decil.group_name, {name: decil.group_name, nulos: ar_temp, recall_nulo: arg_nulo});

                }
                else {
                    _nameMap.set(decil.group_name, {name: decil.group_name, nulos: [null_values], recall_nulo: [recall_nulo]});
                }

                // los elementos no se agrupan por grupo de pre seleccionado, se genera una sola colecicón que depsues es procesada con Crossfilter
                // iter_recall.push({"group_name":decil.group_name, "recall": recall, "vp": vp, "fn": fn, "decil":decil.decil, "ausentes":null_values});
                iter_recall.push({"group_name": decil.group_name, "recall": recall, "vp": vp, "fn": fn, "decil": decil.decil, "recall_nulo": recall_nulo});

            });

        }

        // _VERBOSE ? console.log(iter_recall) : _VERBOSE;
        return iter_recall;

    }

    /**
     * Éste método llama a la función que inicializa las variables necesarias para realizar el proceso de validación.
     *
     * @function startValidationModule
     * @public
     * @memberof! validation_module
     * 
     */
    function startValidationModule() {
        _VERBOSE ? console.log("startValidationModule") : _VERBOSE;
        _initilizeElementsForValidationModule();
    }

    return{
        startValidationModule: startValidationModule,
        validationProcess: validationProcess,
        set_histogram_module: set_histogram_module
    }


});