# Source: validation_module.js

    /**
     * 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 cores 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
        }

    });