/// <reference path="../../Scripts/jquery.js" />
/// <reference path="../../Scripts/MadCapGlobal.js" />
/// <reference path="../../Scripts/MadCapUtilities.js" />
/// <reference path="../../Scripts/MadCapDom.js" />
/// <reference path="../../Scripts/MadCapXhr.js" />
/// <reference path="../../Scripts/MadCapFeedback.js" />
/// <reference path="MadCapHelpSystem.js" />
/// <reference path="MadCapParser.js" />

/*!
 * Copyright MadCap Software
 * http://www.madcapsoftware.com/
 * Unlicensed use is strictly prohibited
 *
 * v12.0.5991.25330
 */


(function () {
    MadCap.WebHelp = MadCap.CreateNamespace("WebHelp");

    MadCap.WebHelp.SearchPane = function (helpSystem, container) {
        this._Init = false;
        this._Container = container;
        this._HelpSystem = helpSystem;
        this._FeedbackController = null;
        this._Parser = null;
        this._Filters = null;
        this._Set = null;
        this._FilteredSet = null;
        this._Highlight = "";
        this._DownloadedSynonymXmlDocRootNode = null;
    };

    var SearchPane = MadCap.WebHelp.SearchPane;

    SearchPane.SearchDBs = new Array(); // Fix from being static/global.

    SearchPane.prototype.Init = function (OnCompleteFunc) {
        if (this._Init) {
            if (OnCompleteFunc) {
                OnCompleteFunc.call(this);
            }

            return;
        }

        var mSelf = this;

        if (this._HelpSystem.LiveHelpEnabled) {
            this._FeedbackController = MadCap.WebHelp.LoadFeedbackController(this._HelpSystem.LiveHelpServer);
            this._FeedbackController.Init(function () {
                if (mSelf._FeedbackController.FeedbackActive) {
                    mSelf._FeedbackController.GetSynonymsFile(mSelf._HelpSystem.LiveHelpOutputId, null, function (synonymsXmlDoc, onCompleteArgs) {
                        var xmlDoc = MadCap.Utilities.Xhr.LoadXmlString(synonymsXmlDoc);

                        if (xmlDoc != null)
                            mSelf._DownloadedSynonymXmlDocRootNode = xmlDoc.documentElement;

                        OnGetSynonymsComplete();
                    }, null);
                }
                else {
                    OnGetSynonymsComplete();
                }
            });
        }
        else {
            OnGetSynonymsComplete();
        }

        function OnGetSynonymsComplete() {
            if (!mSelf._HelpSystem.IsWebHelpPlus) {
                mSelf._HelpSystem.GetSearchDBs(OnGetSearchDBsComplete);
            }
            else {
                OnGetSearchDBsComplete(null);
            }
        }

        function OnGetSearchDBsComplete(searchDBs) {
            SearchPane.SearchDBs = searchDBs;

            mSelf._Filters = new Search.Filters(mSelf._HelpSystem);
            mSelf._Filters.Load(function () {
                mSelf._Init = true;

                if (OnCompleteFunc) {
                    OnCompleteFunc.call(mSelf);
                }
            });
        }
    };

    SearchPane.prototype.Search = function (searchQuery, options) {
        if (MadCap.String.IsNullOrEmpty(MadCap.String.Trim(searchQuery))) {
            return;
        }

        this._Container.addClass("loading");

        var deferred = $.Deferred();

        this.Init(function () {
            var results = {};
            var deferredSearches = [];

            if (options.searchContent) {
                var filterName = options.content ? options.content.filterName : null;

                var searchContent = !this._HelpSystem.IsWebHelpPlus ?
                    this.DoSearch(searchQuery, filterName) :
                    this.DoSearchWebHelpPlus(searchQuery, filterName);

                deferredSearches.push(searchContent.then(function (searchResults, includedTerms) {
                    results.content = searchResults;
                    results.includedTerms = includedTerms;
                }));
            }

            if (options.searchGlossary) {
                deferredSearches.push(this._HelpSystem.SearchGlossary(searchQuery).then(function (searchResults) {
                    results.glossary = searchResults;
                }));
            }

            if (options.searchCommunity) {
                if (this._FeedbackController && this._FeedbackController.PulseActive) {
                    var searchCommunity = this._FeedbackController.GetPulseSearchResults(this._HelpSystem.LiveHelpOutputId, searchQuery, options.community.pageSize, options.community.pageIndex);

                    deferredSearches.push(searchCommunity.then(function (searchResults) {
                        results.community = searchResults;
                    }));
                }
            }

            $.when.apply(this, deferredSearches).done($.proxy(function () {
                this._Container.removeClass("loading");

                deferred.resolve(results);
            }, this));
        });

        return deferred.promise();
    };

    SearchPane.prototype.DoSearch = function (searchQuery, filterName) {
        var deferred = $.Deferred();

        this._Parser = new Search.Parser(searchQuery);
        var root = null;

        try {
            root = this._Parser.ParseExpression();
        }
        catch (err) {
            alert("Ensure that the search string is properly formatted.");
            root = null;
        }

        if (!root) {
            return deferred.resolve();
        }

        var searchDB = SearchPane.SearchDBs[0];
        if (this._DownloadedSynonymXmlDocRootNode != null && searchDB.DownloadedSynonymFile == null) {
            searchDB.DownloadedSynonymFile = new Search.SynonymFile(this._DownloadedSynonymXmlDocRootNode, searchDB.Stemmer);
        }

        var mSelf = this;

        root.Evaluate(filterName, function (resultSet) {
            Search.LoadResultData(resultSet).then(function (resultSet, terms) {
                mSelf._Set = resultSet;

                if (mSelf._Set) {
                    mSelf._Set.sort(function (a, b) {
                        return b.Score - a.Score;
                    });
                }

                MadCap.Utilities.ClearRequireCache();

                deferred.resolve(mSelf._Set, terms);
            });
        });

        return deferred.promise();
    };

    SearchPane.prototype.DoSearchWebHelpPlus = function (searchQuery, filterName) {
        var self = this;
        var deferred = $.Deferred();

        function OnGetSearchResultsComplete(xmlDoc, args) {
            var resultSet = [];
            var results = xmlDoc.getElementsByTagName("Result");
            var resultsLength = results.length;

            var baseUrl = new MadCap.Utilities.Url(document.location.pathname);

            if (!self._HelpSystem.SearchUrl) {
                // tripane only
            if (!MadCap.String.EndsWith(document.location.pathname, "/")) // http://MyServer/MyHTML5/ vs. http://MyServer/MyHTML5/Default.htm
                baseUrl = baseUrl.ToFolder();
            baseUrl = baseUrl.CombinePath(self._HelpSystem.ContentFolder);
            }

            for (var i = 0; i < resultsLength; i++) {
                var resultNode = results[i];
                var rank = MadCap.Dom.GetAttributeInt(resultNode, "Rank", -1);
                var title = resultNode.getAttribute("Title");
                var link = resultNode.getAttribute("Link");
                var linkUrl = new MadCap.Utilities.Url(link).ToRelative(baseUrl);
                var abstractText = resultNode.getAttribute("AbstractText");

                if (MadCap.String.IsNullOrEmpty(title))
                    title = resultNode.getAttribute("Filename");

                var searchResult = new Search.SearchResult(rank, title, linkUrl.FullPath, unescape(abstractText));

                resultSet.push(searchResult);
            }

            //

            deferred.resolve(resultSet);
        }

        MadCap.Utilities.Xhr.CallWebService(self._HelpSystem.GetPath() + "Service/Service.asmx/GetSearchResults?SearchString=" + encodeURIComponent(searchQuery) + "&FilterName=" + encodeURIComponent(filterName), true, OnGetSearchResultsComplete, null);

        var searchTerms = searchQuery.split(" ");
        var firstStem = true;

        this._Highlight = "?Highlight=";

        for (var i = 0; i < searchTerms.length; i++) {
            if (!firstStem)
                this._Highlight += "||";
            else
                firstStem = false;

            this._Highlight += searchTerms[i];
        }

        return deferred.promise();
    };

    var Search = MadCap.CreateNamespace("WebHelp.Search");

    //
    //    Class SearchDB
    //

    MadCap.WebHelp.Search.SearchDB = function (helpSystem) {
        // Public properties

        this.RelevanceWeight = 0;
        this.TopicChunkMap = null;
        this.UrlChunkMap = null;
        this.StemChunkMap = null;
        this.PhraseChunkMap = null;
        this.HelpSystem = helpSystem;
        this.SearchType = null;
        this.NGramSize = 0;
        this.Stemmer = null;
        this.SynonymFile = null;
        this.DownloadedSynonymFile = null;
        this.LoadChunkCompleteFuncs = new MadCap.Utilities.Dictionary();
    };

    var SearchDB = Search.SearchDB;

    // changed to database object
    SearchDB.prototype.Load = function (dbObj, OnCompleteFunc) {
        var self = this;

        this.LoadStemmer(this.HelpSystem.LanguageName, function () {
            MadCap.Utilities.Xhr.Load(self.HelpSystem.GetPath() + "Data/Synonyms.xml", true, function (xmlDoc) {
                if (xmlDoc != null) {
                    self.SynonymFile = new Search.SynonymFile(xmlDoc.documentElement, self.Stemmer);
                }

                // changed to database object
                self._LoadSearchDB(dbObj, OnCompleteFunc);
            }, null, this);
        });
    };

    SearchDB.prototype.LoadStemmer = function (language, OnCompleteFunc) {
        var supportedLanguages = ["danish", "dutch", "english", "finnish", "french", "german", "hungarian", "italian", 
                                  "norwegian", "portuguese", "romanian", "russian", "spanish"];

        var stemmer = null;

        this.Stemmer = {
            stemWord: function (word) {
                if (word != null)
                    word = word.toLowerCase();
                if (stemmer != null)
                    word = stemmer.stemWord(word);
                return word;
            }
        }

        if (language != null && supportedLanguages.indexOf(language.toLowerCase()) != -1) {
            var scriptFile = "stemmer-" + language.toLowerCase() + ".amd.min.js";
            var scriptFolder = MadCap.Utilities.HasRuntimeFileType("SkinPreview") ?
                    "../WebHelp2/Scripts/Stemmers/" :
                    this.HelpSystem.GetPath() + "Resources/Scripts/";

            require([scriptFolder + scriptFile], function (module) {
                stemmer = new module[language + "Stemmer"];
                OnCompleteFunc();
            });
        }
        else {
            OnCompleteFunc();
        }
    };

    SearchDB.prototype.GetTermPhrases = function (term, isExactMatch, subset) {
        var self = this;
        var stemDic = new MadCap.Utilities.Dictionary();
        var startStem = this.Stemmer.stemWord(term);
        var topicIds = new MadCap.Utilities.Dictionary();

        stemDic.Add(startStem, true);

        if (!isExactMatch) {
            if (this.SynonymFile != null) {
                this.SynonymFile.AddSynonymStems(term, startStem, stemDic);
            }

            if (this.DownloadedSynonymFile != null) {
                this.DownloadedSynonymFile.AddSynonymStems(term, startStem, stemDic);
            }
        }

        var dbTopicMap = Object.create(null);

        var stems = [];

        stemDic.ForEach(function (key, value) {
            if (self.SearchType == "NGram") {
                for (var k = 0; k < key.length - self.NGramSize + 1; k++) {
                    var subText = key.substring(k, k + self.NGramSize);

                    stems.push(subText);
                }
            }
            else {
                stems.push(key);
            }
        });

        var deferredLookups = [];

        $.each(stems, function (index, stem) {
            deferredLookups.push(self.LoadStem(stem).then(function (stemObj) {
                for (var phrase in stemObj) {
                    if (!isExactMatch || phrase == term.toLowerCase()) {
                        var topics = stemObj[phrase];

                        if (subset)
                            topics = subset.Intersect(topics);

                        topicIds.Add(phrase, topics);
                    }
                }
            }));
        });

        var deferred = $.Deferred();

        $.when.apply(this, deferredLookups).done(function () {
            deferred.resolve(term, topicIds);
        });

        return deferred.promise();
    }

    SearchDB.prototype.LookupPhrase = function (text, isExactMatch, filterName) {
        var self = this;
        var deferred = $.Deferred();
        var terms = Search.SplitPhrase(text);
        var resultSet = null;
        var deferredLookups = [];
        var termPhraseMap = new MadCap.Utilities.Dictionary();
        
        if (text)
            text = text.trim();

        if (!text || MadCap.Utilities.StopWords.indexOf(text) > -1) {
            deferred.resolve(self, null);
            return deferred.promise();
        }

        var subset;
        if (filterName) {
            subset = []; // initialize as empty set
            var filterMap = this.HelpSystem.GetMasterHelpsystem().GetSearchFilters();

            if (filterMap) {
                var filterTopics = filterMap[filterName];

                if (filterTopics) {
                    var concepts = filterTopics.c;
                    var conceptArr = concepts.split(';');

                    var conceptTopics = this.HelpSystem.GetConcepts();
                    for (var i = 0; i < conceptArr.length; i++) {
                        var concept = conceptArr[i];
                        subset = subset.Union(conceptTopics[concept]);
                    }
                }
            }
        }

        // gets list of topic ids for each term's phrases
        for (var i = 0; i < terms.length; i++) {
            var term = terms[i];

            deferredLookups.push(this.GetTermPhrases(term, isExactMatch, subset).then(function (term, topicIds) {
                termPhraseMap.Add(term, topicIds);
            }));
        }

        $.when.apply(self, deferredLookups).done(function () {
            var intersection;

            // unions the topic ids of all the phrases per term
            termPhraseMap.ForEach(function (term, phrases) {
                var union = [];
                phrases.ForEach(function (phrase, topicIds) {
                    union = union.Union(topicIds);
                });

                // (1st intersection) calculates the intersection of topic ids of the previous term(s) and current term
                if (!intersection)
                    intersection = union;
                else
                    intersection = intersection.Intersect(union);
            });

            var termWordMap = Object.create(null);
            var deferredLookups = [];

            termPhraseMap.ForEach(function (term, phrases) {
                termWordMap[term] = Object.create(null);

                // (2nd intersection) gets individual topics from the word , from the list of intersected topics
                phrases.ForEach(function (phrase, topicIds) {
                    var phraseTopicIds = topicIds.Intersect(intersection);

                    // do deferred here and load phrases/calculate scores
                    // loop through topic ids
                    $.each(phraseTopicIds, function (index, topicId) {
                        deferredLookups.push(self.LoadPhrase(phrase, topicId).then(function (topicId, phraseObj) {
                            var topicPhraseObj = termWordMap[term][topicId];
                            if (phraseObj) {
                                if (!topicPhraseObj)
                                    termWordMap[term][topicId] = phraseObj;
                                else {
                                    topicPhraseObj.r = MadCap.Utilities.CombineRelevancy(topicPhraseObj.r, phraseObj.r);
                                    $.extend(true, topicPhraseObj.w, phraseObj.w);
                                }
                            }
                        }));
                    });
                });
            });

            // for terms in this search database
            var topicRelevancyMap = Object.create(null);
            var topicDataMap = Object.create(null);
            $.when.apply(this, deferredLookups).done(function () {
                $.each(intersection, function (i, topicId) {
                    var topicRelevancy = 0;

                    if (!isExactMatch) {
                        topicRelevancy = termWordMap[terms[0]][topicId].r;
                    }
                    else {
                        var wordMap = termWordMap[terms[0]][topicId].w;
                        for (var word in wordMap) {
                            var phraseRelevancy = wordMap[word];

                            for (var termIndex = 1; termIndex < terms.length; termIndex++) {
                                var wordIndex = parseInt(word);
                                var nextTerm = terms[termIndex];
                                var nextWordMap = termWordMap[nextTerm][topicId].w;

                                var nextWordRel = nextWordMap[wordIndex + termIndex];
                                if (nextWordRel) {
                                    phraseRelevancy = Math.max(phraseRelevancy, nextWordRel);
                                }
                                else {
                                    phraseRelevancy = 0;
                                    break;
                                }
                            }

                            topicRelevancy = MadCap.Utilities.CombineRelevancy(topicRelevancy, phraseRelevancy);
                        }
                    }

                    if (topicRelevancy > 0)
                        topicDataMap[topicId] = { r: topicRelevancy };
                });

                deferred.resolve(self, topicDataMap);
            });
        });

        return deferred.promise();
    }

    SearchDB.prototype.LoadTopics = function (topicDataMap) {
        var deferredTopicLookups = [];
        var data = topicDataMap.data;

        for (var topicId in data) {
            deferredTopicLookups.push(this.LoadTopic(topicId).then(function (topicId, topicData) {
                $.extend(data[topicId], topicData);
            }));
        }

        topicDataMap.count = deferredTopicLookups.length;

        var deferred = $.Deferred();

        $.when.apply(this, deferredTopicLookups).done(function () {
            deferred.resolve(topicDataMap);
        });

        return deferred.promise();
    }

    // Private functions

    // changed to database object
    SearchDB.prototype._LoadSearchDB = function (dbObj, OnCompleteFunc) {
        this.TopicChunkMap = dbObj["t"];
        this.UrlChunkMap = dbObj["u"];
        this.StemChunkMap = dbObj["s"];
        this.PhraseChunkMap = dbObj["p"];
        this.RelevanceWeight = dbObj["r"];
        this.SearchType = dbObj["st"];
        this.NGramSize = dbObj["n"];

        if (OnCompleteFunc)
            OnCompleteFunc();
    };

    SearchDB.prototype.LookupPhraseChunkId = function (phrase, topicId) {
        var mapLength = this.PhraseChunkMap.length;
        for (var i = 0; i < mapLength; i++) {
            var lookupString = this.PhraseChunkMap[i][0];
            var compareIndex = MadCap.String.Compare(phrase, lookupString);

            if (compareIndex == 0) {
                if (topicId < this.PhraseChunkMap[i][1])
                    return i - 1;
                else if (topicId == this.PhraseChunkMap[i][1])
                    return i;
            }
            else if (compareIndex == -1) {
                return i - 1;
            }
        }

        return mapLength - 1;
    }

    SearchDB.prototype.LoadTopic = function (topicId) {
        var deferred = $.Deferred();

        var chunkId = MadCap.Utilities.GetChunkId(this.TopicChunkMap, topicId, function (a, b) {
            if (a < b)
                return -1;
            else if (a == b)
                return 0;
            else
                return 1;
        });

        if (chunkId == -1)
            deferred.resolve();
        else {
            MadCap.Utilities.Require([this.HelpSystem.GetPath() + "Data/SearchTopic_Chunk" + chunkId], function (data) {
                deferred.resolve(topicId, data[topicId]);
            });
        }

        return deferred.promise();
    }

    SearchDB.prototype.LoadUrl = function (url) {
        var deferred = $.Deferred();

        var chunkId = MadCap.Utilities.GetChunkId(this.UrlChunkMap, url, function (a, b) {
            return MadCap.String.Compare(a, b);
        });

        if (chunkId == -1)
            deferred.resolve();
        else {
            MadCap.Utilities.Require([this.HelpSystem.GetPath() + "Data/SearchUrl_Chunk" + chunkId], function (data) {
                deferred.resolve(url, data[url]);
            });
        }

        return deferred.promise();
    }

    SearchDB.prototype.LoadTopicByUrl = function (url) {
        var self = this;

        return this.LoadUrl(url).then(function (url, topicID) {
            return self.LoadTopic(topicID);
        });
    }

    SearchDB.prototype.LoadStem = function (stem) {
        var self = this;
        var deferred = $.Deferred();

        var chunkIds = MadCap.Utilities.GetChunkIds(this.StemChunkMap, stem, function (a, b) {
            return MadCap.String.Compare(a, b);
        });

        if (chunkIds.length === 0)
            deferred.resolve();
        else {
            var deferreds = [];
            var result = Object.create(null);

            $.each(chunkIds, function(index, chunkId) {
                deferreds.push(self.LoadStemChunk(chunkId).then(function(data) {
                    $.extend(result, data[stem]);
                }));
            });

            $.when.apply(this, deferreds).done(function() {
                deferred.resolve(result);
            }, this);
        }

        return deferred.promise();
    }

    SearchDB.prototype.LoadStemChunk = function (chunkId) {
        var deferred = $.Deferred();

        MadCap.Utilities.Require([this.HelpSystem.GetPath() + "Data/SearchStem_Chunk" + chunkId], function (data) {
            deferred.resolve(data);
        });

        return deferred.promise();
    }

    SearchDB.prototype.LoadPhrase = function (phrase, topicId) {
        var deferred = $.Deferred();

        var chunkId = this.LookupPhraseChunkId(phrase, topicId);
        MadCap.Utilities.Require([this.HelpSystem.GetPath() + "Data/SearchPhrase_Chunk" + chunkId], function (data) {
            var phraseObj;
            if (data[phrase])
                phraseObj = data[phrase][topicId];

            deferred.resolve(topicId, phraseObj);
        });

        return deferred.promise();
    }

    //
    //    End class SearchDB
    //

    //
    //    Class SearchQuery
    //

    Search.SearchQuery = function (query, filter, pageIndex) {
        // Private 
        function AppendParams(query, params) {
            var hasParam = false;

            for (var i = 0; i < params.length; i++) {
                var name = params[i][0];
                var val = params[i][1];

                if (!MadCap.String.IsNullOrEmpty(val)) {
                    query += (hasParam ? "&" : "?") + name + "=" + encodeURIComponent(val);
                    hasParam = true;
                }
            }

            return query;
        }

        // Public 
        this.Query = query;
        this.Filter = filter;
        this.PageIndex = pageIndex;

        this.ToString = function () {
            return AppendParams(this.Query, [[Search.SearchQuery._filter, this.Filter], [Search.SearchQuery._pageIndex, this.PageIndex]]);
        };
    };

    Search.SearchQuery._filter = "f";
    Search.SearchQuery._pageIndex = "p";

    Search.SearchQuery.Parse = function (queryString) {
        var queryUrl = new MadCap.Utilities.Url(queryString);

        var searchQuery = queryUrl.PlainPath;
        if (!MadCap.String.IsNullOrEmpty(searchQuery))
            searchQuery = decodeURIComponent(searchQuery);

        var searchFilter = queryUrl.QueryMap.GetItem(Search.SearchQuery._filter);
        if (searchFilter)
            searchFilter = decodeURIComponent(searchFilter);

        var pageIndex = queryUrl.QueryMap.GetItem(Search.SearchQuery._pageIndex);
        if (pageIndex != null)
            pageIndex = parseInt(pageIndex);

        return new Search.SearchQuery(searchQuery, searchFilter, pageIndex);
    }

    //
    //    End class SearchQuery
    //

    //
    //    Class SearchResult
    //

    Search.SearchResult = function (score, title, link, abstractText) {
        // Public properties

        this.Score = score;
        this.Title = title;
        this.Link = link;
        this.AbstractText = abstractText
    };

    //
    //    End class SearchResult
    //

    //
    //    Class Filters
    //

    Search.Filters = function (helpSystem) {
        // Private member variables

        var _HelpSystem = helpSystem;

        // Public member functions

        this.Load = function (OnCompleteFunc) {
            _HelpSystem.LoadSearchFilters().then(function () {
                _HelpSystem.LoadAllConcepts(function () {
                    OnCompleteFunc();
                });
            }, null, null);
        };
    };

    //
    //    End class Filters
    //

    //
    //    Class SynonymFile
    //

    Search.SynonymFile = function (rootNode, stemmer) {
        // Public properties

        this.Stemmer = stemmer;
        this.WordToStem = new MadCap.Utilities.Dictionary(true);
        this.Directionals = new MadCap.Utilities.Dictionary(true);
        this.DirectionalStems = new MadCap.Utilities.Dictionary(true);
        this.Groups = new MadCap.Utilities.Dictionary(true);
        this.GroupStems = new MadCap.Utilities.Dictionary(true);
        this.GroupStemSources = new MadCap.Utilities.Dictionary(true);

        this.LoadSynonymFile(rootNode);
    };

    var SynonymFile = Search.SynonymFile;

    SynonymFile.prototype.LoadSynonymFile = function (rootNode) {
        var groups = MadCap.Dom.GetChildNodeByTagName(rootNode, "Groups", 0);
        var syns = MadCap.Dom.GetChildNodeByTagName(rootNode, "Directional", 0);

        if (syns != null) {
            var childNodesLength = syns.childNodes.length;

            for (var i = 0; i < childNodesLength; i++) {
                var child = syns.childNodes[i];

                if (child.nodeName == "DirectionalSynonym") {
                    var from = MadCap.Dom.GetAttribute(child, "From");
                    var to = MadCap.Dom.GetAttribute(child, "To");
                    var stem = MadCap.Dom.GetAttributeBool(child, "Stem", false);
                    var fromStem = MadCap.Dom.GetAttribute(child, "FromStem");
                    var toStem = MadCap.Dom.GetAttribute(child, "ToStem");

                    if (stem) {
                        if (fromStem == null) {
                            fromStem = this.Stemmer.stemWord(from);
                        }
                    }

                    if (toStem == null) {
                        toStem = this.Stemmer.stemWord(to);
                    }

                    if (from != null && to != null) {
                        if (stem) {
                            this.DirectionalStems.Add(fromStem, toStem);

                            this.WordToStem.Add(from, fromStem);
                            this.WordToStem.Add(to, toStem);
                        }
                        else {
                            this.Directionals.Add(from, toStem);

                            this.WordToStem.Add(to, toStem);
                        }
                    }
                }
            }
        }

        if (groups != null) {
            var childNodesLength = groups.childNodes.length;

            for (var i = 0; i < childNodesLength; i++) {
                var child = groups.childNodes[i];

                if (child.nodeName == "SynonymGroup") {
                    var words = new Array();
                    var stemmedWords = new Array();
                    var stem = MadCap.Dom.GetAttributeBool(child, "Stem", false);

                    var synGroupChildNodesLength = child.childNodes.length;

                    for (var j = 0; j < synGroupChildNodesLength; j++) {
                        var wordNode = child.childNodes[j];

                        if (wordNode.nodeType != 1) {
                            continue;
                        }

                        words.push(wordNode.firstChild.nodeValue);
                    }

                    for (var j = 0; j < synGroupChildNodesLength; j++) {
                        var wordNode = child.childNodes[j];

                        if (wordNode.nodeType != 1) {
                            continue;
                        }

                        var stemmed = MadCap.Dom.GetAttribute(wordNode, "Stem");

                        if (stemmed == null) {
                            stemmed = this.Stemmer.stemWord(wordNode.firstChild.nodeValue);
                        }

                        this.WordToStem.Add(wordNode.firstChild.nodeValue, stemmed);

                        stemmedWords.push(stemmed);
                    }


                    //

                    var wordsLength = words.length;

                    for (var j = 0; j < wordsLength; j++) {
                        var word = words[j];
                        var stemmedWord = stemmedWords[j];

                        for (var k = 0; k < wordsLength; k++) {
                            var word1 = words[k];

                            if (stem) {
                                var group = this.GroupStemSources.GetItem(word);

                                if (group == null) {
                                    group = new MadCap.Utilities.Dictionary();
                                    this.GroupStemSources.Add(word, group);
                                }

                                group.Add(word1, stemmedWord);
                            }
                            else {
                                var group = this.GroupStemSources.GetItem(word);

                                if (group == null) {
                                    group = new MadCap.Utilities.Dictionary();
                                    this.Groups.Add(word, group);
                                }

                                group.Add(word1, stemmedWord);
                            }
                        }
                    }

                    //

                    var stemmedWordsLength = stemmedWords.length;

                    for (var j = 0; j < stemmedWordsLength; j++) {
                        var stemmedWord = stemmedWords[j];

                        for (var k = 0; k < stemmedWordsLength; k++) {
                            var stemmedWord1 = stemmedWords[k];
                            var group = this.GroupStems.GetItem(stemmedWord);

                            if (group == null) {
                                group = new MadCap.Utilities.Dictionary();
                                this.GroupStems.Add(stemmedWord, group);
                            }

                            group.Add(stemmedWord1, stemmedWord);
                        }
                    }
                }
            }
        }
    };

    SynonymFile.prototype.AddSynonymStems = function (term, termStem, stems) {
        var synonym = this.Directionals.GetItem(term);

        if (synonym != null) {
            stems.AddUnique(synonym);
        }

        //

        synonym = this.DirectionalStems.GetItem(termStem);

        if (synonym != null) {
            stems.AddUnique(synonym);
        }

        var group = this.Groups.GetItem(term);

        if (group != null) {
            group.ForEach(function (key, value) {
                stems.AddUnique(key);

                return true;
            });
        }

        //

        group = this.GroupStems.GetItem(termStem);

        if (group != null) {
            group.ForEach(function (key, value) {
                stems.AddUnique(key);

                return true;
            });
        }
    };

    //
    //    End class SynonymFile
    //
})();
