layer/layerRec/filter.js

const shared = require('./shared.js')();

/**
 * @class Filter
 */
class Filter {
    // handles state, result caches, and notifications for data filters on feature classes

    /**
     * @param {Object} parent        the FC object that this Filter belongs to
     */
    constructor (parent) {
        this._parent = parent;

        this.clearAll();
    }

    /**
     * Indicates if any filters are active
     *
     * @method isActive
     * @returns {Boolean} indicates if any filters are active
     */
    isActive () {
        return !!(this._symbolSql || this._apiSql || this._gridSql);
    }

    /**
     * Returns a SQL WHERE condition that is combination of any symbol and api filter statements that are active
     *
     * @method getCombinedSql
     * @returns {String} combination of any active symbol and/or api filter statements
     */
    getCombinedSql () {
        // puts all active layer-based sql statements into a single statement, ANDed
        if (this._symbolSql && this._apiSql) {
            return `${this._symbolSql} AND ${this._apiSql}`;
        } else {
            return this._symbolSql || this._apiSql;
        }
    }

    /**
     * Returns a SQL WHERE condition that is combination of any symbol and api filter statements that are active
     * and also includes a condition that restricts against any active grid filter
     *
     * @method getSqlPlusGrid
     * @returns {String} combination of any active symbol, api, and grid filter statements
     */
    getSqlPlusGrid () {
        // puts all active sql statements into a single statement, ANDed
        const cSql = this.getCombinedSql();

        if (cSql && this._gridSql) {
            return `${cSql} AND ${this._gridSql}`;
        } else {
            return cSql || this._gridSql;
        }
    }

    /**
     * Tells what object ids are currently passing the layer-specific filters.
     *
     * @method getLayerFilterOIDs
     * @param {Extent} [extent] if provided, the result list will only include features intersecting the extent
     * @returns {Promise} resolves with array of valid OIDs that layer is filtering. resolves with undefined if there is no filters being used
     */
    getLayerFilterOIDs (extent) {
        return this._parent.getLayerFilterOIDs(extent);
    }

    /**
     * Helper method for raising filter events
     *
     * @method eventRaiser
     * @private
     * @param {String} filterType type of filter event being raised. Should be member of shared.filterType
     */
    eventRaiser (filterType) {
        const fcID = this._parent.fcID;
        this._parent._parent.raiseFilterEvent(fcID.layerId, fcID.layerIdx, filterType);
    }

    /**
     * Helper method generating IN SQL clauses against the OID field
     *
     * @method arrayToIn
     * @private
     * @param {Array} array an array of integers
     * @returns {String} a SQL IN clause that dictates the object id field must match a number in the input array
     */
    arrayToIn (array) {
        // TODO do we need empty array checks? caller should be smart enough to recognize prior to calling this
        return `${this._parent.oidField} IN (${array.join(',')})`
    }

    /**
     * Registers a new symbol filter clause and triggers filter change events.
     *
     * @method setSymbolSql
     * @param {String} whereClause clause defining the active filters on symbols. Use '' for no filter. Use '1=2' for everything filtered.
     */
    setSymbolSql (whereClause) {
        this._symbolSql = whereClause;

        // invalidate caches
        this._layerExtentOID = undefined;
        this._layerSqlOID = undefined;

        // tell the world
        this.eventRaiser(shared.filterType.SYMBOL);
    }

    /**
     * Registers a new grid filter clause and triggers filter change events.
     *
     * @method setGridSql
     * @param {String} whereClause clause defining the active filters on the grid. Use '' for no filter. Use '1=2' for everything filtered.
     */
    setGridSql (whereClause) {
        this._gridSql = whereClause;

        // tell the world
        this.eventRaiser(shared.filterType.GRID);
    }

    /**
     * Registers a new API filter clause and triggers filter change events.
     *
     * @method setApiSql
     * @param {String} whereClause clause defining the active filters from the API. Use '' for no filter. Use '1=2' for everything filtered.
     */
    setApiSql (whereClause) {
        this._apiSql = whereClause;

        // invalidate caches
        this._layerExtentOID = undefined;
        this._layerSqlOID = undefined;

        // tell the world
        this.eventRaiser(shared.filterType.API);
    }

    /**
     * Registers a new extent for cache tracking.
     *
     * @method setExtent
     * @param {Extent} extent the extent to filter against
     */
    setExtent (extent) {
        // NOTE while technically we can support other geometries (for server based layers)
        //      only extent works for file layers. for now, limit to extent.
        //      we can add fancier things later when we need them

        // if our extent is different than our last request, clear the cache
        // and update our tracker
        if (!shared.areExtentsSame(extent, this._extent)) {
            this._extent = extent;
            this._layerExtentOID = undefined;
        }
        
    }

    /**
     * Resets all internal filter settings to have no filter applied. Does not trigger filter change events.
     *
     * @method clearAll
     */
    clearAll () {
        this._symbolSql = ''; // holds any symbol-filter sql string. '' means no filter active
        this._apiSql = '';  // holds any api-defined sql string. '' means no filter active
        this._gridSql = ''; // holds any grid sql string. '' means no filter active
        this._extent = undefined; 
        this._layerSqlOID = undefined; // promise.  resolves with list of OIDs that pass any SQL filters that are active
        this._layerExtentOID = undefined; // promise.  resolves with list of OIDs that pass any SQL filters AND extent filters that are active
    }

}

module.exports = () => ({
    Filter
});