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_TRAVERSE_SESSION_H
18#define LATINIME_DIC_TRAVERSE_SESSION_H
19
20#include <vector>
21
22#include "defines.h"
23#include "dictionary/utils/multi_bigram_map.h"
24#include "jni.h"
25#include "suggest/core/dicnode/dic_nodes_cache.h"
26#include "suggest/core/layout/proximity_info_state.h"
27#include "utils/int_array_view.h"
28
29namespace latinime {
30
31class Dictionary;
32class DictionaryStructureWithBufferPolicy;
33class NgramContext;
34class ProximityInfo;
35class SuggestOptions;
36
37class DicTraverseSession {
38 public:
39
40    // A factory method for DicTraverseSession
41    static AK_FORCE_INLINE void *getSessionInstance(JNIEnv *env, jstring localeStr,
42            jlong dictSize) {
43        // To deal with the trade-off between accuracy and memory space, large cache is used for
44        // dictionaries larger that the threshold
45        return new DicTraverseSession(env, localeStr,
46                dictSize >= DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION);
47    }
48
49    static AK_FORCE_INLINE void releaseSessionInstance(DicTraverseSession *traverseSession) {
50        delete traverseSession;
51    }
52
53    AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
54            : mPrevWordIdCount(0), mProximityInfo(nullptr), mDictionary(nullptr),
55              mSuggestOptions(nullptr), mDicNodesCache(usesLargeCache), mMultiBigramMap(),
56              mInputSize(0), mMaxPointerCount(1), mMultiWordCostMultiplier(1.0f) {
57        // NOTE: mProximityInfoStates is an array of instances.
58        // No need to initialize it explicitly here.
59    }
60
61    // Non virtual inline destructor -- never inherit this class
62    AK_FORCE_INLINE ~DicTraverseSession() {}
63
64    void init(const Dictionary *dictionary, const NgramContext *const ngramContext,
65            const SuggestOptions *const suggestOptions);
66    // TODO: Remove and merge into init
67    void setupForGetSuggestions(const ProximityInfo *pInfo, const int *inputCodePoints,
68            const int inputSize, const int *const inputXs, const int *const inputYs,
69            const int *const times, const int *const pointerIds, const float maxSpatialDistance,
70            const int maxPointerCount);
71    void resetCache(const int thresholdForNextActiveDicNodes, const int maxWords);
72
73    const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const;
74
75    //--------------------
76    // getters and setters
77    //--------------------
78    const ProximityInfo *getProximityInfo() const { return mProximityInfo; }
79    const SuggestOptions *getSuggestOptions() const { return mSuggestOptions; }
80    const WordIdArrayView getPrevWordIds() const {
81        return WordIdArrayView::fromArray(mPrevWordIdArray).limit(mPrevWordIdCount);
82    }
83    DicNodesCache *getDicTraverseCache() { return &mDicNodesCache; }
84    MultiBigramMap *getMultiBigramMap() { return &mMultiBigramMap; }
85    const ProximityInfoState *getProximityInfoState(int id) const {
86        return &mProximityInfoStates[id];
87    }
88    int getInputSize() const { return mInputSize; }
89
90    bool isOnlyOnePointerUsed(int *pointerId) const {
91        // Not in the dictionary word
92        int usedPointerCount = 0;
93        int usedPointerId = 0;
94        for (int i = 0; i < mMaxPointerCount; ++i) {
95            if (mProximityInfoStates[i].isUsed()) {
96                ++usedPointerCount;
97                usedPointerId = i;
98            }
99        }
100        if (usedPointerCount != 1) {
101            return false;
102        }
103        if (pointerId) {
104            *pointerId = usedPointerId;
105        }
106        return true;
107    }
108
109    ProximityType getProximityTypeG(const DicNode *const dicNode, const int childCodePoint) const {
110        ProximityType proximityType = UNRELATED_CHAR;
111        for (int i = 0; i < MAX_POINTER_COUNT_G; ++i) {
112            if (!mProximityInfoStates[i].isUsed()) {
113                continue;
114            }
115            const int pointerId = dicNode->getInputIndex(i);
116            proximityType = mProximityInfoStates[i].getProximityTypeG(pointerId, childCodePoint);
117            ASSERT(proximityType == UNRELATED_CHAR || proximityType == MATCH_CHAR);
118            // TODO: Make this more generic
119            // Currently we assume there are only two types here -- UNRELATED_CHAR
120            // and MATCH_CHAR
121            if (proximityType != UNRELATED_CHAR) {
122                return proximityType;
123            }
124        }
125        return proximityType;
126    }
127
128    AK_FORCE_INLINE bool isCacheBorderForTyping(const int inputSize) const {
129        return mDicNodesCache.isCacheBorderForTyping(inputSize);
130    }
131
132    /**
133     * Returns whether or not it is possible to continue suggestion from the previous search.
134     */
135    // TODO: Remove. No need to check once the session is fully implemented.
136    bool isContinuousSuggestionPossible() const {
137        if (!mDicNodesCache.hasCachedDicNodesForContinuousSuggestion()) {
138            return false;
139        }
140        ASSERT(mMaxPointerCount <= MAX_POINTER_COUNT_G);
141        for (int i = 0; i < mMaxPointerCount; ++i) {
142            const ProximityInfoState *const pInfoState = getProximityInfoState(i);
143            // If a proximity info state is not continuous suggestion possible,
144            // do not continue searching.
145            if (pInfoState->isUsed() && !pInfoState->isContinuousSuggestionPossible()) {
146                return false;
147            }
148        }
149        return true;
150    }
151
152    bool isTouchPositionCorrectionEnabled() const {
153        return mProximityInfoStates[0].touchPositionCorrectionEnabled();
154    }
155
156    float getMultiWordCostMultiplier() const {
157        return mMultiWordCostMultiplier;
158    }
159
160 private:
161    DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseSession);
162    // threshold to start caching
163    static const int CACHE_START_INPUT_LENGTH_THRESHOLD;
164    static const int DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION;
165    void initializeProximityInfoStates(const int *const inputCodePoints, const int *const inputXs,
166            const int *const inputYs, const int *const times, const int *const pointerIds,
167            const int inputSize, const float maxSpatialDistance, const int maxPointerCount);
168
169    WordIdArray<MAX_PREV_WORD_COUNT_FOR_N_GRAM> mPrevWordIdArray;
170    size_t mPrevWordIdCount;
171    const ProximityInfo *mProximityInfo;
172    const Dictionary *mDictionary;
173    const SuggestOptions *mSuggestOptions;
174
175    DicNodesCache mDicNodesCache;
176    // Temporary cache for bigram frequencies
177    MultiBigramMap mMultiBigramMap;
178    ProximityInfoState mProximityInfoStates[MAX_POINTER_COUNT_G];
179
180    int mInputSize;
181    int mMaxPointerCount;
182
183    /////////////////////////////////
184    // Configuration per dictionary
185    float mMultiWordCostMultiplier;
186
187};
188} // namespace latinime
189#endif // LATINIME_DIC_TRAVERSE_SESSION_H
190