Source: Models/WhyAmISpecialCatalogFunction.js

'use strict';

/*global require*/
var CatalogFunction = require('./CatalogFunction');
var defined = require('terriajs-cesium/Source/Core/defined');
var defineProperties = require('terriajs-cesium/Source/Core/defineProperties');
// var extendLoad = require('./extendLoad');
var GeoJsonCatalogItem = require('./GeoJsonCatalogItem');
var inherit = require('../Core/inherit');
var invokeTerriaAnalyticsService = require('./invokeTerriaAnalyticsService');
var TerriaError = require('../Core/TerriaError');
var RegionDataParameter = require('./RegionDataParameter');
var RegionParameter = require('./RegionParameter');
var RegionTypeParameter = require('./RegionTypeParameter');
// var updateRectangleFromRegion = require('./updateRectangleFromRegion');
var when = require('terriajs-cesium/Source/ThirdParty/when');

/**
 * A Terria Spatial Inference function to determines the characteristics by which a particular region is _most different_
 * from all other regions.
 *
 * @alias WhyAmISpecialCatalogFunction
 * @constructor
 * @extends CatalogFunction
 *
 * @param {Terria} terria The Terria instance.
 */
var WhyAmISpecialCatalogFunction = function(terria) {
    CatalogFunction.call(this, terria);

    this.url = undefined;
    this.name = 'What makes this region unique or special?';
    this.description = 'Determines the characteristics by which a particular region is _most different_ from all other regions.';

    this._regionTypeParameter = new RegionTypeParameter({
        terria: this.terria,
        catalogFunction: this,
        id: 'regionType',
        name: 'Region Type',
        description: 'The type of region to analyze.'
    });

    this._regionParameter = new RegionParameter({
        terria: this.terria,
        catalogFunction: this,
        id: 'region',
        name: 'Region',
        description: 'The region to analyze.  The analysis will determine the characteristics by which this region is most different from all others.',
        regionProvider: this._regionTypeParameter
    });

    this._dataParameter = new RegionDataParameter({
        terria: this.terria,
        catalogFunction: this,
        id: 'data',
        name: 'Characteristics',
        description: 'The region characteristics to include in the analysis.',
        regionProvider: this._regionTypeParameter
    });

    this._parameters = [
        this._regionTypeParameter,
        this._regionParameter,
        this._dataParameter
    ];
};

inherit(CatalogFunction, WhyAmISpecialCatalogFunction);

defineProperties(WhyAmISpecialCatalogFunction.prototype, {
    /**
     * Gets the type of data member represented by this instance.
     * @memberOf WhyAmISpecialCatalogFunction.prototype
     * @type {String}
     */
    type : {
        get : function() {
            return 'why-am-i-special-function';
        }
    },

    /**
     * Gets a human-readable name for this type of data source, 'Why Am I Special?'.
     * @memberOf WhyAmISpecialCatalogFunction.prototype
     * @type {String}
     */
    typeName : {
        get : function() {
            return 'Why Am I Special?';
        }
    },

    /**
     * Gets the parameters used to {@link CatalogProcess#invoke} to this function.
     * @memberOf WhyAmISpecialCatalogFunction
     * @type {CatalogProcessParameters[]}
     */
    parameters : {
        get : function() {
            return this._parameters;
        }
    }
});

WhyAmISpecialCatalogFunction.prototype._load = function() {
};

/**
 * Invokes the function.
 * @return {AsyncProcessResultCatalogItem} The result of invoking this process.  Because the process typically proceeds asynchronously, the result is a temporary
 *         catalog item that resolves to the real one once the process finishes.
 */
WhyAmISpecialCatalogFunction.prototype.invoke = function() {
    var region = this._regionParameter.value;
    var data = this._dataParameter.getRegionDataValue();

    if (!defined(region)) {
        throw new TerriaError({
            title: 'Region not selected',
            message: 'You must select a Region.'
        });
    }

    var regionProvider = this._regionTypeParameter.value;

    var request = {
        algorithm: 'whyamispecial',
        boundaries_name: regionProvider.regionType,
        region_codes: data.regionCodes,
        columns: data.columnHeadings,
        table: data.table,
        parameters: {
            query: region.id
        }
    };

    var regionIndex = regionProvider.regions.indexOf(region);

    var regionName = region.id;
    if (regionIndex >= 0) {
        regionName = regionProvider.regionNames[regionIndex] || regionName;
    }

    var name = 'Why is ' + regionName + ' special?';

    var geoJsonPromise = regionProvider.getRegionFeature(this.terria, region);
    var invocationPromise = invokeTerriaAnalyticsService(this.terria, name, this.url, request);

    var that = this;
    return when.all([geoJsonPromise, invocationPromise]).then(function(allResults) {
        var geoJson = allResults[0];
        var invocationResult = allResults[1];

        var result = invocationResult.result;

        var catalogItem = new GeoJsonCatalogItem(that.terria);
        catalogItem.name = name;
        catalogItem.data = geoJson;
        catalogItem.dataUrl = invocationResult.url;

        var description = 'This is the result of invoking "' + that.name + '" at ' + invocationResult.startDate + ' with these parameters:\n\n';
        description += ' * ' + regionProvider.regionType + ' Region: ' + regionName + ' (' + region.id + ')\n';
        description += ' * Characteristics: ' + data.columnHeadings.join(', ') + '\n';

        catalogItem.description = description;

        var shortReport = '<p>These are the top characteristics that make ' + regionName + ' unique or special:</p>';

        for (var i = 0; i < 5 && i < result.columns.length; ++i) {
            var columnName = data.columnHeadings[result.columns[i]];
            var histogram = result.histograms[i];
            var binCounts = histogram.bin_counts;
            var binEdges = histogram.bin_edges;

            var chartData = [['Value', 'Count']];

            for (var j = 0; j < binCounts.length; ++j) {
                var leftEdge = binEdges[j];
                var rightEdge = binEdges[j + 1];
                if (!defined(rightEdge)) {
                    // Assume equally-spaced bins.
                    rightEdge = leftEdge + (binEdges[1] - binEdges[0]);
                }

                var center = (leftEdge + rightEdge) * 0.5;

                chartData.push([center, binCounts[j]]);
            }

            var percentile = result.percentiles[i];
            var percentileDescription = 'lower';
            if (percentile > 50) {
                percentile = 100 - percentile;
                percentileDescription = 'higher';
            }

            shortReport += '<collapsible title="' + columnName + '" open="' + (i === 0 ? 'true' : 'false') + '">\n';
            shortReport += '<p style="font-size: 0.8em">The value of ' + columnName + ' in ' + regionName + ' is ' + result.values[i] + '.</p>\n';
            shortReport += '<p style="font-size: 0.8em">Only ' + percentile.toFixed(1) + '% of regions have an equal or ' + percentileDescription + ' value.</p>\n';
            shortReport += '<chart title="Distribution of ' + columnName + ' across ' + regionProvider.regionType + ' regions" id="' + columnName + '" data=\'' + JSON.stringify(chartData) + '\' column-names="," y-column="1" styling="histogram" highlight-x="' + result.values[i] + '" hide-buttons="true"></chart>\n';
            shortReport += '</collapsible>\n';
        }

        catalogItem.shortReport = shortReport;
        catalogItem.isEnabled = true;
    });
};

module.exports = WhyAmISpecialCatalogFunction;