"use strict";
// -*- mode: typescript; indent-tabs-mode: nil; js-basic-offset: 4 -*-
//
// This file is part of Genie
//
// Copyright 2020 The Board of Trustees of the Leland Stanford Junior University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Giovanni Campagna <gcampagn@cs.stanford.edu>
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const thingtalk_1 = require("thingtalk");
const I18n = __importStar(require("../../i18n"));
class GenieEntityRetriever extends thingtalk_1.Syntax.EntityRetriever {
    constructor(sentence, entities, options) {
        super(sentence, entities);
        this._locale = options.locale;
        this._langPack = I18n.get(this._locale);
        this._tokenizer = this._langPack.getTokenizer();
        this._allowNonConsecutive = options.allowNonConsecutive;
        this._useHeuristics = options.useHeuristics;
        this._alwaysAllowStrings = options.alwaysAllowStrings;
        this._ignoreSentence = options.ignoreSentence;
    }
    _sentenceContainsNonConsecutive(tokens) {
        // check that the sequence "sentence" contains the subsequence "tokens"
        // other tokens can be interspersed between the tokens of "tokens"
        // but the order cannot be changed
        //
        // this uses a greedy algorithm
        // the recurrence is:
        //  - for a suffix of sentence starting at index i
        //    - for a suffix of tokens starting at index j
        //      - if sentence[i] == tokens[j]
        //         - return recurse(i+1, j+1)
        //      - else
        //         - return recurse(i+1, j)
        const sentence = this.sentence;
        function recursiveHelper(i, j) {
            if (j === tokens.length) // no tokens left to match (all tokens matched)
                return true;
            if (i === sentence.length) // empty sentence suffix
                return false;
            if (sentence[i] === tokens[j])
                return recursiveHelper(i + 1, j + 1);
            else
                return recursiveHelper(i + 1, j);
        }
        return recursiveHelper(0, 0);
    }
    _findEntityFromSentence(entityType, entityString, ignoreNotFound) {
        // use the raw tokens, rather than the preprocessed tokens
        // the difference is NUMBER/TIME/etc are shown in numeric form
        // if those tokens are present in the entity name we have a bug
        // (they should not be in the parameter datasets) but if we expose
        // the neural network to out of order entities we'll have a
        // bigger problem
        // those tokens won't be found in the sentence anyway, so this matters
        // only if `alwaysAllowStrings` is set
        const entityTokens = this._tokenizer.tokenize(entityString).rawTokens;
        const found = this._allowNonConsecutive ?
            this._sentenceContainsNonConsecutive(entityTokens) :
            this._sentenceContains(entityTokens);
        if (found)
            return entityTokens;
        if (this._useHeuristics) {
            if (entityType === 'LOCATION') {
                // HACK to support paraphrasing
                // we're changing the location name here, slightly
                if (entityString.indexOf(',') >= 0) {
                    const entityNoComma = this._tokenizer.tokenize(entityString.replace(/,/g, '')).rawTokens;
                    if (this._sentenceContains(entityNoComma))
                        return entityNoComma;
                }
                if (entityString === 'los angeles , california' && this._sentenceContains(['los', 'angeles']))
                    return ['los', 'angeles'];
                if (entityString === 'palo alto , california' && this._sentenceContains(['palo', 'alto']))
                    return ['palo', 'alto'];
            }
            // "pluralize" the entity and try again
            const entityPlural = this._langPack.pluralize(entityTokens.join(' '));
            // note: if we find the plural form, we'll still predict the singular form!
            // this is used for certain cases of MultiWOZ where we need to predict normalized
            // strings or we fail to find results in database
            if (entityPlural && this._sentenceContains(entityPlural.split(' ')))
                return entityTokens;
        }
        if (this._ignoreSentence) {
            if (ignoreNotFound)
                return undefined; // check the entities in the bag first
            else
                return entityTokens;
        }
        // if we get here, we have not found the entity...
        // to accommodate certain MultiWOZ misannotations, we allow the neural network
        // to hallucinate entities entirely
        if (!ignoreNotFound && this._alwaysAllowStrings)
            return entityTokens;
        return undefined;
    }
}
exports.default = GenieEntityRetriever;
//# sourceMappingURL=entity-retriever.js.map