1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#ifndef LATINIME_DIC_NODE_H
18#define LATINIME_DIC_NODE_H
19
20#include "defines.h"
21#include "suggest/core/dicnode/dic_node_profiler.h"
22#include "suggest/core/dicnode/dic_node_release_listener.h"
23#include "suggest/core/dicnode/internal/dic_node_state.h"
24#include "suggest/core/dicnode/internal/dic_node_properties.h"
25#include "suggest/core/dictionary/digraph_utils.h"
26#include "utils/char_utils.h"
27
28#if DEBUG_DICT
29#define LOGI_SHOW_ADD_COST_PROP \
30        do { char charBuf[50]; \
31        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
32        AKLOGI("%20s, \"%c\", size = %03d, total = %03d, index(0) = %02d, dist = %.4f, %s,,", \
33                __FUNCTION__, getNodeCodePoint(), inputSize, getTotalInputIndex(), \
34                getInputIndex(0), getNormalizedCompoundDistance(), charBuf); } while (0)
35#define DUMP_WORD_AND_SCORE(header) \
36        do { char charBuf[50]; char prevWordCharBuf[50]; \
37        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
38        INTS_TO_CHARS(mDicNodeState.mDicNodeStatePrevWord.mPrevWord, \
39                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf, \
40                NELEMS(prevWordCharBuf)); \
41        AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %s, %d, %5f,", header, \
42                getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \
43                getNormalizedCompoundDistance(), getRawLength(), prevWordCharBuf, charBuf, \
44                getInputIndex(0), getNormalizedCompoundDistanceAfterFirstWord()); \
45        } while (0)
46#else
47#define LOGI_SHOW_ADD_COST_PROP
48#define DUMP_WORD_AND_SCORE(header)
49#endif
50
51namespace latinime {
52
53// This struct is purely a bucket to return values. No instances of this struct should be kept.
54struct DicNode_InputStateG {
55    DicNode_InputStateG()
56            : mNeedsToUpdateInputStateG(false), mPointerId(0), mInputIndex(0),
57              mPrevCodePoint(0), mTerminalDiffCost(0.0f), mRawLength(0.0f),
58              mDoubleLetterLevel(NOT_A_DOUBLE_LETTER) {}
59
60    bool mNeedsToUpdateInputStateG;
61    int mPointerId;
62    int16_t mInputIndex;
63    int mPrevCodePoint;
64    float mTerminalDiffCost;
65    float mRawLength;
66    DoubleLetterLevel mDoubleLetterLevel;
67};
68
69class DicNode {
70    // Caveat: We define Weighting as a friend class of DicNode to let Weighting change
71    // the distance of DicNode.
72    // Caution!!! In general, we avoid using the "friend" access modifier.
73    // This is an exception to explicitly hide DicNode::addCost() from all classes but Weighting.
74    friend class Weighting;
75
76 public:
77#if DEBUG_DICT
78    DicNodeProfiler mProfiler;
79#endif
80    //////////////////
81    // Memory utils //
82    //////////////////
83    AK_FORCE_INLINE static void managedDelete(DicNode *node) {
84        node->remove();
85    }
86    // end
87    /////////////////
88
89    AK_FORCE_INLINE DicNode()
90            :
91#if DEBUG_DICT
92              mProfiler(),
93#endif
94              mDicNodeProperties(), mDicNodeState(), mIsCachedForNextSuggestion(false),
95              mIsUsed(false), mReleaseListener(0) {}
96
97    DicNode(const DicNode &dicNode);
98    DicNode &operator=(const DicNode &dicNode);
99    virtual ~DicNode() {}
100
101    // Init for copy
102    void initByCopy(const DicNode *dicNode) {
103        mIsUsed = true;
104        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
105        mDicNodeProperties.init(&dicNode->mDicNodeProperties);
106        mDicNodeState.init(&dicNode->mDicNodeState);
107        PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
108    }
109
110    // Init for root with prevWordNodePos which is used for bigram
111    void initAsRoot(const int rootGroupPos, const int prevWordNodePos) {
112        mIsUsed = true;
113        mIsCachedForNextSuggestion = false;
114        mDicNodeProperties.init(
115                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
116                NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
117                true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */,
118                0 /* terminalDepth */);
119        mDicNodeState.init(prevWordNodePos);
120        PROF_NODE_RESET(mProfiler);
121    }
122
123    // Init for root with previous word
124    void initAsRootWithPreviousWord(DicNode *dicNode, const int rootGroupPos) {
125        mIsUsed = true;
126        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
127        mDicNodeProperties.init(
128                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
129                NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
130                true /* hasChildren */, false /* isBlacklistedOrNotAWord */,  0 /* depth */,
131                0 /* terminalDepth */);
132        // TODO: Move to dicNodeState?
133        mDicNodeState.mDicNodeStateOutput.init(); // reset for next word
134        mDicNodeState.mDicNodeStateInput.init(
135                &dicNode->mDicNodeState.mDicNodeStateInput, true /* resetTerminalDiffCost */);
136        mDicNodeState.mDicNodeStateScoring.init(
137                &dicNode->mDicNodeState.mDicNodeStateScoring);
138        mDicNodeState.mDicNodeStatePrevWord.init(
139                dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1,
140                dicNode->mDicNodeProperties.getProbability(),
141                dicNode->mDicNodeProperties.getPos(),
142                dicNode->mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
143                dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(),
144                dicNode->getOutputWordBuf(),
145                dicNode->mDicNodeProperties.getDepth(),
146                dicNode->mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex(),
147                mDicNodeState.mDicNodeStateInput.getInputIndex(0) /* lastInputIndex */);
148        PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
149    }
150
151    void initAsPassingChild(DicNode *parentNode) {
152        mIsUsed = true;
153        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
154        const int c = parentNode->getNodeTypedCodePoint();
155        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
156        mDicNodeState.init(&parentNode->mDicNodeState);
157        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
158    }
159
160    void initAsChild(const DicNode *const dicNode, const int pos, const int childrenPos,
161            const int probability, const bool isTerminal, const bool hasChildren,
162            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
163            const int *const mergedNodeCodePoints) {
164        mIsUsed = true;
165        uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
166        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
167        const uint16_t newLeavingDepth = static_cast<uint16_t>(
168                dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
169        mDicNodeProperties.init(pos, childrenPos, mergedNodeCodePoints[0], probability,
170                isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth, newLeavingDepth);
171        mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
172                mergedNodeCodePoints);
173        PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
174    }
175
176    AK_FORCE_INLINE void remove() {
177        mIsUsed = false;
178        if (mReleaseListener) {
179            mReleaseListener->onReleased(this);
180        }
181    }
182
183    bool isUsed() const {
184        return mIsUsed;
185    }
186
187    bool isRoot() const {
188        return getNodeCodePointCount() == 0;
189    }
190
191    bool hasChildren() const {
192        return mDicNodeProperties.hasChildren();
193    }
194
195    bool isLeavingNode() const {
196        ASSERT(getNodeCodePointCount() <= mDicNodeProperties.getLeavingDepth());
197        return getNodeCodePointCount() == mDicNodeProperties.getLeavingDepth();
198    }
199
200    AK_FORCE_INLINE bool isFirstLetter() const {
201        return getNodeCodePointCount() == 1;
202    }
203
204    bool isCached() const {
205        return mIsCachedForNextSuggestion;
206    }
207
208    void setCached() {
209        mIsCachedForNextSuggestion = true;
210    }
211
212    // Used to expand the node in DicNodeUtils
213    int getNodeTypedCodePoint() const {
214        return mDicNodeState.mDicNodeStateOutput.getCodePointAt(getNodeCodePointCount());
215    }
216
217    // Check if the current word and the previous word can be considered as a valid multiple word
218    // suggestion.
219    bool isValidMultipleWordSuggestion() const {
220        if (isBlacklistedOrNotAWord()) {
221            return false;
222        }
223        // Treat suggestion as invalid if the current and the previous word are single character
224        // words.
225        const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
226                - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
227        const int currentWordLen = getNodeCodePointCount();
228        return (prevWordLen != 1 || currentWordLen != 1);
229    }
230
231    bool isFirstCharUppercase() const {
232        const int c = getOutputWordBuf()[0];
233        return CharUtils::isAsciiUpper(c);
234    }
235
236    bool isFirstWord() const {
237        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_DICT_POS;
238    }
239
240    bool isCompletion(const int inputSize) const {
241        return mDicNodeState.mDicNodeStateInput.getInputIndex(0) >= inputSize;
242    }
243
244    bool canDoLookAheadCorrection(const int inputSize) const {
245        return mDicNodeState.mDicNodeStateInput.getInputIndex(0) < inputSize - 1;
246    }
247
248    // Used to get bigram probability in DicNodeUtils
249    int getPos() const {
250        return mDicNodeProperties.getPos();
251    }
252
253    // Used to get bigram probability in DicNodeUtils
254    int getPrevWordPos() const {
255        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
256    }
257
258    // Used in DicNodeUtils
259    int getChildrenPos() const {
260        return mDicNodeProperties.getChildrenPos();
261    }
262
263    int getProbability() const {
264        return mDicNodeProperties.getProbability();
265    }
266
267    AK_FORCE_INLINE bool isTerminalWordNode() const {
268        const bool isTerminalNodes = mDicNodeProperties.isTerminal();
269        const int currentNodeDepth = getNodeCodePointCount();
270        const int terminalNodeDepth = mDicNodeProperties.getLeavingDepth();
271        return isTerminalNodes && currentNodeDepth > 0 && currentNodeDepth == terminalNodeDepth;
272    }
273
274    bool shouldBeFilteredBySafetyNetForBigram() const {
275        const uint16_t currentDepth = getNodeCodePointCount();
276        const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
277                - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
278        return !(currentDepth > 0 && (currentDepth != 1 || prevWordLen != 1));
279    }
280
281    bool isTotalInputSizeExceedingLimit() const {
282        const int prevWordsLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
283        const int currentWordDepth = getNodeCodePointCount();
284        // TODO: 3 can be 2? Needs to be investigated.
285        // TODO: Have a const variable for 3 (or 2)
286        return prevWordsLen + currentWordDepth > MAX_WORD_LENGTH - 3;
287    }
288
289    // TODO: This may be defective. Needs to be revised.
290    bool truncateNode(const DicNode *const topNode, const int inputCommitPoint) {
291        const int prevWordLenOfTop = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
292        int newPrevWordStartIndex = inputCommitPoint;
293        int charCount = 0;
294        // Find new word start index
295        for (int i = 0; i < prevWordLenOfTop; ++i) {
296            const int c = mDicNodeState.mDicNodeStatePrevWord.getPrevWordCodePointAt(i);
297            // TODO: Check other separators.
298            if (c != KEYCODE_SPACE && c != KEYCODE_SINGLE_QUOTE) {
299                if (charCount == inputCommitPoint) {
300                    newPrevWordStartIndex = i;
301                    break;
302                }
303                ++charCount;
304            }
305        }
306        if (!mDicNodeState.mDicNodeStatePrevWord.startsWith(
307                &topNode->mDicNodeState.mDicNodeStatePrevWord, newPrevWordStartIndex - 1)) {
308            // Node mismatch.
309            return false;
310        }
311        mDicNodeState.mDicNodeStateInput.truncate(inputCommitPoint);
312        mDicNodeState.mDicNodeStatePrevWord.truncate(newPrevWordStartIndex);
313        return true;
314    }
315
316    void outputResult(int *dest) const {
317        const uint16_t prevWordLength = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
318        const uint16_t currentDepth = getNodeCodePointCount();
319        DicNodeUtils::appendTwoWords(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
320                   prevWordLength, getOutputWordBuf(), currentDepth, dest);
321        DUMP_WORD_AND_SCORE("OUTPUT");
322    }
323
324    // "Total" in this context (and other methods in this class) means the whole suggestion. When
325    // this represents a multi-word suggestion, the referenced PtNode (in mDicNodeState) is only
326    // the one that corresponds to the last word of the suggestion, and all the previous words
327    // are concatenated together in mPrevWord - which contains a space at the end.
328    int getTotalNodeSpaceCount() const {
329        if (isFirstWord()) return 0;
330        return CharUtils::getSpaceCount(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
331                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength());
332    }
333
334    int getSecondWordFirstInputIndex(const ProximityInfoState *const pInfoState) const {
335        const int inputIndex = mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex();
336        if (inputIndex == NOT_AN_INDEX) {
337            return NOT_AN_INDEX;
338        } else {
339            return pInfoState->getInputIndexOfSampledPoint(inputIndex);
340        }
341    }
342
343    bool hasMultipleWords() const {
344        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() > 0;
345    }
346
347    int getProximityCorrectionCount() const {
348        return mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount();
349    }
350
351    int getEditCorrectionCount() const {
352        return mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount();
353    }
354
355    // Used to prune nodes
356    float getNormalizedCompoundDistance() const {
357        return mDicNodeState.mDicNodeStateScoring.getNormalizedCompoundDistance();
358    }
359
360    // Used to prune nodes
361    float getNormalizedSpatialDistance() const {
362        return mDicNodeState.mDicNodeStateScoring.getSpatialDistance()
363                / static_cast<float>(getInputIndex(0) + 1);
364    }
365
366    // Used to prune nodes
367    float getCompoundDistance() const {
368        return mDicNodeState.mDicNodeStateScoring.getCompoundDistance();
369    }
370
371    // Used to prune nodes
372    float getCompoundDistance(const float languageWeight) const {
373        return mDicNodeState.mDicNodeStateScoring.getCompoundDistance(languageWeight);
374    }
375
376    // Used to commit input partially
377    int getPrevWordNodePos() const {
378        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
379    }
380
381    AK_FORCE_INLINE const int *getOutputWordBuf() const {
382        return mDicNodeState.mDicNodeStateOutput.mCodePointsBuf;
383    }
384
385    int getPrevCodePointG(int pointerId) const {
386        return mDicNodeState.mDicNodeStateInput.getPrevCodePoint(pointerId);
387    }
388
389    // Whether the current codepoint can be an intentional omission, in which case the traversal
390    // algorithm will always check for a possible omission here.
391    bool canBeIntentionalOmission() const {
392        return CharUtils::isIntentionalOmissionCodePoint(getNodeCodePoint());
393    }
394
395    // Whether the omission is so frequent that it should incur zero cost.
396    bool isZeroCostOmission() const {
397        // TODO: do not hardcode and read from header
398        return (getNodeCodePoint() == KEYCODE_SINGLE_QUOTE);
399    }
400
401    // TODO: remove
402    float getTerminalDiffCostG(int path) const {
403        return mDicNodeState.mDicNodeStateInput.getTerminalDiffCost(path);
404    }
405
406    //////////////////////
407    // Temporary getter //
408    // TODO: Remove     //
409    //////////////////////
410    // TODO: Remove once touch path is merged into ProximityInfoState
411    // Note: Returned codepoint may be a digraph codepoint if the node is in a composite glyph.
412    int getNodeCodePoint() const {
413        const int codePoint = mDicNodeProperties.getNodeCodePoint();
414        const DigraphUtils::DigraphCodePointIndex digraphIndex =
415                mDicNodeState.mDicNodeStateScoring.getDigraphIndex();
416        if (digraphIndex == DigraphUtils::NOT_A_DIGRAPH_INDEX) {
417            return codePoint;
418        }
419        return DigraphUtils::getDigraphCodePointForIndex(codePoint, digraphIndex);
420    }
421
422    ////////////////////////////////
423    // Utils for cost calculation //
424    ////////////////////////////////
425    AK_FORCE_INLINE bool isSameNodeCodePoint(const DicNode *const dicNode) const {
426        return mDicNodeProperties.getNodeCodePoint()
427                == dicNode->mDicNodeProperties.getNodeCodePoint();
428    }
429
430    // TODO: remove
431    // TODO: rename getNextInputIndex
432    int16_t getInputIndex(int pointerId) const {
433        return mDicNodeState.mDicNodeStateInput.getInputIndex(pointerId);
434    }
435
436    ////////////////////////////////////
437    // Getter of features for scoring //
438    ////////////////////////////////////
439    float getSpatialDistanceForScoring() const {
440        return mDicNodeState.mDicNodeStateScoring.getSpatialDistance();
441    }
442
443    float getLanguageDistanceForScoring() const {
444        return mDicNodeState.mDicNodeStateScoring.getLanguageDistance();
445    }
446
447    // For space-aware gestures, we store the normalized distance at the char index
448    // that ends the first word of the suggestion. We call this the distance after
449    // first word.
450    float getNormalizedCompoundDistanceAfterFirstWord() const {
451        return mDicNodeState.mDicNodeStateScoring.getNormalizedCompoundDistanceAfterFirstWord();
452    }
453
454    float getLanguageDistanceRatePerWordForScoring() const {
455        const float langDist = getLanguageDistanceForScoring();
456        const float totalWordCount =
457                static_cast<float>(mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1);
458        return langDist / totalWordCount;
459    }
460
461    float getRawLength() const {
462        return mDicNodeState.mDicNodeStateScoring.getRawLength();
463    }
464
465    bool isLessThanOneErrorForScoring() const {
466        return mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount()
467                + mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount() <= 1;
468    }
469
470    DoubleLetterLevel getDoubleLetterLevel() const {
471        return mDicNodeState.mDicNodeStateScoring.getDoubleLetterLevel();
472    }
473
474    void setDoubleLetterLevel(DoubleLetterLevel doubleLetterLevel) {
475        mDicNodeState.mDicNodeStateScoring.setDoubleLetterLevel(doubleLetterLevel);
476    }
477
478    bool isInDigraph() const {
479        return mDicNodeState.mDicNodeStateScoring.getDigraphIndex()
480                != DigraphUtils::NOT_A_DIGRAPH_INDEX;
481    }
482
483    void advanceDigraphIndex() {
484        mDicNodeState.mDicNodeStateScoring.advanceDigraphIndex();
485    }
486
487    bool isExactMatch() const {
488        return mDicNodeState.mDicNodeStateScoring.isExactMatch();
489    }
490
491    bool isBlacklistedOrNotAWord() const {
492        return mDicNodeProperties.isBlacklistedOrNotAWord();
493    }
494
495    inline uint16_t getNodeCodePointCount() const {
496        return mDicNodeProperties.getDepth();
497    }
498
499    // Returns code point count including spaces
500    inline uint16_t getTotalNodeCodePointCount() const {
501        return getNodeCodePointCount() + mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
502    }
503
504    AK_FORCE_INLINE void dump(const char *tag) const {
505#if DEBUG_DICT
506        DUMP_WORD_AND_SCORE(tag);
507#if DEBUG_DUMP_ERROR
508        mProfiler.dump();
509#endif
510#endif
511    }
512
513    void setReleaseListener(DicNodeReleaseListener *releaseListener) {
514        mReleaseListener = releaseListener;
515    }
516
517    AK_FORCE_INLINE bool compare(const DicNode *right) {
518        if (!isUsed() && !right->isUsed()) {
519            // Compare pointer values here for stable comparison
520            return this > right;
521        }
522        if (!isUsed()) {
523            return true;
524        }
525        if (!right->isUsed()) {
526            return false;
527        }
528        // Promote exact matches to prevent them from being pruned.
529        const bool leftExactMatch = isExactMatch();
530        const bool rightExactMatch = right->isExactMatch();
531        if (leftExactMatch != rightExactMatch) {
532            return leftExactMatch;
533        }
534        const float diff =
535                right->getNormalizedCompoundDistance() - getNormalizedCompoundDistance();
536        static const float MIN_DIFF = 0.000001f;
537        if (diff > MIN_DIFF) {
538            return true;
539        } else if (diff < -MIN_DIFF) {
540            return false;
541        }
542        const int depth = getNodeCodePointCount();
543        const int depthDiff = right->getNodeCodePointCount() - depth;
544        if (depthDiff != 0) {
545            return depthDiff > 0;
546        }
547        for (int i = 0; i < depth; ++i) {
548            const int codePoint = mDicNodeState.mDicNodeStateOutput.getCodePointAt(i);
549            const int rightCodePoint = right->mDicNodeState.mDicNodeStateOutput.getCodePointAt(i);
550            if (codePoint != rightCodePoint) {
551                return rightCodePoint > codePoint;
552            }
553        }
554        // Compare pointer values here for stable comparison
555        return this > right;
556    }
557
558 private:
559    DicNodeProperties mDicNodeProperties;
560    DicNodeState mDicNodeState;
561    // TODO: Remove
562    bool mIsCachedForNextSuggestion;
563    bool mIsUsed;
564    DicNodeReleaseListener *mReleaseListener;
565
566    AK_FORCE_INLINE int getTotalInputIndex() const {
567        int index = 0;
568        for (int i = 0; i < MAX_POINTER_COUNT_G; i++) {
569            index += mDicNodeState.mDicNodeStateInput.getInputIndex(i);
570        }
571        return index;
572    }
573
574    // Caveat: Must not be called outside Weighting
575    // This restriction is guaranteed by "friend"
576    AK_FORCE_INLINE void addCost(const float spatialCost, const float languageCost,
577            const bool doNormalization, const int inputSize, const ErrorType errorType) {
578        if (DEBUG_GEO_FULL) {
579            LOGI_SHOW_ADD_COST_PROP;
580        }
581        mDicNodeState.mDicNodeStateScoring.addCost(spatialCost, languageCost, doNormalization,
582                inputSize, getTotalInputIndex(), errorType);
583    }
584
585    // Saves the current normalized compound distance for space-aware gestures.
586    // See getNormalizedCompoundDistanceAfterFirstWord for details.
587    AK_FORCE_INLINE void saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet() {
588        mDicNodeState.mDicNodeStateScoring.saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet();
589    }
590
591    // Caveat: Must not be called outside Weighting
592    // This restriction is guaranteed by "friend"
593    AK_FORCE_INLINE void forwardInputIndex(const int pointerId, const int count,
594            const bool overwritesPrevCodePointByNodeCodePoint) {
595        if (count == 0) {
596            return;
597        }
598        mDicNodeState.mDicNodeStateInput.forwardInputIndex(pointerId, count);
599        if (overwritesPrevCodePointByNodeCodePoint) {
600            mDicNodeState.mDicNodeStateInput.setPrevCodePoint(0, getNodeCodePoint());
601        }
602    }
603
604    AK_FORCE_INLINE void updateInputIndexG(const DicNode_InputStateG *const inputStateG) {
605        if (mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() == 1 && isFirstLetter()) {
606            mDicNodeState.mDicNodeStatePrevWord.setSecondWordFirstInputIndex(
607                    inputStateG->mInputIndex);
608        }
609        mDicNodeState.mDicNodeStateInput.updateInputIndexG(inputStateG->mPointerId,
610                inputStateG->mInputIndex, inputStateG->mPrevCodePoint,
611                inputStateG->mTerminalDiffCost, inputStateG->mRawLength);
612        mDicNodeState.mDicNodeStateScoring.addRawLength(inputStateG->mRawLength);
613        mDicNodeState.mDicNodeStateScoring.setDoubleLetterLevel(inputStateG->mDoubleLetterLevel);
614    }
615};
616} // namespace latinime
617#endif // LATINIME_DIC_NODE_H
618