1 /*
2     SPDX-FileCopyrightText: 2013-2019 Andreas Cord-Landwehr <cordlandwehr@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 #include "trainingsession.h"
8 #include "artikulate_debug.h"
9 #include "core/icourse.h"
10 #include "core/language.h"
11 #include "core/phrase.h"
12 #include "core/unit.h"
13 #include "learner.h"
14 #include "profilemanager.h"
15 #include "trainingaction.h"
16 
TrainingSession(LearnerProfile::ProfileManager * manager,QObject * parent)17 TrainingSession::TrainingSession(LearnerProfile::ProfileManager *manager, QObject *parent)
18     : ISessionActions(parent)
19     , m_profileManager(manager)
20     , m_course(nullptr)
21 {
22     Q_ASSERT(m_profileManager != nullptr);
23 }
24 
course() const25 ICourse *TrainingSession::course() const
26 {
27     return m_course;
28 }
29 
setCourse(ICourse * course)30 void TrainingSession::setCourse(ICourse *course)
31 {
32     if (!course) {
33         updateTrainingActions();
34         return;
35     }
36     if (m_course == course) {
37         return;
38     }
39     m_course = course;
40     if (m_course && m_course->units().count() > 0) {
41         setUnit(m_course->units().constFirst().get());
42     }
43 
44     // lazy loading of training data
45     LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->id());
46     if (!goal) {
47         goal = m_profileManager->registerGoal(LearnerProfile::LearningGoal::Language, course->language()->id(), course->language()->i18nTitle());
48     }
49     auto data = m_profileManager->progressValues(m_profileManager->activeProfile(), goal, m_course->id());
50     const auto unitList = m_course->units();
51     for (auto unit : qAsConst(unitList)) {
52         const auto phrases = unit->phrases();
53         for (auto &phrase : phrases) {
54             auto iter = data.find(phrase->id());
55             if (iter != data.end()) {
56                 //                phrase->setProgress(iter.value()); //FIXME add a decorator?
57             }
58         }
59     }
60     updateTrainingActions();
61     emit courseChanged();
62 }
63 
activeUnit() const64 IUnit *TrainingSession::activeUnit() const
65 {
66     if (auto phrase = activePhrase()) {
67         return phrase->unit().get();
68     }
69     return nullptr;
70 }
71 
setUnit(IUnit * unit)72 void TrainingSession::setUnit(IUnit *unit)
73 {
74     // checking phrases in increasing order ensures that always the first phrase is selected
75     for (int i = 0; i < m_actions.count(); ++i) {
76         for (int j = 0; j < m_actions.at(i)->actions().count(); ++j) {
77             const auto testPhrase = qobject_cast<TrainingAction *>(m_actions.at(i)->actions().at(j))->phrase();
78             if (unit == testPhrase->unit().get()) {
79                 if (auto action = activeAction()) {
80                     action->setChecked(false);
81                 }
82                 m_indexUnit = i;
83                 m_indexPhrase = j;
84                 if (auto action = activeAction()) {
85                     action->setChecked(true);
86                 }
87                 emit phraseChanged();
88                 return;
89             }
90         }
91     }
92 }
93 
activeAction() const94 TrainingAction *TrainingSession::activeAction() const
95 {
96     if (m_indexUnit < 0 || m_indexPhrase < 0) {
97         return nullptr;
98     }
99     return qobject_cast<TrainingAction *>(m_actions.at(m_indexUnit)->actions().at(m_indexPhrase));
100 }
101 
activePhrase() const102 IPhrase *TrainingSession::activePhrase() const
103 {
104     if (const auto action = activeAction()) {
105         return action->phrase();
106     }
107     return nullptr;
108 }
109 
setActivePhrase(IPhrase * phrase)110 void TrainingSession::setActivePhrase(IPhrase *phrase)
111 {
112     for (int i = 0; i < m_actions.count(); ++i) {
113         for (int j = 0; j < m_actions.at(i)->actions().count(); ++j) {
114             const auto testPhrase = qobject_cast<TrainingAction *>(m_actions.at(i)->actions().at(j))->phrase();
115             if (phrase == testPhrase) {
116                 if (auto action = activeAction()) {
117                     action->setChecked(false);
118                 }
119                 m_indexUnit = i;
120                 m_indexPhrase = j;
121                 if (auto action = activeAction()) {
122                     action->setChecked(true);
123                 }
124                 emit phraseChanged();
125                 return;
126             }
127         }
128     }
129 }
130 
accept()131 void TrainingSession::accept()
132 {
133     Q_ASSERT(m_indexUnit >= 0);
134     Q_ASSERT(m_indexPhrase >= 0);
135     if (m_indexUnit < 0 || m_indexPhrase < 0) {
136         return;
137     }
138     auto phrase = activePhrase();
139 
140     // possibly update goals of learner
141     updateGoal();
142     //    phrase->updateProgress(Phrase::Progress::Done); //FIXME
143 
144     // store training activity
145     LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->language()->id());
146     //    m_profileManager->recordProgress(m_profileManager->activeProfile(), //FIXME
147     //        goal,
148     //        m_course->id(),
149     //        phrase->id(),
150     //        static_cast<int>(LearnerProfile::ProfileManager::Skip),
151     //        phrase->progress()
152     //    );
153 
154     selectNextPhrase();
155 }
156 
skip()157 void TrainingSession::skip()
158 {
159     Q_ASSERT(m_indexUnit >= 0);
160     Q_ASSERT(m_indexPhrase >= 0);
161     if (m_indexUnit < 0 || m_indexPhrase < 0) {
162         return;
163     }
164 
165     // possibly update goals of learner
166     updateGoal();
167     auto phrase = activePhrase();
168     //    phrase->updateProgress(Phrase::Progress::Skip); //FIXME
169 
170     // store training activity
171     LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->language()->id());
172     //    m_profileManager->recordProgress(m_profileManager->activeProfile(),
173     //        goal,
174     //        m_course->id(),
175     //        phrase->id(),
176     //        static_cast<int>(LearnerProfile::ProfileManager::Skip),
177     //        phrase->progress()
178     //    ); // FIXME
179 
180     selectNextPhrase();
181 }
182 
selectNextPhrase()183 void TrainingSession::selectNextPhrase()
184 {
185     if (auto action = activeAction()) {
186         action->setChecked(false);
187     }
188     // try to find next phrase, otherwise return completed
189     // 1. case: last phrase in unit
190     if (m_indexPhrase >= m_actions.at(m_indexUnit)->actions().count() - 1) {
191         // close current unit
192         emit closeUnit();
193         qDebug() << "switching to next unit";
194         if (m_indexUnit >= m_actions.count() - 1) {
195             emit completed();
196         } else {
197             ++m_indexUnit;
198             m_indexPhrase = 0;
199             // currently there is no way to automatically enter a submenu, at least inform user about new unit
200             m_actions.at(m_indexUnit)->setChecked(true);
201         }
202     // 2. case: next phrase inside current unit
203     } else {
204         ++m_indexPhrase;
205     }
206     if (auto action = activeAction()) {
207         action->setChecked(true);
208     }
209     emit phraseChanged();
210 }
211 
hasPrevious() const212 bool TrainingSession::hasPrevious() const
213 {
214     return m_indexUnit > 0 || m_indexPhrase > 0;
215 }
216 
hasNext() const217 bool TrainingSession::hasNext() const
218 {
219     if (m_indexUnit < m_actions.count() - 1) {
220         return true;
221     }
222     if (!m_actions.isEmpty() && m_actions.constLast()) {
223         if (m_indexPhrase < m_actions.constLast()->actions().count() - 1) {
224             return true;
225         }
226     }
227     return false;
228 }
229 
updateGoal()230 void TrainingSession::updateGoal()
231 {
232     if (!m_profileManager) {
233         qCWarning(ARTIKULATE_LOG()) << "No ProfileManager registered, aborting operation";
234         return;
235     }
236     LearnerProfile::Learner *learner = m_profileManager->activeProfile();
237     if (!learner) {
238         qCWarning(ARTIKULATE_LOG()) << "No active Learner registered, aborting operation";
239         return;
240     }
241     LearnerProfile::LearningGoal *goal = m_profileManager->goal(LearnerProfile::LearningGoal::Language, m_course->language()->id());
242     learner->addGoal(goal);
243     learner->setActiveGoal(goal);
244 }
245 
trainingActions() const246 QVector<TrainingAction *> TrainingSession::trainingActions() const
247 {
248     return m_actions;
249 }
250 
updateTrainingActions()251 void TrainingSession::updateTrainingActions()
252 {
253     for (const auto &action : qAsConst(m_actions)) {
254         action->deleteLater();
255     }
256     m_actions.clear();
257 
258     if (!m_course) {
259         m_indexUnit = -1;
260         m_indexPhrase = -1;
261         return;
262     }
263 
264     const auto unitList = m_course->units();
265     for (const auto &unit : qAsConst(unitList)) {
266         auto action = new TrainingAction(unit->title(), this);
267         const auto phraseList = unit->phrases();
268         for (const auto &phrase : qAsConst(phraseList)) {
269             if (phrase->sound().isEmpty()) {
270                 continue;
271             }
272             action->appendAction(new TrainingAction(phrase, this, unit.get()));
273         }
274         if (action->actions().count() > 0) {
275             m_actions.append(action);
276         } else {
277             action->deleteLater();
278         }
279     }
280 
281     // update indices
282     m_indexUnit = -1;
283     m_indexPhrase = -1;
284     if (m_course->units().count() > 0) {
285         m_indexUnit = 0;
286         if (m_course->units().constFirst()->phrases().count() > 0) {
287             m_indexPhrase = 0;
288         }
289     }
290 }
291