Source: Models/WebProcessingServiceCatalogItem.js

'use strict';

/*global require*/
var CatalogItem = require('./CatalogItem');
var defined = require('terriajs-cesium/Source/Core/defined');
var defineProperties = require('terriajs-cesium/Source/Core/defineProperties');
var GeoJsonCatalogItem = require('./GeoJsonCatalogItem');
var inherit = require('../Core/inherit');
var knockout = require('terriajs-cesium/Source/ThirdParty/knockout');
var loadXML = require('../Core/loadXML');
var proxyCatalogItemUrl = require('./proxyCatalogItemUrl');
var ShortReportSection = require('./ShortReportSection');
var URI = require('urijs');
var when = require('terriajs-cesium/Source/ThirdParty/when');
var xml2json = require('../ThirdParty/xml2json');
var createCatalogMemberFromType = require('./createCatalogMemberFromType');

/**
 * A catalog item representing the result of invoking a web processing service (WPS).
 *
 * @alias WebProcessingServiceCatalogItem
 * @constructor
 * @extends CatalogItem
 *
 * @param {Terria} terria The Terria instance.
 */
function WebProcessingServiceCatalogItem(terria) {
    CatalogItem.call(this, terria);

    this._geoJsonItem = undefined;

    /**
     * Gets or sets the parameters to the WPS function.
     * All parameter names must be entered in lowercase in order to be consistent with references in TerrisJS code.
     * @type {FunctionParameter[]}
     */
    this.parameters = undefined;

    /**
     * Gets or sets the values of the parameters that were used to invoke this function.
     * @type {Object}
     */
    this.parameterValues = undefined;

    /**
     * Gets or sets the URL of the WPS completed response.  This property is ignored if
     * {@link WebProcessingServiceCatalogItem#wpsResponse} is defined.  This property is observable.
     * @type {String}
     */
    this.wpsResponseUrl = undefined;

    /**
     * Gets or sets the completed WPS response, as either XML or the result of passing the
     * XML through {@link xml2json}.
     * @type {Object|Document}
     */
    this.wpsResponse = undefined;

    knockout.track(this, ['wpsResponseUrl', 'wpsResponse']);
}

inherit(CatalogItem, WebProcessingServiceCatalogItem);

defineProperties(WebProcessingServiceCatalogItem.prototype, {
    /**
     * Gets the type of data member represented by this instance.
     * @memberOf WebProcessingServiceCatalogItem.prototype
     * @type {String}
     */
    type : {
        get : function() {
            return 'wps-result';
        }
    },

    /**
     * Gets a human-readable name for this type of data source, 'Web Processing Service Result'.
     * @memberOf WebProcessingServiceCatalogItem.prototype
     * @type {String}
     */
    typeName : {
        get : function() {
            return 'Web Processing Service Result';
        }
    },

    /**
     * Gets the data source associated with this catalog item.
     * @memberOf WebProcessingServiceCatalogItem.prototype
     * @type {DataSource}
     */
    dataSource : {
        get : function() {
            return defined(this._geoJsonItem) ? this._geoJsonItem.dataSource : undefined;
        }
    }
});

WebProcessingServiceCatalogItem.prototype._getValuesThatInfluenceLoad = function() {
    return [this.wpsResponseUrl, this.wpsResponse];
};

WebProcessingServiceCatalogItem.prototype._load = function() {
    if (defined(this._geoJsonItem)) {
        this._geoJsonItem._hide();
        this._geoJsonItem._disable();
        this._geoJsonItem = undefined;
    }

    var that = this;
    return getGeoJson(this).then(function(geojson) {
        var wpsResponsePromise;
        if (defined(that.wpsResponse)) {
            wpsResponsePromise = when(that.wpsResponse);
        } else {
            wpsResponsePromise = loadXML(proxyCatalogItemUrl(that, that.wpsResponseUrl, '1d'));
        }

        return wpsResponsePromise.then(function(xmlOrJson) {
            var json;
            if (xmlOrJson instanceof Document) {
                json = xml2json(xmlOrJson);
            } else {
                json = xmlOrJson;
            }

            var processOutputs = json.ProcessOutputs;

            if (!defined(processOutputs)) {
                return;
            }

            var outputs = processOutputs.Output;
            if (!defined(outputs)) {
                return;
            } else if (!Array.isArray(outputs)) {
                outputs = [outputs];
            }

            for (var i = 0; i < outputs.length; ++i) {
                if (!defined(outputs[i].Data)) {
                    continue;
                }

                // skip process contexts
                if (defined(outputs[i].Identifier) && outputs[i].Identifier === '.context') {
                    continue;
                }

                if (defined(outputs[i].Data.LiteralData)) {
                    that.shortReportSections.push(new ShortReportSection({
                        name: outputs[i].Title,
                        content: formatOutputValue(outputs[i].Title, outputs[i].Data.LiteralData)
                    }));
                } else if (defined(outputs[i].Data.ComplexData)) {
                    var content = outputs[i].Data.ComplexData;
                    if (outputs[i].Data.ComplexData.mimeType === 'text/csv') {
                        content = '<collapsible title="' + outputs[i].Title + '" open="' + (i === 0 ? 'true' : 'false') + '">';
                        content += '<chart can-download="true" hide-buttons="false" title="' + outputs[i].Title + '" data=\'' + outputs[i].Data.ComplexData + '\' styling="histogram"></chart>';
                        content += '</collapsible>';
                    } else if (outputs[i].Data.ComplexData.mimeType === 'application/vnd.terriajs.catalog-member+json') {
                        content = 'Chart ' + outputs[i].Title + ' generated.';
                        var jsonCatalogItem = JSON.parse(outputs[i].Data.ComplexData.text);
                        var catalogItem = createCatalogMemberFromType(jsonCatalogItem.type, that.terria);
                        catalogItem.updateFromJson(jsonCatalogItem);
                    }

                    that.shortReportSections.push(new ShortReportSection({
                        content: content
                    }));
                }
            }

            var inputsSection =
                '<table class="cesium-infoBox-defaultTable">' +
                (that.parameters || []).reduce(function(previousValue, parameter) {
                    return previousValue +
                        '<tr>' +
                            '<td style="vertical-align: middle">' + parameter.name + '</td>' +
                            '<td style="padding-left: 4px">' + parameter.formatValueAsString(that.parameterValues[parameter.id]) + '</td>' +
                        '</tr>';
                }, '') +
                '</table>';

            var outputsSection =
                '<table class="cesium-infoBox-defaultTable">' +
                (outputs || []).reduce(function(previousValue, output) {
                    if (!defined(output.Data) || (!defined(output.Data.LiteralData) && !defined(output.Data.ComplexData))) {
                        return previousValue;
                    }
                    var content = "";
                    if (defined(output.Data.LiteralData)) {
                        content = formatOutputValue(output.Title, output.Data.LiteralData);
                    } else if (defined(output.Data.ComplexData)) {
                        if (output.Data.ComplexData.mimeType === 'text/csv' ) {
                            content = '<chart can-download="true" hide-buttons="false" title="' + output.Title + '" data=\'' + output.Data.ComplexData + '\' styling: "feature-info">';
                        }
                        else if (output.Data.ComplexData.mimeType === 'application/vnd.terriajs.catalog-member+json') {
                            content = 'Chart ' + output.Title + ' generated.';
                        }
                        // Support other types of ComplexData here as it becomes necessary.
                    }

                    return previousValue +
                        '<tr>' +
                            '<td style="vertical-align: middle">' + output.Title + '</td>' +
                            '<td>' + content + '</td>' +
                        '</tr>';
                }, '') +
                '</table>';

            var section = that.findInfoSection('Inputs');
            if (!defined(section)) {
                that.info.push({
                    name: 'Inputs',
                    content: inputsSection
                });
            }

            if (!defined(that.featureInfoTemplate)) {
                that.featureInfoTemplate =
                    '#### Inputs\n\n' +
                    inputsSection + '\n\n' +
                    '#### Outputs\n\n' +
                    outputsSection;
            }

            if (defined(geojson)) {
                that._geoJsonItem = new GeoJsonCatalogItem(that.terria);
                that._geoJsonItem.name = that.name;
                that._geoJsonItem.data = geojson;
                return that._geoJsonItem.load().then(function() {
                    if (!defined(that.rectangle)) {
                        that.rectangle = that._geoJsonItem.rectangle;
                    }
                });
            }
        });
    });
};

WebProcessingServiceCatalogItem.prototype._enable = function() {
    if (defined(this._geoJsonItem)) {
        this._geoJsonItem._enable();
    }
};

WebProcessingServiceCatalogItem.prototype._disable = function() {
    if (defined(this._geoJsonItem)) {
        this._geoJsonItem._disable();
    }
};

WebProcessingServiceCatalogItem.prototype._show = function() {
    if (defined(this._geoJsonItem)) {
        this._geoJsonItem._show();
    }
};

WebProcessingServiceCatalogItem.prototype._hide = function() {
    if (defined(this._geoJsonItem)) {
        this._geoJsonItem._hide();
    }
};

function formatOutputValue(title, value) {
    if (!defined(value)) {
        return '';
    }

    var values = value.split(',');

    return values.reduce(function(previousValue, currentValue) {
        if (value.match(/[.\/](png|jpg|jpeg|gif|svg)/i)) {
            return previousValue + '<a href="' + currentValue + '"><img src="' + currentValue + '" alt="' + title + '" /></a>';
        } else if (currentValue.indexOf('http:') === 0 || currentValue.indexOf('https:') === 0) {
            var uri = new URI(currentValue);
            return previousValue + '<a href="' + currentValue + '">' + uri.filename() + '</a>';
        } else {
            return previousValue + currentValue;
        }
    }, '');
}

function getGeoJson(catalogItem) {
    if (!defined(catalogItem.parameters) || !defined(catalogItem.parameterValues)) {
        return when(undefined);
    }
    var features = [];

    for (var i=0; i<catalogItem.parameters.length; i++) {
        if (typeof catalogItem.parameters[i].getGeoJsonFeature === 'function') {
            var potentialFeature = catalogItem.parameters[i].getGeoJsonFeature(catalogItem.parameterValues[catalogItem.parameters[i].id]);
            if (defined(potentialFeature)) {
                if (potentialFeature instanceof Array) {
                    features = features.concat(potentialFeature);
                } else {
                    features.push(potentialFeature);
                }
            }
        }
    }
    return when.all(features).then(function(results) {
        var geojson = {
            "type":"FeatureCollection",
            "features": results,
            "totalFeatures": results.length
        };
        return geojson;
    });
}

module.exports = WebProcessingServiceCatalogItem;