"use strict";
/**
 * Liza classification match (cmatch) handling
 *
 *  Copyright (C) 2010-2020 R-T Specialty, LLC.
 *
 *  This file is part of the Liza Data Collection Framework
 *
 *  Liza is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * TODO: This is code directly extracted from Client, modified to maintain
 * references to necessary objects.  It is coupled with far too many things,
 * and the code is a mess.  Getting this clean and well-tested is important,
 * as this is one of the core systems and is both complicated and complex.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cmatch = void 0;
/**
 * Handles classification matching
 */
var Cmatch = /** @class */ (function () {
    /**
     * Initialize match handler
     *
     * This relies on too many objects; see header.
     *
     * @param _classMatcher  - class/field matcher
     * @param _program       - active program
     * @param _client        - active client
     * @param _visibility    - determines show hide logic from class visibility
     * @param _fieldResetter - determines field values when reset
     */
    function Cmatch(_classMatcher, _program, _client, _visibility, _fieldResetter) {
        this._classMatcher = _classMatcher;
        this._program = _program;
        this._client = _client;
        this._visibility = _visibility;
        this._fieldResetter = _fieldResetter;
        /** Contains classification match data per field **/
        this._cmatch = {};
        /**
         * Fields that were hidden (including indexes) since the
         * last cmatch clear
         */
        this._cmatchHidden = {};
    }
    Cmatch.prototype._cmatchVisFromUi = function (field, all) {
        var step = this._client.getUi().getCurrentStep();
        if (!step) {
            return [];
        }
        var group = step.getElementGroup(field);
        if (!group) {
            return [];
        }
        var i = group.getCurrentIndexCount();
        var ret = [];
        while (i--) {
            ret.push(all);
        }
        return ret;
    };
    Cmatch.prototype.hookClassifier = function (data_validator) {
        var _this = this;
        // clear/initialize cmatches
        this._cmatch = {};
        var cmatchprot = false;
        // set classifier
        this._client
            .getQuote()
            .setClassifier(this._program.getClassifierKnownFields(), function (args) {
            return _this._program.classify(args);
        })
            .on('classify', function (classes) {
            if (cmatchprot === true) {
                _this._client.handleError(Error('cmatch recursion'));
            }
            cmatchprot = true;
            // handle field fixes
            data_validator
                .validate(undefined, classes)
                .catch(function (e) { return _this._client.handleError(e); });
            _this._classMatcher.match(classes, function (cmatch) {
                // it's important that we do this here so that everything
                // that uses the cmatch data will consistently benefit
                _this._postProcessCmatch(cmatch);
                // if we're not on a current step, defer
                if (!_this._client.getUi().getCurrentStep()) {
                    _this._cmatch = cmatch;
                    cmatchprot = false;
                    return;
                }
                _this.handleClassMatch(cmatch);
                cmatchprot = false;
            });
        });
    };
    Cmatch.prototype._postProcessCmatch = function (cmatch) {
        // for any matches that are scalars (they will have no indexes), loop
        // through each field and set the index to the value of 'all'
        for (var field in cmatch) {
            if (field === '__classes') {
                continue;
            }
            var cfield = cmatch[field];
            if (cfield.indexes.length === 0) {
                var data = this._client.getQuote().getDataByName(field);
                var i = data.length;
                // this will do nothing if there is no data found
                while (i--) {
                    cfield.indexes[i] = cfield.all;
                }
            }
        }
        return cmatch;
    };
    Cmatch.prototype._mergeCmatchHidden = function (name, indexes, hidden) {
        if (!this._cmatchHidden[name]) {
            this._cmatchHidden[name] = {};
        }
        var cindexes = this._cmatchHidden[name];
        for (var i in indexes) {
            if (hidden) {
                cindexes[indexes[i]] = i;
            }
            else {
                delete cindexes[indexes[i]];
            }
        }
        var some = false;
        for (var _ in cindexes) {
            some = true;
            break;
        }
        if (!some) {
            // v8 devs do not recomment delete as it progressively slows down
            // property access on the object
            this._cmatchHidden[name] = undefined;
        }
    };
    Cmatch.prototype.handleClassMatch = function (cmatch, force) {
        var _this = this;
        force = !!force;
        this._client.getUi().setCmatch(cmatch);
        var quote = this._client.getQuote();
        // oh dear god...(Demeter, specifically..)
        var cur_step = this._client.getUi().getCurrentStep();
        if (cur_step === null) {
            throw TypeError('Cannot handle class match on undefined step');
        }
        // Prepare to keep track of any fields that show/hide in this process and
        // update the bucket if necessary
        var shown = {};
        var hidden = {};
        var visq = {};
        for (var field in cmatch) {
            // if the match is still false, then we can rest assured
            // that nothing has changed (and skip the overhead)
            if (!force && cmatch[field] === false && this._cmatch[field] === false) {
                continue;
            }
            var show = [], hide = [], cfield = cmatch[field], cur = (this._cmatch[field] || {}).indexes || [];
            var vis = cfield.indexes;
            // this should really only ever be the case for __classes
            if (!vis) {
                continue;
            }
            // TODO: Figure out something better here.  This is currently
            // needed for hiding statics---they are registered as exclusive
            // fields (`fields', above), but aren't actually fields (they're
            // not in the bucket).  But we must show/hide them appropriately.
            if (vis.length === 0) {
                vis = this._cmatchVisFromUi(field, cfield.all);
            }
            // consider the number of indexes in the bucket first;
            // otherwise, we might try to operate on fields that don't
            // exist (bucket/class indexes not the same).  the check for
            // undefined in the first index is a workaround for the explicit
            // setting of the length property of the bucket value when
            // indexes are removed
            var curdata = quote.getDataByName(field), fieldn = curdata.length > 0 && curdata[0] !== undefined
                ? curdata.length
                : vis.length;
            for (var i = 0; i < fieldn; i++) {
                // current cmatch will contain only those
                // indexes that we will handle
                cmatch[field].indexes = cmatch[field].indexes.slice(0, fieldn);
                // do not record unchanged indexes as changed
                // (avoiding the event overhead)
                if (!force && vis[i] === cur[i]) {
                    continue;
                }
                (vis[i] ? show : hide).push(i);
            }
            this.markShowHide(field, visq, show, hide);
            var _a = this._getVisibilityUpdates(field, { show: show, hide: hide }), indexes_shown = _a.indexes_shown, indexes_hidden = _a.indexes_hidden;
            shown[field] = indexes_shown;
            hidden[field] = indexes_hidden;
        }
        // it's important to do this before showing/hiding fields, since
        // those might trigger events that check the current cmatches
        this._cmatch = cmatch;
        Object.keys(visq).forEach(function (field) {
            var field_vis = visq[field];
            Object.keys(field_vis).forEach(function (event_id) {
                var indexes = field_vis[event_id];
                _this._client.handleEvent(event_id, {
                    elementName: field,
                    indexes: indexes,
                });
            });
            _this._dapiTrigger(field);
        });
        // Queue the setting of data into a task instead of running immediately
        setTimeout(function (_) {
            _this._client.getQuote().setData(shown);
            _this._client.getQuote().setData(hidden);
        }, 0);
    };
    /**
     * Provide updated data for fields/indexes that have changed visibility
     *
     * @param field      - field name
     * @param visibility - indexes that have changed visibility
     *
     * @return updated bucket values for the field at various indexes
     */
    Cmatch.prototype._getVisibilityUpdates = function (field, visibility) {
        var show = visibility.show, hide = visibility.hide;
        var indexes_shown = [];
        var indexes_hidden = [];
        var class_data = this._client.getQuote().getLastClassify();
        /**
         * When we clear N/A fields, ensure that their value is reset when a new
         * index is added if they are hidden on a class match. This is considered
         * an initialization for the field at the new index.
         */
        if (this._program.hasKnownType(field) &&
            this._program.clearNaFields &&
            hide.length) {
            for (var _i = 0, hide_1 = hide; _i < hide_1.length; _i++) {
                var index = hide_1[_i];
                var is_new_index = this._cmatch[field] !== undefined &&
                    this._cmatch[field].indexes[index] === undefined;
                var na = this._program.hasNaField(field, class_data, index);
                if (!na || !is_new_index) {
                    continue;
                }
                indexes_hidden[index] = this._program.naFieldValue;
            }
        }
        /**
         * When we clear N/A fields, ensure that their default value is restored
         * if they become visible.
         */
        if (this._program.hasKnownType(field) &&
            this._program.clearNaFields &&
            show.length) {
            var default_value = this._client.program.defaults[field] || '';
            var current_value = this._client.getQuote().getDataByName(field);
            for (var _a = 0, show_1 = show; _a < show_1.length; _a++) {
                var index = show_1[_a];
                // Only update value on show when it has been reset previously
                if (current_value[index] !== this._program.naFieldValue) {
                    continue;
                }
                // No need to update if the bucket is already up to date
                if (current_value[index] === default_value) {
                    continue;
                }
                indexes_shown[index] = default_value;
            }
        }
        return { indexes_shown: indexes_shown, indexes_hidden: indexes_hidden };
    };
    /**
     * Mark fields to be shown/hidden
     *
     * This also updates the cached visibility of field FIELD.
     *
     * @param field - field name
     * @param visq  - field visibility queue
     * @param show  - indexes to show
     * @param hide  - indexes to hide
     */
    Cmatch.prototype.markShowHide = function (field, visq, show, hide) {
        if (!(show.length || hide.length)) {
            return visq;
        }
        var _a = visq, _b = field, _c = _a[_b], result = _c === void 0 ? {} : _c;
        if (show.length) {
            this._mergeCmatchHidden(field, show, false);
            result.show = show;
        }
        if (hide.length) {
            this._mergeCmatchHidden(field, hide, true);
            result.hide = hide;
        }
        visq[field] = result;
        return visq;
    };
    /**
     * Trigger DataApi event for field FIELD
     */
    Cmatch.prototype._dapiTrigger = function (field) {
        var _this = this;
        var current_step_id = this._client.nav.getCurrentStepId();
        this._client.getQuote().visitData(function (bucket) {
            _this._program.dapi(current_step_id, field, bucket, {}, _this._cmatch, null);
        });
    };
    /**
     * Facilitate detection of field visibility and reset those which are hidden
     */
    Cmatch.prototype.clearCmatchFields = function () {
        var _this = this;
        var step = this._client.getUi().getCurrentStep(), program = this._program;
        // don't bother if we're not yet on a step
        if (!step) {
            return;
        }
        var legend = Object.keys(program.whens).map(function (name) {
            return { name: name, cname: program.whens[name][0] };
        });
        this._visibility
            .getBlueprints(legend)
            .forEach(function (bp) {
            _this.markShowHide(bp.name, {}, bp.show, bp.hide);
        });
        var reset = {};
        for (var name_1 in program.whens) {
            var data = this._cmatchHidden[name_1];
            // if there is no data or we have been asked to retain this field's
            // value, then do not clear
            if (!data || program.cretain[name_1]) {
                continue;
            }
            // what state is the current data in?
            var cur = this._client.getQuote().getDataByName(name_1);
            // we could have done Array.join(',').split(','), but we're trying
            // to keep performance sane here
            var indexes = [];
            for (var i in data) {
                // we do *not* want to reset fields that have been removed
                if (cur[i] === undefined) {
                    break;
                }
                indexes.push(i);
            }
            reset[name_1] = indexes;
        }
        // batch reset (limit the number of times events are kicked off)
        var reset_data = this._fieldResetter.reset(reset);
        this._client.getQuote().setData(reset_data);
        // we've done our deed; reset it for the next time around
        this._cmatchHidden = {};
    };
    /**
     * Return filtered array of fields that have cmatch data
     *
     * This can be used to filter elements in a group
     * by all the fields with cmatch data
     */
    Cmatch.prototype.getCmatchFields = function (fields) {
        var _this = this;
        if (!this._cmatch) {
            return [];
        }
        return fields.filter(function (field) { return _this._cmatch[field] !== undefined; });
    };
    /**
     * Force handling of the most recent cmatch data
     *
     * This can be used to refresh the UI to ensure that it is consistent with
     * the cmatch data.
     */
    Cmatch.prototype.forceCmatchAction = function () {
        if (!this._cmatch) {
            return this;
        }
        this.handleClassMatch(this._cmatch, true);
        return this;
    };
    /**
     * Get matches from last classifier application
     *
     * TODO: Remove me; breaks encapsulation.  Intended for transition from
     * mammoth Client.
     */
    Cmatch.prototype.getMatches = function () {
        return this._cmatch;
    };
    return Cmatch;
}());
exports.Cmatch = Cmatch;
//# sourceMappingURL=data:application/json;base64, 