"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseRuleStore = void 0;
const auditory_description_js_1 = require("../audio/auditory_description.js");
const dynamic_cstr_js_1 = require("./dynamic_cstr.js");
const speech_rule_js_1 = require("./speech_rule.js");
const speech_rule_context_js_1 = require("./speech_rule_context.js");
class BaseRuleStore {
    static compareStaticConstraints_(cstr1, cstr2) {
        if (cstr1.length !== cstr2.length) {
            return false;
        }
        for (let i = 0, cstr; (cstr = cstr1[i]); i++) {
            if (cstr2.indexOf(cstr) === -1) {
                return false;
            }
        }
        return true;
    }
    static comparePreconditions_(rule1, rule2) {
        const prec1 = rule1.precondition;
        const prec2 = rule2.precondition;
        if (prec1.query !== prec2.query) {
            return false;
        }
        return BaseRuleStore.compareStaticConstraints_(prec1.constraints, prec2.constraints);
    }
    constructor() {
        this.context = new speech_rule_context_js_1.SpeechRuleContext();
        this.parseOrder = dynamic_cstr_js_1.DynamicCstr.DEFAULT_ORDER;
        this.parser = new dynamic_cstr_js_1.DynamicCstrParser(this.parseOrder);
        this.locale = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.LOCALE];
        this.modality = dynamic_cstr_js_1.DynamicCstr.DEFAULT_VALUES[dynamic_cstr_js_1.Axis.MODALITY];
        this.domain = '';
        this.initialized = false;
        this.inherits = null;
        this.kind = 'standard';
        this.customTranscriptions = {};
        this.preconditions = new Map();
        this.speechRules_ = [];
        this.rank = 0;
        this.parseMethods = {
            Rule: this.defineRule,
            Generator: this.generateRules,
            Action: this.defineAction,
            Precondition: this.definePrecondition,
            Ignore: this.ignoreRules
        };
    }
    defineRule(name, dynamic, action, prec, ...args) {
        const postc = this.parseAction(action);
        const fullPrec = this.parsePrecondition(prec, args);
        const dynamicCstr = this.parseCstr(dynamic);
        if (!(postc && fullPrec && dynamicCstr)) {
            console.error(`Rule Error: ${prec}, (${dynamic}): ${action}`);
            return null;
        }
        const rule = new speech_rule_js_1.SpeechRule(name, dynamicCstr, fullPrec, postc);
        rule.precondition.rank = this.rank++;
        this.addRule(rule);
        return rule;
    }
    addRule(rule) {
        rule.context = this.context;
        this.speechRules_.unshift(rule);
    }
    deleteRule(rule) {
        const index = this.speechRules_.indexOf(rule);
        if (index !== -1) {
            this.speechRules_.splice(index, 1);
        }
    }
    findRule(pred) {
        for (let i = 0, rule; (rule = this.speechRules_[i]); i++) {
            if (pred(rule)) {
                return rule;
            }
        }
        return null;
    }
    findAllRules(pred) {
        return this.speechRules_.filter(pred);
    }
    evaluateDefault(node) {
        const rest = node.textContent.slice(0);
        if (rest.match(/^\s+$/)) {
            return this.evaluateWhitespace(rest);
        }
        return this.evaluateString(rest);
    }
    evaluateWhitespace(_str) {
        return [];
    }
    evaluateCustom(str) {
        const trans = this.customTranscriptions[str];
        return trans !== undefined
            ? auditory_description_js_1.AuditoryDescription.create({ text: trans }, { adjust: true, translate: false })
            : null;
    }
    evaluateCharacter(str) {
        return (this.evaluateCustom(str) ||
            auditory_description_js_1.AuditoryDescription.create({ text: str }, { adjust: true, translate: true }));
    }
    removeDuplicates(rule) {
        for (let i = this.speechRules_.length - 1, oldRule; (oldRule = this.speechRules_[i]); i--) {
            if (oldRule !== rule &&
                rule.dynamicCstr.equal(oldRule.dynamicCstr) &&
                BaseRuleStore.comparePreconditions_(oldRule, rule)) {
                this.speechRules_.splice(i, 1);
            }
        }
    }
    getSpeechRules() {
        return this.speechRules_;
    }
    setSpeechRules(rules) {
        this.speechRules_ = rules;
    }
    getPreconditions() {
        return this.preconditions;
    }
    parseCstr(cstr) {
        try {
            return this.parser.parse(this.locale +
                '.' +
                this.modality +
                (this.domain ? '.' + this.domain : '') +
                '.' +
                cstr);
        }
        catch (err) {
            if (err.name === 'RuleError') {
                console.error('Rule Error ', `Illegal Dynamic Constraint: ${cstr}.`, err.message);
                return null;
            }
            else {
                throw err;
            }
        }
    }
    parsePrecondition(query, rest) {
        try {
            const queryCstr = this.parsePrecondition_(query);
            query = queryCstr[0];
            let restCstr = queryCstr.slice(1);
            for (const cstr of rest) {
                restCstr = restCstr.concat(this.parsePrecondition_(cstr));
            }
            return new speech_rule_js_1.Precondition(query, ...restCstr);
        }
        catch (err) {
            if (err.name === 'RuleError') {
                console.error('Rule Error ', `Illegal preconditions: ${query}, ${rest}.`, err.message);
                return null;
            }
            else {
                throw err;
            }
        }
    }
    parseAction(action) {
        try {
            return speech_rule_js_1.Action.fromString(action);
        }
        catch (err) {
            if (err.name === 'RuleError') {
                console.error('Rule Error ', `Illegal action: ${action}.`, err.message);
                return null;
            }
            else {
                throw err;
            }
        }
    }
    parse(ruleSet) {
        this.modality = ruleSet.modality || this.modality;
        this.locale = ruleSet.locale || this.locale;
        this.domain = ruleSet.domain || this.domain;
        this.context.parse(ruleSet.functions || []);
        if (ruleSet.kind !== 'actions') {
            this.kind = ruleSet.kind || this.kind;
            this.inheritRules();
        }
        this.parseRules(ruleSet.rules || []);
    }
    parseRules(rules) {
        for (let i = 0, rule; (rule = rules[i]); i++) {
            const type = rule[0];
            const method = this.parseMethods[type];
            if (type && method) {
                method.apply(this, rule.slice(1));
            }
        }
    }
    generateRules(generator) {
        const method = this.context.customGenerators.lookup(generator);
        if (method) {
            method(this);
        }
    }
    defineAction(name, action) {
        let postc;
        try {
            postc = speech_rule_js_1.Action.fromString(action);
        }
        catch (err) {
            if (err.name === 'RuleError') {
                console.error('Action Error ', action, err.message);
                return;
            }
            else {
                throw err;
            }
        }
        const prec = this.getFullPreconditions(name);
        if (!prec) {
            console.error(`Action Error: No precondition for action ${name}`);
            return;
        }
        this.ignoreRules(name);
        const regexp = new RegExp('^\\w+\\.\\w+\\.' + (this.domain ? '\\w+\\.' : ''));
        prec.conditions.forEach(([dynamic, prec]) => {
            const newDynamic = this.parseCstr(dynamic.toString().replace(regexp, ''));
            this.addRule(new speech_rule_js_1.SpeechRule(name, newDynamic, prec, postc));
        });
    }
    getFullPreconditions(name) {
        const prec = this.preconditions.get(name);
        if (prec || !this.inherits) {
            return prec;
        }
        return this.inherits.getFullPreconditions(name);
    }
    definePrecondition(name, dynamic, prec, ...args) {
        const fullPrec = this.parsePrecondition(prec, args);
        const dynamicCstr = this.parseCstr(dynamic);
        if (!(fullPrec && dynamicCstr)) {
            console.error(`Precondition Error: ${prec}, (${dynamic})`);
            return;
        }
        fullPrec.rank = this.rank++;
        this.preconditions.set(name, new Condition(dynamicCstr, fullPrec));
    }
    inheritRules() {
        if (!this.inherits || !this.inherits.getSpeechRules().length) {
            return;
        }
        const regexp = new RegExp('^\\w+\\.\\w+\\.' + (this.domain ? '\\w+\\.' : ''));
        this.inherits.getSpeechRules().forEach((rule) => {
            const newDynamic = this.parseCstr(rule.dynamicCstr.toString().replace(regexp, ''));
            this.addRule(new speech_rule_js_1.SpeechRule(rule.name, newDynamic, rule.precondition, rule.action));
        });
    }
    ignoreRules(name, ...cstrs) {
        let rules = this.findAllRules((r) => r.name === name);
        if (!cstrs.length) {
            rules.forEach(this.deleteRule.bind(this));
            return;
        }
        let rest = [];
        for (const cstr of cstrs) {
            const dynamic = this.parseCstr(cstr);
            for (const rule of rules) {
                if (dynamic.equal(rule.dynamicCstr)) {
                    this.deleteRule(rule);
                }
                else {
                    rest.push(rule);
                }
            }
            rules = rest;
            rest = [];
        }
    }
    parsePrecondition_(cstr) {
        const generator = this.context.customGenerators.lookup(cstr);
        return generator ? generator() : [cstr];
    }
}
exports.BaseRuleStore = BaseRuleStore;
class Condition {
    constructor(base, condition) {
        this.base = base;
        this._conditions = [];
        this.constraints = [];
        this.allCstr = {};
        this.constraints.push(base);
        this.addCondition(base, condition);
    }
    get conditions() {
        return this._conditions;
    }
    addConstraint(dynamic) {
        if (this.constraints.filter((cstr) => cstr.equal(dynamic)).length) {
            return;
        }
        this.constraints.push(dynamic);
        const newConds = [];
        for (const [dyn, pre] of this.conditions) {
            if (this.base.equal(dyn)) {
                newConds.push([dynamic, pre]);
            }
        }
        this._conditions = this._conditions.concat(newConds);
    }
    addBaseCondition(cond) {
        this.addCondition(this.base, cond);
    }
    addFullCondition(cond) {
        this.constraints.forEach((cstr) => this.addCondition(cstr, cond));
    }
    addCondition(dynamic, cond) {
        const condStr = dynamic.toString() + ' ' + cond.toString();
        if (this.allCstr.condStr) {
            return;
        }
        this.allCstr[condStr] = true;
        this._conditions.push([dynamic, cond]);
    }
}
