"use strict";
var fs = require('fs');
// var path = require('path');
var crypto = require('crypto');
const nlp = require("../wordServices/nlp");
const SentenceRule = require("./sentenceRule");

class PosDictionary {
    constructor() {

    }
}

module.exports = class TextAnalysisManagerBase {
    constructor(options, language, mode) {
        this.mode = mode;
        this.conflictMode = options.conflictMode != undefined ? options.conflictMode : 0;
        this.isProduction = false;
        this.language = language;

        // localRootFolder
        this.localRootFolder = options.prefixFolders && options.prefixFolders.local ? options.prefixFolders.local : "../";
        this.localRootFolder += language + "/";
        // rootFolder
        this.rootFolder = options.prefixFolders && options.prefixFolders.root ? options.prefixFolders.root : "./sentenceAnalysis/";
        this.rootFolder += language + "/";
        this.annotatorRules = [];
        this.phraseRules = [];
        this.lessonRules = [];
        this.errorRules = [];
        this.dictionaries = {};
        this.objectDictionnaries = {};
        this.functions = {};
        this.dicoConflictRules = {
            error: {},
            lesson: {},
            lessonError: {}
        };

        this.conflictRuleSentences = {};


        if (language == "fr") {
            this.posDictionaries = {};
            var files = ["words.csv", "missingWords.csv"];
            for (var fi = 0; fi < files.length; fi++) {
                var wordsPath = this.rootFolder + "misc/" + files[fi];
                var stream = fs.readFileSync(wordsPath, 'utf8').split('\n');
                for (var i = 1; i < stream.length; i++) {
                    var word = stream[i].split(",");
                    if (!word[1]) {
                        continue;
                    }
                    var dictionary = this.posDictionaries[word[1]];
                    if (!dictionary) {
                        dictionary = new PosDictionary();
                        this.posDictionaries[word[1]] = dictionary;
                    }
                    var object = {
                        lemma: word[2]
                    };
                    if (word[3]) {
                        object.gender = word[3];
                    }
                    if (word[4]) {
                        object.number = word[4];
                    }
                    // abaissées,adj,abaissé,f,p,,10
                    dictionary[word[0]] = object;
                }
            }
        }

        // if (language == "en") {
        //     var lines = fs.readFileSync(this.rootFolder + "dictionaries/adjectivesWithCompOrSup.csv", 'utf8').split(/(?:\r\n?|\n)/);
        //     this.adjectives = [];
        //     this.dicoAdjectives = {};
        //     for (var i = 1; i < lines.length; i++) {
        //         var line = lines[i];
        //         //var line = CleanLine(lines[i]);
        //         var words = line.split(",");
        //         if (words.length >= 6 && words[5] == "1") {
        //             if (words[0] != "" && words[1] != "" && words[3] != "") {
        //                 var adjective = {
        //                     lemma: words[0],
        //                     comparative: words[1],
        //                     //comparative2: "",
        //                     superlative: words[3]
        //                     //superlative2: ""
        //                 };

        //                 this.dicoAdjectives[adjective.lemma] = {
        //                     pos: 'JJ',
        //                     indice: this.adjectives.length
        //                 };
        //                 this.dicoAdjectives[adjective.comparative] = {
        //                     pos: 'JJR',
        //                     indice: this.adjectives.length
        //                 };
        //                 this.dicoAdjectives[adjective.superlative] = {
        //                     pos: 'JJS',
        //                     indice: this.adjectives.length
        //                 };
        //                 this.adjectives.push(adjective);
        //             };
        //         }
        //     }
        // }
    }
    static GetId(rules) {
        var id = crypto.randomBytes(16).toString("hex");
        while (rules[id] != undefined) {
            id = crypto.randomBytes(16).toString("hex");
        }
        return id;
    }

    ReadDictionaries() {
        var self = this;
        this.dictionaries = {};
        var folder = this.rootFolder + "dictionaries/";
        if (fs.existsSync(folder)) {
            fs.readdirSync(folder).forEach(file => {
                if (file.length > 4 && file.endsWith(".csv")) {
                    var name = file.substring(0, file.length - 4);
                    var lines = fs.readFileSync(folder + file).toString().split(/(?:\r\n?|\n)/);
                    if (lines.length > 1) {
                        var firstColumns = lines[0].split(",");

                        if (firstColumns.length >= 1) {
                            // if (firstColumns[0]!="key" && 
                            //     firstColumns[0]!="lemma")
                            // {
                            //     debugger;
                            // }
                            self.dictionaries[name] = {};
                            var dictionary = self.dictionaries[name];

                            if (firstColumns.length == 1) {
                                for (var wi = 1; wi < lines.length; wi++) {
                                    var word = lines[wi];
                                    if (word != "") {
                                        dictionary[word] = 1;
                                    }
                                }
                            }
                            else {
                                var atts = [];
                                for (var i = 0; i < firstColumns.length; i++) {
                                    atts.push(firstColumns[i]);
                                }
                                for (var wi = 1; wi < lines.length; wi++) {
                                    var columns = lines[wi].split(",");
                                    if (columns.length >= atts.length) {
                                        var key = columns[0];
                                        if (key != "") {
                                            var object = {};
                                            for (var ci = 0; ci < atts.length; ci++) {
                                                object[atts[ci]] = columns[ci];
                                            }
                                            dictionary[key] = object;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else {
                        console.error("dictionary is empty: " + name);
                    }
                }
                else if (file.length > 3 && file.endsWith(".js")) {
                    var name = file.substring(0, file.length - 3);
                    var dic = require(self.localRootFolder + "dictionaries/" + name);
                    self.objectDictionnaries[name] = new dic(this.rootFolder);
                }
            });
        }
    }
    ReadFunctions() {
        var self = this;
        // functions
        this.functions = {};
        var folder = this.rootFolder + "functions/";
        if (fs.existsSync(folder)) {
            fs.readdirSync(folder).forEach(file => {
                if (file.length > 3 && file.endsWith(".js")) {
                    var functionName = file.substring(0, file.length - 3);
                    self.functions[functionName] = require(self.localRootFolder + "functions/" + functionName);
                }
            });
        }
    }
    BeforeReadingRule(json, exceptions, rulesOnResults) {
        if (!json) {
            debugger;
            return;
        }

        if (json.exceptions) {
            for (var i = 0; i < json.exceptions.length; i++) {
                var id = json.exceptions[i].id;
                if (id && !exceptions[id]) {
                    var exception = SentenceRule.Instanciate(this, json.exceptions[i]);
                    exceptions[id] = exception;
                }
                else {
                    debugger;
                }
            }
        }
        if (json.rulesOnResults) {
            for (var i = 0; i < json.rulesOnResults.length; i++) {
                var id = json.rulesOnResults[i].id;
                if (id && !rulesOnResults[id]) {
                    var rulesOnResult = SentenceRule.Instanciate(this, json.rulesOnResults[i]);
                    rulesOnResults[id] = rulesOnResult;
                }
                else {
                    debugger;
                }
            }
        }
    }
    LoadFilteredRules(rules, json, ruleType, typeRules, category, ruleId) {
        var exceptions = {};
        var rulesOnResults = {};
        this.BeforeReadingRule(json, exceptions, rulesOnResults);
        var namedRules = json[ruleType];
        if (ruleId && ruleId != "") {
            for (var i = 0; i < namedRules.length; i++) {
                var jsonRule = namedRules[i];
                var rule = rules[jsonRule.id];
                if (jsonRule.id == ruleId) {
                    var newRule = SentenceRule.Instanciate(this, jsonRule, exceptions, rulesOnResults, rule);
                    if (newRule) {
                        typeRules.push(newRule);
                    }
                    break;
                }
            }
        }
        else if (category && category != "") {
            for (var i = 0; i < namedRules.length; i++) {
                var jsonRule = namedRules[i];
                var rule = rules[jsonRule.id];
                if (rule && rule.status != "disabled" && (!category || rule.category == category)) {
                    var newRule = SentenceRule.Instanciate(this, jsonRule, exceptions, rulesOnResults, rule);
                    if (newRule) {
                        typeRules.push(newRule);
                    }
                }
            }
        }
        else {
            for (var i = 0; i < namedRules.length; i++) {
                var jsonRule = namedRules[i];
                var rule = rules[jsonRule.id];
                if (rule && rule.status != "disabled") {
                    var newRule = SentenceRule.Instanciate(this, jsonRule, exceptions, rulesOnResults, rule);
                    if (newRule) {
                        typeRules.push(newRule);
                    }
                }
            }
        }
    }
    LoadRules(url, ruleType, rules) {
        var json = require(url);
        var exceptions = {};
        var rulesOnResults = {};
        this.BeforeReadingRule(json, exceptions, rulesOnResults);
        var typedRules = json[ruleType];
        if (typedRules) {
            for (var i = 0; i < typedRules.length; i++) {
                var rule = SentenceRule.Instanciate(this, typedRules[i], exceptions, rulesOnResults);
                if (rule) {
                    rules.push(rule);
                }
            }
        }
        else {
            debugger;
        }
    }
    // CommonAddModifyTokenFeatures(sentence) {
    // if (this.language == "en") {
    //     if (this.adjectives && this.dicoAdjectives) {
    //         for (var ti = 0; ti < sentence.tokens.length; ti++) {
    //             var token = sentence.tokens[ti];
    //             if (!token) {
    //                 debugger;
    //             }
    //             if (token.pos.startsWith("JJ")) {
    //                 var adj = this.dicoAdjectives[token.word];
    //                 if (adj) {
    //                     token.pos = adj.pos;
    //                     var adjective = this.adjectives[adj.indice];
    //                     token.lemma = adjective.lemma;
    //                     if (token.pos == 'JJ') {
    //                         token.comparative = adjective.comparative;
    //                         token.superlative = adjective.superlative;
    //                     }
    //                     else if (token.pos == 'JJR') {
    //                         token.superlative = adjective.superlative;
    //                     }
    //                     else if (token.pos == 'JJS') {
    //                         token.comparative = adjective.comparative;
    //                     }
    //                     else {
    //                         debugger;
    //                     }
    //                 }
    //             }
    //         }
    //     }
    // }
    // }
    SetTokenWordToLowerCase(sentence) {
        for (var ti = 0; ti < sentence.tokens.length; ti++) {
            var token = sentence.tokens[ti];
            var word = token.word.toLowerCase();
            if (word != token.word) {
                token.originalWord = token.word;
                token.word = word;
            }
            // if (token.lemma && token.lemma!="I")
            // {
            //     var lemma = token.lemma.toLowerCase();
            //     if (lemma != token.lemma) {
            //         token.originalLemma = token.lemma;
            //         token.lemma = lemma;
            //     }
            // }
        }
    }
    SetTokenWordToOriginalWord(sentence) {
        for (var ti = 0; ti < sentence.tokens.length; ti++) {
            var token = sentence.tokens[ti];
            if (token.originalWord) {
                token.word = token.originalWord;
                delete token.originalWord;
            }
            // if (token.originalLemma) {
            //     token.lemma = token.originalLemma;
            //     delete token.originalLemma;
            // }
        }
    }

    AddModifyTokenFeatures(sentence, mode) {
        this.CommonAddModifyTokenFeatures(sentence);
        for (var ti = 0; ti < sentence.tokens.length; ti++) {
            sentence.tokens[ti].conditionResults = {};
        }
        if (mode & 1 || mode & 2) {
            for (var i = 0; i < this.annotatorRules.length; i++) {
                var annotatorRule = this.annotatorRules[i];
                annotatorRule.match(this, sentence, "annotators");
            }
        }
        if (mode & 2) {
            for (var i = 0; i < this.phraseRules.length; i++) {
                var phraseRule = this.phraseRules[i];
                phraseRule.match(this, sentence, "phrases");
            }
        }
    }
    RecCombineErrorResults(usedResults, errorResults, currentErrorResult, otherErrorResults, toMergeResults) {
        if (!otherErrorResults) {
            return;
        }
        // if (!currentErrorResult.otherErrorResults)
        // {
        //     currentErrorResult.otherErrorResults = [];
        // }
        for (var ri = 0; ri < otherErrorResults.length; ri++) {
            var otherErrorResultsIndice = otherErrorResults[ri];
            if (usedResults[otherErrorResultsIndice]) {
                continue;
            }
            usedResults[otherErrorResultsIndice] = true;
            // currentErrorResult.otherErrorResults.push(otherErrorResultsIndice);
            var otherErrorResult = errorResults[otherErrorResultsIndice];
            otherErrorResult.toRemove = 1;

            if (currentErrorResult.corrections &&
                currentErrorResult.corrections.suggestions &&
                otherErrorResult.corrections &&
                otherErrorResult.corrections.suggestions) {
                for (var oercsi = 0; oercsi < otherErrorResult.corrections.suggestions.length; oercsi++) {
                    var suggestion = otherErrorResult.corrections.suggestions[oercsi];
                    if (otherErrorResult.learningCard) {
                        suggestion.learningCard = otherErrorResult.learningCard;
                        suggestion.ruleTitle = otherErrorResult.rule.title;
                        suggestion.tokenIndices = otherErrorResult.tokenIndices;
                    }
                    currentErrorResult.corrections.suggestions.push(suggestion);
                }
            }
            else {
                debugger;
            }
            //
            this.RecCombineErrorResults(usedResults, errorResults, currentErrorResult, toMergeResults[otherErrorResultsIndice], toMergeResults);
        }
    }
    _Analyze(sentenceNLP, lessonResults, errorResults, options, mode) {
        if (sentenceNLP && sentenceNLP.tokens) {
            // lower case
            this.SetTokenWordToLowerCase(sentenceNLP);
            // annotators & phrases
            this.AddModifyTokenFeatures(sentenceNLP, 3);
            // lessons
            for (var i = 0; i < this.lessonRules.length; i++) {
                this.lessonRules[i].match(this, sentenceNLP, "lessons", lessonResults);
            }
            // errors
            for (var i = 0; i < this.errorRules.length; i++) {
                this.errorRules[i].match(this, sentenceNLP, "errors", errorResults);
            }
            // conflict management
            //if (mode & 1) {
                this.IdentifyConflicts("lesson", lessonResults, options);
            //}
            //if (mode & 2) {
                this.IdentifyConflicts("error", errorResults, options);
            //}
            this.IdentifyLessonErrorConflicts(lessonResults, errorResults, options);
            // new method with conflict
            this.ApplyPrioritiesNew(sentenceNLP, lessonResults, errorResults);
            //
            var priorityGroups = {};
            // old method with priorites
            this.ApplyPriorities(sentenceNLP, "lesson", lessonResults, priorityGroups, errorResults, options);
            this.ApplyPriorities(sentenceNLP, "error", errorResults, priorityGroups, lessonResults, options);
            // Merge Error
            //if (mode & 2) {
                this.MergeErrors(errorResults);
            //}
            // remove lower case
            this.SetTokenWordToOriginalWord(sentenceNLP);
        }
        else {
            debugger;
        }
        //console.log("_Analyze CalcultedTokenConditionCount="+CalcultedTokenConditionCount);
    }
    AnalyzeAnnotators(sentenceNLP) {
        if (sentenceNLP && sentenceNLP.tokens) {
            this.SetTokenWordToLowerCase(sentenceNLP);
            this.AddModifyTokenFeatures(sentenceNLP, 1);
            this.SetTokenWordToOriginalWord(sentenceNLP);
        }
        else {
            debugger;
        }
        //console.log("_Analyze CalcultedTokenConditionCount="+CalcultedTokenConditionCount);
    }
    AnalyzePhrases(sentenceNLP) {
        if (sentenceNLP && sentenceNLP.tokens) {
            this.SetTokenWordToLowerCase(sentenceNLP);
            this.AddModifyTokenFeatures(sentenceNLP, 2);
            this.SetTokenWordToOriginalWord(sentenceNLP);
        }
        else {
            debugger;
        }
        //console.log("_Analyze CalcultedTokenConditionCount="+CalcultedTokenConditionCount);
    }
    Analyze(sentenceNLP, lessonResults, errorResults, options) {
        this._Analyze(sentenceNLP, lessonResults, errorResults, options, 3);
    }
    IdentifyLearningCards(sentenceNLP, results, options) {
        var errorResults = [];
        this._Analyze(sentenceNLP, results, errorResults, options, 1);
    }
    CheckErrors(sentenceNLP, results, options) {
        var lessonResults = [];
        this._Analyze(sentenceNLP, lessonResults, results, options, 2);
    }
    AddRuleInfos(conflictType, rule, generate) {
        var conflictRules = this.dicoConflictRules[conflictType];
        if (!conflictRules) {
            debugger;
            return { rules: {} };
        }
        if (!rule || !rule.id) {
            debugger;
            return { rules: {} };
        }
        var newRule = conflictRules[rule.id];
        if (!newRule && generate) {
            newRule = {
                // title: rule.title,
                rules: {}
            };
            conflictRules[rule.id] = newRule;
        }
        return newRule;
    }
    UnstreamConflictRules1() {
        this.dicoConflictRules = {
            error: {},
            lesson: {},
            lessonError: {}
        };
        var priorities = {
        };
        for (var conflictType in this.dicoConflictRules) {
            //var conflictRules = this.dicoConflictRules[conflictType];
            var jsonConflictRules = [];
            try {
                jsonConflictRules = require(this.localRootFolder + conflictType + "ConflictRules");
            }
            catch (e) {
                debugger;
                jsonConflictRules = [];
            }
            for (var cri = 0; cri < jsonConflictRules.length; cri++) {
                var conflictRule = jsonConflictRules[cri];
                if (!conflictRule.priority || !conflictRule.priority.noCondition) {
                    continue;
                }
                var rule1Id = conflictRule.rule1.id;
                var rule2Id = conflictRule.rule2.id;
                if (!priorities[rule1Id]) {
                    priorities[rule1Id] = {
                        // losers: {},
                        winners: {},
                        loserCount: 0,
                        winnerCount: 0,
                        depth: 0
                    }
                }
                if (!priorities[rule2Id]) {
                    priorities[rule2Id] = {
                        // losers: {},
                        winners: {},
                        loserCount: 0,
                        winnerCount: 0,
                        depth: 0
                    }
                }
                if (conflictRule.priority.noCondition == "rule1") {
                    // if (!priorities[rule1Id].losers[rule2Id])
                    // {
                    // priorities[rule1Id].losers[rule2Id] = true;
                    priorities[rule1Id].loserCount++;
                    // }
                    // else{
                    // debugger;
                    // }
                    if (!priorities[rule2Id].winners[rule1Id]) {
                        priorities[rule2Id].winners[rule1Id] = true;
                        priorities[rule2Id].winnerCount++;
                    }
                    else {
                        debugger;
                    }
                }
                else if (conflictRule.priority.noCondition == "rule2") {
                    // if (!priorities[rule2Id].losers[rule1Id])
                    // {
                    // priorities[rule2Id].losers[rule1Id] = true;
                    priorities[rule2Id].loserCount++;
                    // }
                    // else{
                    // debugger;
                    // }
                    if (!priorities[rule1Id].winners[rule2Id]) {
                        priorities[rule1Id].winners[rule2Id] = true;
                        priorities[rule1Id].winnerCount++;
                    }
                    else {
                        debugger;
                    }
                }
                else {
                    debugger;
                    continue;
                }
            }
        }

        var output = this.rootFolder + "conflictLoops.json";
        if (fs.existsSync(output)) {
            fs.unlinkSync(output);
        }
        var data = {
            ruleCount: 0,
            loopCount: 0,
            rules: {},
            loops: {},
            // output: output
        }
        var count = 0;
        for (var ruleId in priorities) {
            var rulePriorites = priorities[ruleId];
            if (rulePriorites.loserCount == 0) {
                console.log(count);
                count++;
                var used = [];
                used.push(ruleId);
                this.RecComputeConflictDepth(priorities, used, rulePriorites, 1, data);
                // debugger;
            }
        }
        fs.appendFileSync(output, JSON.stringify(data, null, 2));
        console.log(data.loopCount);
        debugger;
    }
    RecComputeConflictDepth(priorities, used, rulePriorites, depth, data) {
        for (var ruleId in rulePriorites.winners) {
            var indice = used.indexOf(ruleId);
            if (indice != -1) {
                var loop = [];
                for (var i = indice; i < used.length; i++) {
                    loop.push(used[i]);
                }
                loop.sort(function (a, b) {
                    if (a < b) {
                        return -1;
                    }
                    if (a > b) {
                        return 1;
                    }
                    return 0;
                })
                var key = "";
                for (var i = 0; i < loop.length; i++) {
                    key += loop[i];
                }
                if (!data.loops[key]) {

                    for (var i = 0; i < loop.length; i++) {
                        var lRuleId = loop[i];
                        if (data.rules[lRuleId]) {
                            data.rules[lRuleId]++;
                        }
                        else {
                            data.rules[lRuleId] = 1;
                            data.ruleCount++;
                        }
                    }
                    data.loops[key] = loop;
                    data.loopCount++;
                }

                // console.error(ruleId);
                // debugger;
            }
            else {
                used.push(ruleId);
                var otherRulePriorites = priorities[ruleId];
                if (otherRulePriorites.depth < depth) {
                    otherRulePriorites.depth = depth;
                }
                this.RecComputeConflictDepth(priorities, used, otherRulePriorites, depth + 1, data);
                used.splice(used.length - 1, 1);
            }
        }
    }
    GetRule1Rule2Key(rule1, rule2) {
        let key = "";
        if (rule1 < rule2) {
            key = rule1 + "_" + rule2;
        }
        else {
            key = rule2 + "_" + rule1;
        }
        return key;
    }
    UnstreamConflictRules() {

        // this.UnstreamConflictRules1();
        this.dicoConflictRules = {
            error: {},
            lesson: {},
            lessonError: {}
        };
        let ids = {};
        for (var conflictType in this.dicoConflictRules) {
            var conflictRules = this.dicoConflictRules[conflictType];
            var jsonConflictRules = [];
            try {
                jsonConflictRules = require(this.localRootFolder + conflictType + "ConflictRules");
            }
            catch (e) {
                debugger;
                jsonConflictRules = [];
            }
            for (var cri = 0; cri < jsonConflictRules.length; cri++) {
                var conflictRule = jsonConflictRules[cri];
                // let id = conflictRule.id;
                // if (!conflictRule.id)
                // {
                //     id = TextAnalysisManagerBase.GetId(ids);
                //     ids[id] = true;
                // }

                var rule1Id = conflictRule.rule1.id;
                var rule2Id = conflictRule.rule2.id;

                if (!conflictRules[rule1Id]) {
                    conflictRules[rule1Id] = {
                        rules: {}
                    };
                }
                if (!conflictRules[rule2Id]) {
                    conflictRules[rule2Id] = {
                        rules: {}
                    };
                }

                var proofread = conflictRule.proofread != undefined ? conflictRule.proofread : 0;
                var priority = null;
                if (conflictRule.priority) {
                    if (typeof conflictRule.priority == "object") {
                        if (conflictRule.priority.noCondition != "" ||
                            conflictRule.priority.rule1BeforeRule2 != "" ||
                            conflictRule.priority.rule2BeforeRule1 != "") {
                            priority = conflictRule.priority;
                        }
                    }
                    else {
                        priority = {
                            noCondition: conflictRule.priority,
                            rule1BeforeRule2: "",
                            rule2BeforeRule1: ""
                        };
                    }
                }
                conflictRules[rule1Id].rules[rule2Id] = { priority: priority, proofread: proofread };
                var otherPriority = priority;
                if (priority) {
                    for (var key in priority) {
                        var pr = priority[key];
                        if (pr != "" &&
                            pr != "rule1" &&
                            pr != "rule2") {
                            debugger;
                        }
                    }
                    otherPriority = {};
                    otherPriority.noCondition = priority.noCondition == "" ? "" : (priority.noCondition == "rule1" ? "rule2" : "rule1");
                    otherPriority.rule1BeforeRule2 = priority.rule2BeforeRule1 == "" ? "" : (priority.rule2BeforeRule1 == "rule1" ? "rule2" : "rule1");
                    otherPriority.rule2BeforeRule1 = priority.rule1BeforeRule2 == "" ? "" : (priority.rule1BeforeRule2 == "rule1" ? "rule2" : "rule1");
                }
                //else{
                //    debugger;
                //}
                conflictRules[rule2Id].rules[rule1Id] = { priority: otherPriority, proofread: proofread };

                if (conflictType == "error") {
                    var conflict = conflictRule.conflict != undefined ? conflictRule.conflict : 1;
                    conflictRules[rule1Id].rules[rule2Id].conflict = conflict;
                    conflictRules[rule2Id].rules[rule1Id].conflict = conflict;
                    if (conflictRule.corrections) {
                        conflictRules[rule1Id].rules[rule2Id].corrections = conflictRule.corrections;
                        conflictRules[rule2Id].rules[rule1Id].corrections = conflictRule.corrections;
                    }
                }
                if (conflictRule.sentences && !(this.conflictMode & 2)) {
                    var key = this.GetRule1Rule2Key(rule1Id, rule2Id);

                    this.conflictRuleSentences[key] = conflictRule.sentences;
                    key = rule2Id + "_" + rule1Id;
                    this.conflictRuleSentences[key] = conflictRule.sentences;
                }
            }
        }
    }
    StreamConflictRules() {
        if (!this.isProduction && this.mode == "allRules") {
            //console.log("StreamConflictRules START");
            var lessons = require(this.localRootFolder + "lessonRules");
            var errors = require(this.localRootFolder + "errorRules");
            for (var conflictType in this.dicoConflictRules) {
                var conflictRules = this.dicoConflictRules[conflictType];
                if (conflictRules) {
                    for (var id1 in conflictRules) {
                        var rule1 = conflictRules[id1];
                        var refRule = lessons[id1];
                        if (!refRule)
                            refRule = errors[id1];

                        if (rule1 && refRule) {
                            rule1.title = refRule.title;
                            if (conflictType == "lessonError") {
                                rule1.type = refRule.type;
                            }

                            for (var id2 in rule1.rules) {
                                var rule2 = rule1.rules[id2];
                                var refRule = lessons[id2];
                                if (!refRule)
                                    refRule = errors[id2];

                                if (rule2 && refRule) {
                                    rule2.title = refRule.title;
                                    if (conflictType == "lessonError") {
                                        rule2.type = refRule.type;
                                    }
                                }
                                else {
                                    console.error(conflictType + ": rule not found id= " + id2);
                                    if (rule1.rules[id2]) delete rule1.rules[id2];
                                    //debugger;
                                }
                            }
                        }
                        else {
                            console.error(conflictType + ": rule not found id= " + id1);
                            if (conflictRules[id1]) delete conflictRules[id1];
                        }
                    }
                    var keys = {};
                    var jsonConflictRules = [];
                    for (var id1 in conflictRules) {
                        var rule1 = conflictRules[id1];
                        // var errorRule = errorRules[id];
                        // if (rule && errorRule) {
                        // rule.title = errorRule.title;
                        // }
                        for (var id2 in rule1.rules) {
                            if (id1 < id2) {
                                // var key = id1 + "_" + id2;
                                var key = this.GetRule1Rule2Key(id1, id2);
                                if (!keys[key]) {
                                    var sentences = [];
                                    if (this.conflictRuleSentences[key]) {
                                        sentences = this.conflictRuleSentences[key];
                                    }
                                    let keepConflict = true;
                                    var rule2 = rule1.rules[id2];
                                    if (this.conflictMode & 1 &&
                                        sentences.length == 0 &&
                                        (rule2.proofread == 0 ||
                                        ((!rule2.priority||(
                                          !rule2.priority.noCondition && 
                                          !rule2.priority.rule1BeforeRule2 && 
                                          !rule2.priority.rule1BeforeRule2)) && 
                                           (conflictType != "error" ||
                                            rule2.conflict == 1
                                        ) )
                                        ))
                                    {
                                        keepConflict = false;
                                    }
                                    if (keepConflict) {
                                        
                                        var conflictRule = {
                                            key: key,
                                            rule1: {
                                                id: id1,
                                                title: rule1.title,
                                            },
                                            rule2: {
                                                id: id2,
                                                title: rule2.title,
                                            }
                                            // priority: rule2.priority
                                        };
                                        if (rule2.priority) {
                                            conflictRule.priority = rule2.priority;
                                        }
                                        else {
                                            conflictRule.priority = {
                                                noCondition: "",
                                                rule1BeforeRule2: "",
                                                rule2BeforeRule1: ""
                                            };
                                        }
                                        if (rule2.conflict != undefined) {
                                            conflictRule.conflict = rule2.conflict;
                                        }
                                        conflictRule.proofread = rule2.proofread != undefined ? rule2.proofread : 0;
                                        if (rule2.advice) {
                                            conflictRule.advice = rule2.advice;
                                        }
                                        if (rule2.corrections != undefined) {
                                            conflictRule.corrections = rule2.corrections;
                                        }
                                        else if (conflictType == "error") {
                                            conflictRule.corrections = "";
                                        }
                                        sentences.sort(function (s1, s2) {
                                            if (s1 < s2) {
                                                return -1;
                                            }
                                            if (s1 > s2) {
                                                return 1;
                                            }
                                            return 0;
                                        });
                                        conflictRule.sentences = sentences;
                                        if (conflictType == "lessonError") {
                                            conflictRule.rule1.type = rule1.type;
                                            conflictRule.rule2.type = rule2.type;
                                        }
                                        jsonConflictRules.push(conflictRule);
                                        keys[key] = true;
                                        keys[id2 + "_" + id1] = true;
                                    }
                                }
                            }
                        }
                    }
                    try {
                        jsonConflictRules.sort(function (c1, c2) {
                            if (c1.key < c2.key) {
                                return -1;
                            }
                            else if (c1.key > c2.key) {
                                return 1;
                            }
                            debugger;
                            return 0;
                        });
                        jsonConflictRules.forEach(function (conflictRule) {
                            if (conflictRule.key) delete conflictRule.key;
                        })
                        var path = this.rootFolder + conflictType + "ConflictRules.json";
                        if (fs.existsSync(path)) {
                            fs.unlinkSync(path);
                        }
                        fs.appendFileSync(path, JSON.stringify(jsonConflictRules, null, 2));
                    }
                    catch (e) {
                        debugger;
                    }
                }
            }
            //console.log("StreamConflictRules END"); 
        }
    }
    AddConflict(conflictType, results, tokenIndiceResults, ti, result, options) {
        if (tokenIndiceResults[ti] && tokenIndiceResults[ti].length) {
            var rule = this.AddRuleInfos(conflictType, result.rule, options.generateConflictRules);
            for (var tir = 0; tir < tokenIndiceResults[ti].length; tir++) {
                var otherRi = tokenIndiceResults[ti][tir];
                var otherResult = results[otherRi];
                var otherRule = this.AddRuleInfos(conflictType, results[otherRi].rule, options.generateConflictRules);
                var status = undefined;
                if (rule && otherRule) {
                    status = rule.rules[otherResult.rule.id];
                }
                // generate
                if (options.generateConflictRules) {
                    if (!status) {
                        status = {
                            priority: {
                                noCondition: "",
                                rule1BeforeRule2: "",
                                rule2BeforeRule1: "",
                            }
                        };
                        if (conflictType == "error") {
                            status.conflictRule = 1;
                        }
                        rule.rules[otherResult.rule.id] = status;
                    }
                    if (!otherRule.rules[result.rule.id]) {
                        otherRule.rules[result.rule.id] = {
                            conflict: conflictType == "lesson" ? 0 : 1,
                            priority: {
                                noCondition: "",
                                rule1BeforeRule2: "",
                                rule2BeforeRule1: "",
                            }
                        };
                    }
                    if (options.sentence) {

                        let key = this.GetRule1Rule2Key(result.rule.id, otherResult.rule.id);

                        if (!this.conflictRuleSentences[key]) {
                            this.conflictRuleSentences[key] = [];
                        }
                        if (this.conflictRuleSentences[key].indexOf(options.sentence) == -1) {
                            this.conflictRuleSentences[key].push(options.sentence);
                        }
                        key = otherResult.rule.id + "_" + result.rule.id;
                        if (!this.conflictRuleSentences[key]) {
                            this.conflictRuleSentences[key] = [];
                        }
                        if (this.conflictRuleSentences[key].indexOf(options.sentence) == -1) {
                            this.conflictRuleSentences[key].push(options.sentence);
                        }
                    }
                    else {
                        // debugger;
                    }
                }
                // end generate
            }
        }
    }
    IdentifyConflicts(conflictType, results, options) {
        var tokenIndiceResults = {};
        for (var ri = 0; ri < results.length; ri++) {
            var result = results[ri];
            result.id = ri;
            for (var j = 0; j < result.conflictTokenIndices.length; j++) {
                var ti = result.conflictTokenIndices[j];
                if (!tokenIndiceResults[ti]) {
                    tokenIndiceResults[ti] = [];
                }
                this.AddConflict(conflictType, results, tokenIndiceResults, ti, result, options);

                tokenIndiceResults[ti].push(ri);
            }
        }
    }
    ComputeToMergeErrorResults(errorResults, tokenIndiceResults, ti, result, toMergeResults) {
        if (tokenIndiceResults[ti] && tokenIndiceResults[ti].length) {
            var rule = this.AddRuleInfos("error", result.rule, 0);
            for (var tir = 0; tir < tokenIndiceResults[ti].length; tir++) {
                var otherRi = tokenIndiceResults[ti][tir];
                var otherResult = errorResults[otherRi];
                //var otherRule = this.AddRuleInfos(conflictType, results[otherRi].rule, 0);
                var status = undefined;
                if (rule) {
                    status = rule.rules[otherResult.rule.id];


                    if (status == undefined || status.conflict == 1) {
                        if (!result.conflicts) {
                            result.conflicts = [];
                        }
                        if (!otherResult.conflicts) {
                            otherResult.conflicts = [];
                        }
                        if (result.conflicts.indexOf(otherResult.id) == -1) {
                            result.conflicts.push(otherResult.id);
                        }
                        if (otherResult.conflicts.indexOf(result.id) == -1) {
                            otherResult.conflicts.push(result.id);
                        }
                    }
                    else if (status.conflict == 2) {
                        if (toMergeResults) {
                            if (!toMergeResults[result.id]) {
                                toMergeResults[result.id] = [];
                            }
                            if (toMergeResults[result.id].indexOf(otherResult.id) == -1) {
                                toMergeResults[result.id].push(otherResult.id);
                            }
                            if (!toMergeResults[otherResult.id]) {
                                toMergeResults[otherResult.id] = [];
                            }
                            if (toMergeResults[otherResult.id].indexOf(result.id) == -1) {
                                toMergeResults[otherResult.id].push(result.id);
                            }
                        }
                        else {
                            debugger;
                        }
                    }
                }
            }
        }
    }
    MergeErrors(errorResults) {
        var toMergeResults = {};

        var tokenIndiceResults = {};
        for (var ri = 0; ri < errorResults.length; ri++) {
            var result = errorResults[ri];
            result.id = ri;
            for (var j = 0; j < result.conflictTokenIndices.length; j++) {
                var ti = result.conflictTokenIndices[j];
                if (!tokenIndiceResults[ti]) {
                    tokenIndiceResults[ti] = [];
                }
                this.ComputeToMergeErrorResults(errorResults, tokenIndiceResults, ti, result, toMergeResults);
                tokenIndiceResults[ti].push(ri);
            }
        }
        // this.IdentifyConflicts("error", errorResults, options, toMergeResults);
        var usedResults = {};
        for (var ri in toMergeResults) {
            if (usedResults[ri]) {
                continue;
            }
            var otherResults = toMergeResults[ri];
            if (otherResults) {
                usedResults[ri] = true;
                this.RecCombineErrorResults(usedResults, errorResults, errorResults[ri], otherResults, toMergeResults);
            }
            else {
                debugger;
            }
        }
        for (var ri = errorResults.length - 1; ri >= 0; ri--) {
            if (errorResults[ri].toRemove) {
                errorResults.splice(ri, 1);
            }
        }
    }
    IdentifyLessonErrorConflicts(lessonResults, errorResults, options) {
        var tokenIndiceResults = {};
        for (var ri = 0; ri < lessonResults.length; ri++) {
            var lessonResult = lessonResults[ri];
            // result.id = ri;
            for (var j = 0; j < lessonResult.conflictTokenIndices.length; j++) {
                var ti = lessonResult.conflictTokenIndices[j];
                if (!tokenIndiceResults[ti]) {
                    tokenIndiceResults[ti] = [];
                }
                tokenIndiceResults[ti].push(ri);
            }
        }
        for (var ri = 0; ri < errorResults.length; ri++) {
            var errorResult = errorResults[ri];
            for (var j = 0; j < errorResult.conflictTokenIndices.length; j++) {
                var ti = errorResult.conflictTokenIndices[j];
                this.AddConflict("lessonError", lessonResults, tokenIndiceResults, ti, errorResult, options);
            }
        }
    }
    RemoveResults(sentenceNLP, ruleType, results, toRemoveLessonIndices) {
        if (results && toRemoveLessonIndices.length) {
            toRemoveLessonIndices.sort(function (a, b) {
                if (a < b)
                    return 1;
                if (a > b)
                    return -1;
                return 0;
            });
            for (var i = 0; i < toRemoveLessonIndices.length; i++) {
                var indice = toRemoveLessonIndices[i];
                if (indice >= 0 && indice < results.length) {

                    var result = results[indice];
                    var tokens = "";
                    for (var j = 0; j < result.tokenIndices.length; j++) {
                        if (j) {
                            tokens += " ";
                        }
                        tokens += sentenceNLP.tokens[result.tokenIndices[j]].word;
                    }

                    // console.log(ruleType + " Rule removed= " + result.rule.title + " -> " + tokens);
                    results.splice(indice, 1);
                }
                else {
                    debugger;
                }
            }
        }
    }
    _ApplyPriority(priority, ri1, ri2, toRemoveIndices1, toRemoveIndices2) {
        if (priority == "") {
            return;
        }
        if (priority == "rule1") {
            if (toRemoveIndices2.indexOf(ri2) == -1) {
                toRemoveIndices2.push(ri2);
            }
        }
        else if (priority == "rule2") {
            if (toRemoveIndices1.indexOf(ri1) == -1) {
                toRemoveIndices1.push(ri1);
            }
        }
        else {
            debugger;
        }
    }
    ApplyPriority(conflict, ri1, result1, ri2, result2, toRemoveIndices1, toRemoveIndices2) {
        if (conflict &&
            conflict.priority) {
            for (var ti = 0; ti < result1.conflictTokenIndices.length; ti++) {
                if (result2.conflictTokenIndices.indexOf(result1.conflictTokenIndices[ti]) != -1) {
                    if (conflict.priority.rule1BeforeRule2) {
                        if (result1.conflictTokenIndices.length &&
                            result2.conflictTokenIndices.length) {
                            if (result1.conflictTokenIndices[0] < result2.conflictTokenIndices[0]) {
                                this._ApplyPriority(conflict.priority.rule1BeforeRule2, ri1, ri2, toRemoveIndices1, toRemoveIndices2);
                                break;
                            }
                            else if (result1.conflictTokenIndices[0] == result2.conflictTokenIndices[0] &&
                                result1.conflictTokenIndices[result1.conflictTokenIndices.length - 1] < result2.conflictTokenIndices[result2.conflictTokenIndices.length - 1]) {
                                this._ApplyPriority(conflict.priority.rule1BeforeRule2, ri1, ri2, toRemoveIndices1, toRemoveIndices2);
                                break;
                            }
                        }
                        else {
                            debugger;
                        }
                    }
                    if (conflict.priority.rule2BeforeRule1) {
                        if (result1.conflictTokenIndices.length &&
                            result2.conflictTokenIndices.length) {
                            if (result2.conflictTokenIndices[0] < result1.conflictTokenIndices[0]) {
                                this._ApplyPriority(conflict.priority.rule2BeforeRule1, ri1, ri2, toRemoveIndices1, toRemoveIndices2);
                                break;
                            }
                            else if (result2.conflictTokenIndices[0] == result1.conflictTokenIndices[0] &&
                                result2.conflictTokenIndices[result2.conflictTokenIndices.length - 1] < result1.conflictTokenIndices[result1.conflictTokenIndices.length - 1]) {
                                this._ApplyPriority(conflict.priority.rule2BeforeRule1, ri1, ri2, toRemoveIndices1, toRemoveIndices2);
                                break;
                            }
                        }
                        else {
                            debugger;
                        }
                    }
                    this._ApplyPriority(conflict.priority.noCondition, ri1, ri2, toRemoveIndices1, toRemoveIndices2);

                    break;
                }
            }
        }
    }
    ApplyPrioritiesNew(sentenceNLP, lessonResults, errorResults) {
        var toRemoveLessonIndices = [];
        var toRemoveErrorIndices = [];
        for (var ri1 = 0; ri1 < lessonResults.length - 1; ri1++) {
            var lessonResult1 = lessonResults[ri1];
            var conflictRules = this.dicoConflictRules.lesson[lessonResult1.rule.id];
            if (conflictRules) {
                for (var ri2 = ri1 + 1; ri2 < lessonResults.length; ri2++) {
                    var lessonResult2 = lessonResults[ri2];
                    var conflict = conflictRules.rules[lessonResult2.rule.id];
                    this.ApplyPriority(conflict, ri1, lessonResult1, ri2, lessonResult2, toRemoveLessonIndices, toRemoveLessonIndices);
                }
            }
        }
        for (var ri1 = 0; ri1 < errorResults.length - 1; ri1++) {
            var errorResult1 = errorResults[ri1];
            var conflictRules = this.dicoConflictRules.error[errorResult1.rule.id];
            if (conflictRules) {
                for (var ri2 = ri1 + 1; ri2 < errorResults.length; ri2++) {
                    var errorResult2 = errorResults[ri2];
                    var conflict = conflictRules.rules[errorResult2.rule.id];
                    this.ApplyPriority(conflict, ri1, errorResult1, ri2, errorResult2, toRemoveErrorIndices, toRemoveErrorIndices);
                }
            }
        }
        for (var ri1 = 0; ri1 < lessonResults.length; ri1++) {
            var lessonResult = lessonResults[ri1];
            var conflictRules = this.dicoConflictRules.lessonError[lessonResult.rule.id];
            if (conflictRules) {
                for (var ri2 = 0; ri2 < errorResults.length; ri2++) {
                    var errorResult = errorResults[ri2];
                    var conflict = conflictRules.rules[errorResult.rule.id];
                    this.ApplyPriority(conflict, ri1, lessonResult, ri2, errorResult, toRemoveLessonIndices, toRemoveErrorIndices);
                }
            }
        }
        this.RemoveResults(sentenceNLP, "lesson", lessonResults, toRemoveLessonIndices);
        this.RemoveResults(sentenceNLP, "error", errorResults, toRemoveErrorIndices);
    }
    AddAdvice(conflictType, key, resultToRemove, otherResult) {
        var added = false;
        var conflictRules = this.dicoConflictRules[conflictType][resultToRemove.rule.id];
        if (conflictRules) {
            var conflict = conflictRules.rules[otherResult.rule.id];
            if (conflict) {
                if (conflict.advice &&
                    conflict.advice.priority != "rule2") {
                    debugger;
                }
                conflict.advice = {
                    priorityName: key,
                    priority: "rule2"
                };
            }
            else {
                return 0;
            }
        }
        else {
            return 0;
        }
        conflictRules = this.dicoConflictRules[conflictType][otherResult.rule.id];
        if (conflictRules) {
            var conflict = conflictRules.rules[resultToRemove.rule.id];
            if (conflict) {
                if (conflict.advice &&
                    conflict.advice.priority != "rule1") {
                    debugger;
                }
                conflict.advice = {
                    priorityName: key,
                    priority: "rule1"
                };
            }
        }
        else if (added) {
            debugger;
        }
        return 1;
    }
    LogResultToRemove(options, key, resultToRemove, otherResult) {
        if (options.toRemoveResultsFileName) {

            var adviceAdded = 0;
            if (resultToRemove.rule.type == "lesson") {
                if (resultToRemove.rule.type == otherResult.rule.type) {
                    adviceAdded = this.AddAdvice("lesson", key, resultToRemove, otherResult);
                }
                else {
                    adviceAdded = this.AddAdvice("lessonError", key, resultToRemove, otherResult);
                }
            }
            else if (resultToRemove.rule.type == "error") {
                if (resultToRemove.rule.type == otherResult.rule.type) {
                    adviceAdded = this.AddAdvice("error", key, resultToRemove, otherResult);
                }
                else {
                    adviceAdded = this.AddAdvice("lessonError", key, resultToRemove, otherResult);
                }
            }
            else {
                debugger;
            }
            var line = adviceAdded;
            line += ",";
            line += key;
            line += ",";
            line += resultToRemove.rule.type;
            // line += ",";
            // line += resultToRemove.rule.title;
            line += ",";
            line += resultToRemove.rule.id;
            line += ",";
            line += otherResult.rule.type;
            line += ",";
            line += otherResult.rule.id;
            line += ",";
            line += '"' + options.sentence + '"';
            line += "\n";
            fs.appendFileSync(options.toRemoveResultsFileName, line);
        }

    }
    ApplyPriorities(sentenceNLP, ruleType, results, priorityGroups, otherResults, options) {
        //return;
        var otherRuleType = ruleType == "lesson" ? "error" : "lesson";
        var toRemoveLessonIndices = [];
        var toRemoveErrorIndices = [];
        for (var ri = 0; ri < results.length; ri++) {
            var result = results[ri];
            if (result.priorityGroup) {
                var key = result.priorityGroup.name;
                if (result.mainTokenIndices.length) {

                    if (!priorityGroups[key]) {
                        priorityGroups[key] = {};
                    }

                    for (var mti = 0; mti < result.mainTokenIndices.length; mti++) {
                        var ti = result.mainTokenIndices[mti];
                        var tiPriorityGroup = priorityGroups[key][ti];
                        if (!tiPriorityGroup) {
                            priorityGroups[key][ti] = {
                                priority: result.priorityGroup.priority
                            };
                            priorityGroups[key][ti][ruleType] = [result];
                            priorityGroups[key][ti][otherRuleType] = [];
                        }
                        else {
                            if (tiPriorityGroup.priority > result.priorityGroup.priority) {
                                for (var tpgri = 0; tpgri < tiPriorityGroup[ruleType].length; tpgri++) {
                                    var resultToRemove = tiPriorityGroup[ruleType][tpgri];
                                    var indice = results.indexOf(resultToRemove);
                                    if (indice != -1) {
                                        if (toRemoveLessonIndices.indexOf(indice) == -1) {
                                            toRemoveLessonIndices.push(indice);
                                            debugger;
                                            this.LogResultToRemove(options, key, resultToRemove, result);
                                            // results.splice(indice, 1);
                                        }
                                    }
                                    else {
                                        debugger;
                                    }
                                }
                                if (otherResults) {
                                    for (var tpgri = 0; tpgri < tiPriorityGroup[otherRuleType].length; tpgri++) {
                                        var resultToRemove = tiPriorityGroup[otherRuleType][tpgri];
                                        var indice = otherResults.indexOf(resultToRemove);
                                        if (indice != -1) {
                                            if (toRemoveErrorIndices.indexOf(indice) == -1) {
                                                // console.log(indice);
                                                //otherResults.splice(indice, 1);
                                                toRemoveErrorIndices.push(indice);
                                                this.LogResultToRemove(options, key, resultToRemove, result);
                                            }
                                        }
                                        else {
                                            console.error(indice);
                                            debugger;
                                        }
                                    }
                                }
                                tiPriorityGroup.priority = result.priorityGroup.priority;
                                tiPriorityGroup[ruleType] = [result];
                                tiPriorityGroup[otherRuleType] = [];
                            }
                            else if (tiPriorityGroup.priority == result.priorityGroup.priority) {
                                tiPriorityGroup[ruleType].push(result);
                            }
                            else {
                                //status = 0;
                                if (toRemoveLessonIndices.indexOf(ri) == -1) {
                                    toRemoveLessonIndices.push(ri);
                                    debugger; //remind: remove old priorities
                                    if (options.toRemoveResultsFileName) {
                                        for (var tpgri = 0; tpgri < tiPriorityGroup[ruleType].length; tpgri++) {
                                            var otherResult = tiPriorityGroup[ruleType][tpgri];
                                            this.LogResultToRemove(options, key, result, otherResult);
                                        }
                                        for (var tpgri = 0; tpgri < tiPriorityGroup[otherRuleType].length; tpgri++) {
                                            var otherResult = tiPriorityGroup[otherRuleType][tpgri];
                                            this.LogResultToRemove(options, key, result, otherResult);
                                        }
                                    }
                                }
                                // else {
                                //     debugger;
                                // }
                            }
                        }
                    }
                }
                else {
                    console.error("You have forgetten to add mainToken");
                    debugger;
                }
            }
        }
        this.RemoveResults(sentenceNLP, ruleType, results, toRemoveLessonIndices);
        this.RemoveResults(sentenceNLP, otherRuleType, otherResults, toRemoveErrorIndices);
    }
    LogResult(sentence, results) {
        for (var i = 0; i < results.length; i++) {
            var result = results[i];
            if (result.learningCard && 
                result.learningCard.exerciceType == "complete-sentence")
            {
                if (result.tokenIndices.length == 0) {
                    console.error("no token for exercice");
                }
                else if (result.tokenIndices.length == 1) {
                    console.error("not enough token for exercice count=" + result.tokenIndices.length);
                }
            }
            var tokens = "";
            if (result.rule.type == "error") {
                tokens = "[ERROR] ";
            }
            else {
                tokens = "[IDENT] ";
            }
            if (result.rule.title) {
                tokens += result.rule.title + " : ";
            }
            else {
                debugger;
            }

            for (var j = 0; j < result.tokenIndices.length; j++) {
                if (j) {
                    tokens += " ";
                }
                tokens += sentence.tokens[result.tokenIndices[j]].word;
            }
            if (result.learningCard) {
                tokens += " (";
                if (result.learningCard.title) {
                    tokens += result.learningCard.title;
                }
                if (result.learningCard.lessonNumber) {
                    tokens += " " + result.learningCard.lessonNumber;
                }
                tokens += ")";
            }
            if (result.log) {
                tokens += " -> " + result.log;
            }
            console.log(tokens);
            if (result.rule.type == "error" &&
                result.corrections &&
                result.corrections.suggestions) {
                var used = {};
                used[result.rule.title] = true;
                var suggestions = result.corrections.suggestions;
                for (var si = 0; si < suggestions.length; si++) {
                    var suggestion = suggestions[si];
                    if (suggestion.ruleTitle &&
                        !used[suggestion.ruleTitle]) {
                        var line = "  -> [ERROR] ";
                        line += suggestion.ruleTitle;
                        //
                        line += " : "
                        if (suggestion.tokenIndices) {
                            for (var j = 0; j < suggestion.tokenIndices.length; j++) {
                                if (j) {
                                    line += " ";
                                }
                                line += sentence.tokens[suggestion.tokenIndices[j]].word;
                            }
                        }
                        //
                        line += " ("
                        if (suggestion.learningCard.title) {
                            line += suggestion.learningCard.title;
                        }
                        if (suggestion.learningCard.lessonNumber) {
                            line += " " + suggestion.learningCard.lessonNumber;
                        }
                        ;
                        line += ")";
                        console.log(line);
                    }
                }
            }
        }
    }

    CompareWithReferences(folder, sentence, localResults) {
        const hash = crypto.createHmac('sha256', sentence).digest('hex');
        var referenceUrl = folder + hash + ".txt";
        var resultString = JSON.stringify(localResults);
        if (fs.existsSync(referenceUrl)) {
            if (resultString == fs.readFileSync(referenceUrl).toString()) {
                //console.log("[TEST-OK] "+sentence);
            }
            else {
                console.log("[TEST-KO] " + sentence);
            }
        }
        else {
            fs.appendFileSync(referenceUrl, resultString);
        }
    }
    AddCorrection(result, suggestionIndice, modification) {
        if (!result) {
            console.error("AddCorrection result is null");
            debugger;
        }
        if (!modification) {
            console.error("AddCorrection modification is null");
            debugger;
        }
        if (!result.corrections) {
            result.corrections = {
                suggestions: [
                    {
                        corrections: []
                    }
                ]
            };
        }
        if (suggestionIndice >= result.corrections.suggestions.length) {
            var start = result.corrections.suggestions.length;
            for (var i = start; i <= suggestionIndice; i++) {
                result.corrections.suggestions.push({ corrections: [] });
            }
        }
        result.corrections.suggestions[suggestionIndice].corrections.push(modification);
    }
    AddError(result, suggestionIndice, modification) {
        if (!result) {
            console.error("AddError result is null");
            debugger;
        }
        if (!modification) {
            console.error("AddError modification is null");
            debugger;
        }
        if (!result.errors) {
            result.errors = {
                suggestions: [
                    {
                        modifications: []
                    }
                ]
            };
        }
        if (suggestionIndice >= result.errors.suggestions.length) {
            var start = result.errors.suggestions.length;
            for (var i = start; i <= suggestionIndice; i++) {
                result.errors.suggestions.push({ modifications: [] });
            }
        }
        result.errors.suggestions[suggestionIndice].modifications.push(modification);
    }
    AddOverload(result, overload) {
        if (result && overload) {
            if (!result.overloads) {
                result.overloads = [];
            }
            result.overloads.push(overload);
        }
        else {
            return 0;
        }
    }
    LoadPostTaggerRules(folderName, rules) {
        var folder = this.rootFolder + folderName + "/";
        var localFolder = this.localRootFolder + folderName + "/";
        var self = this;
        if (fs.existsSync(folder)) {
            fs.readdirSync(folder).forEach(file => {
                if (file.length > 5 && file.endsWith(".json")) {
                    var rulesName = file.substring(0, file.length - 5);
                    self.LoadRules(localFolder + rulesName, folderName, rules);
                }
            });
        }
    }
    LoadPhraseRules() {
        var folderName = "phraseRules";
        this.LoadPostTaggerRules(folderName, this.phraseRules);
        this.phraseRules.sort(function (a, b) {
            if (a.depth < b.depth) {
                return -1;
            }
            else if (a.depth > b.depth) {
                return 1;
            }
            return 0;
        });
    }
    LoadRule(ruleType, category, folder, rules, rule, jsons, result, ruleId) {
        if (!rule.status) {
            return;
        }
        var self = this;
        try {
            var json = require(folder + rule.file);
            jsons[rule.file] = true;

            self.LoadFilteredRules(rules, json, ruleType, result, category, ruleId);
        }
        catch (e) {
            // debugger;
            console.error("error reading rule file= " + rule.file);
        }
    }
    LoadRulesFromJson(json) {
        var exceptions = {};
        var rulesOnResults = {};
        this.BeforeReadingRule(json, exceptions, rulesOnResults);

        var ruleTypes = ["lessonRules", "errorRules"];
        for (var rti = 0; rti < ruleTypes.length; rti++) {
            var rules = json[ruleTypes[rti]];
            if (!rules) {
                continue;
            }
            for (var i = 0; i < rules.length; i++) {
                var jsonRule = rules[i];
                var newRule = SentenceRule.Instanciate(this, jsonRule, exceptions, rulesOnResults, null);
                if (newRule) {
                    if (jsonRule.type == "error") {
                        this.errorRules.push(newRule);
                    }
                    else {
                        this.lessonRules.push(newRule);
                    }
                }
            }
        }
    }
    GetRuleJsons(ruleType) {
        var ruleJsons = {};

        var rules = require(this.localRootFolder + ruleType);
        var folder = this.localRootFolder + ruleType + "/";
        var jsons = {};
        for (var key in rules) {
            var ruleInfo = rules[key];
            if (!jsons[ruleInfo.file]) {

                try {
                    var rootJson = require(folder + ruleInfo.file);
                    jsons[ruleInfo.file] = true;
                    var namedRules = rootJson[ruleType];
                    if (namedRules && namedRules.length) {
                        for (var i = 0; i < namedRules.length; i++) {
                            var jsonRule = namedRules[i];
                            if (jsonRule.id) {
                                if (ruleJsons[jsonRule.id]) {
                                    debugger;
                                }
                                ruleJsons[jsonRule.id] = jsonRule;
                            }
                        }
                    }
                }
                catch (e) {
                    debugger;
                    console.error("error reading rule file= " + rule.file);
                }
            }
        }
        return ruleJsons;
    }
    LoadAllRulesByType(ruleFile, ruleType, list) {
        var rules = require(this.localRootFolder + ruleFile);
        var folder = this.localRootFolder + ruleType + "/";
        var jsons = {};
        for (var key in rules) {
            var rule = rules[key];
            if (rule.status) {
                if (!jsons[rule.file]) {
                    this.LoadRule(ruleType, null, folder, rules, rule, jsons, list);
                }
            }
        }
    }
    LoadAllRules(ruleType, category, ruleId) {
        var self = this;
        var ruleLoaded = false;
        if ((ruleType == "lessonRules" || ruleType == "errorRules") && ruleId && ruleId != "") {
            var rules = require(this.localRootFolder + ruleType);
            var result = (ruleType == "lessonRules") ? self.lessonRules : self.errorRules;
            var folder = self.localRootFolder + ruleType + "/";
            var rule = rules[ruleId];
            var jsons = {};
            if (rule) {
                this.LoadRule(ruleType, null, folder, rules, rule, jsons, result, ruleId);
            }
            else {
                console.error("rule is not defined: " + ruleId);
                return;
            }
            ruleLoaded = true;
        }
        else if ((ruleType == "lessonRules" || ruleType == "errorRules") && category && category != "") {
            var rules = require(this.localRootFolder + ruleType);
            var result = (ruleType == "lessonRules") ? self.lessonRules : self.errorRules;
            var folder = self.localRootFolder + ruleType + "/";
            var jsons = {};
            for (var key in rules) {
                var rule = rules[key];
                if (rule.status && rule.category == category) {
                    if (!jsons[rule.file]) {
                        this.LoadRule(ruleType, category, folder, rules, rule, jsons, result);
                    }
                }
            }
            ruleLoaded = true;
        }
        else {
            if (ruleType === "allRules" || ruleType === "lessonRules") {
                // lesson rules
                this.LoadAllRulesByType("lessonRules", "lessonRules", this.lessonRules);
                ruleLoaded = true;
            }
            if (ruleType === "lessonRules") {
                try {
                    this.LoadAllRulesByType("errorRuleUsedByLessonRules", "errorRules", this.errorRules);
                }
                catch (e) {
                    console.error(e);
                }
            }
            if (ruleType === "allRules" || ruleType === "errorRules") {
                this.LoadAllRulesByType("errorRules", "errorRules", this.errorRules);
                ruleLoaded = true;
            }
            if (ruleType == "errorRules") {
                try {
                    this.LoadAllRulesByType("lessonRuleUsedByErrorRules", "lessonRules", this.lessonRules);
                }
                catch (e) {
                    console.error(e);
                }
            }
        }
        this.lessonRules.sort(function (a, b) {
            if (a.depth < b.depth) {
                return -1;
            }
            else if (a.depth > b.depth) {
                return 1;
            }
            return 0;
        });
        this.errorRules.sort(function (a, b) {
            if (a.depth < b.depth) {
                return -1;
            }
            else if (a.depth > b.depth) {
                return 1;
            }
            return 0;
        });
        if (ruleLoaded == false) {
            console.error("No rules loaded");
            debugger;
        }
    }
    CommonAddModifyTokenFeatures(sentence) {
        if (this.language == "en") {
            var dicoEn = this.dictionaries["american_last_names"];
            var dicoFr = this.dictionaries["french_last_names"];
            if (dicoEn && dicoFr) {
                for (var ti = 0; ti < sentence.tokens.length; ti++) {
                    var token = sentence.tokens[ti];
                    if (!token) {
                        debugger;
                    }
                    if ((token.pos == "NNP" || token.pos == "NNPS") && token.lemma.endsWith("s")) {
                        var subString = token.lemma.substring(0, token.lemma.length - 1);
                        if (dicoEn[subString] ||
                            dicoFr[subString]) {
                            token.lemma = subString;
                            token.pos = "NNPS";
                        }
                    }
                }
            }
            else {
                debugger;
            }
        }
        if (this.language == "fr") {
            for (var ti = 0; ti < sentence.tokens.length; ti++) {
                var token = sentence.tokens[ti];
                if (!token) {
                    debugger;
                }
                var olPos = nlp.GetOLPos(token.pos);
                if (olPos) {
                    var dictionaries = this.posDictionaries[olPos];
                    if (dictionaries) {
                        var object = dictionaries[token.word];
                        if (object) {
                            if (object.lemma) {
                                token.lemma = object.lemma;
                            }
                            else {
                                debugger;
                            }
                            if (object.gender) {
                                token.gender = object.gender;
                            }
                            if (object.number) {
                                token.number = object.number;
                            }
                        }
                        else {
                            //missing word
                            token.lemma = token.word;
                            if (!Number.isInteger(Number.parseInt(token.word))) {
                                debugger;
                                console.error("IS NOT INTEGER WORD= " + token.word);
                            }
                            else {
                                console.log("is integer word= " + token.word);
                            }
                        }
                    }
                }
            }
        }
    }
}
