001// --------------------------------------------------------------------------------
002// Copyright 2002-2026 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.model.control.training.server.logic;
018
019import com.echothree.model.control.training.common.exception.UnknownPartyTrainingClassSessionAnswerException;
020import com.echothree.model.control.training.common.exception.UnknownPartyTrainingClassSessionPageException;
021import com.echothree.model.control.training.common.exception.UnknownPartyTrainingClassSessionQuestionException;
022import com.echothree.model.control.training.common.exception.UnknownPartyTrainingClassSessionSequenceException;
023import com.echothree.model.control.training.common.exception.UnknownPartyTrainingClassSessionStatusException;
024import com.echothree.model.control.training.server.control.TrainingControl;
025import com.echothree.model.data.training.server.entity.PartyTrainingClass;
026import com.echothree.model.data.training.server.entity.PartyTrainingClassSession;
027import com.echothree.model.data.training.server.entity.PartyTrainingClassSessionAnswer;
028import com.echothree.model.data.training.server.entity.PartyTrainingClassSessionPage;
029import com.echothree.model.data.training.server.entity.PartyTrainingClassSessionQuestion;
030import com.echothree.model.data.training.server.entity.PartyTrainingClassSessionSection;
031import com.echothree.model.data.training.server.entity.PartyTrainingClassSessionStatus;
032import com.echothree.model.data.training.server.entity.TrainingClassPage;
033import com.echothree.model.data.training.server.entity.TrainingClassQuestion;
034import com.echothree.util.common.message.ExecutionErrors;
035import com.echothree.util.common.persistence.BasePK;
036import com.echothree.util.server.control.BaseLogic;
037import com.echothree.util.server.message.ExecutionErrorAccumulator;
038import com.echothree.util.server.persistence.EncryptionUtils;
039import com.echothree.util.server.persistence.EntityPermission;
040import com.echothree.util.server.persistence.Session;
041import java.io.Serializable;
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.Comparator;
045import java.util.List;
046import javax.enterprise.context.ApplicationScoped;
047import javax.enterprise.inject.spi.CDI;
048import javax.inject.Inject;
049
050@ApplicationScoped
051public class PartyTrainingClassSessionLogic
052        extends BaseLogic {
053
054    @Inject
055    TrainingControl trainingControl;
056
057    @Inject
058    PartyTrainingClassLogic partyTrainingClassLogic;
059
060    protected PartyTrainingClassSessionLogic() {
061        super();
062    }
063
064    public static PartyTrainingClassSessionLogic getInstance() {
065        return CDI.current().select(PartyTrainingClassSessionLogic.class).get();
066    }
067
068    private PartyTrainingClassSession getPartyTrainingClassSession(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
069            final Integer partyTrainingClassSessionSequence, final EntityPermission entityPermission) {
070        var partyTrainingClassSession = trainingControl.getPartyTrainingClassSessionBySequence(partyTrainingClass,
071                partyTrainingClassSessionSequence, entityPermission);
072
073        if(partyTrainingClass == null) {
074            handleExecutionError(UnknownPartyTrainingClassSessionSequenceException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionSequence.name(),
075                    partyTrainingClass.getLastDetail().getPartyTrainingClassName(), partyTrainingClassSessionSequence.toString());
076        }
077
078        return partyTrainingClassSession;
079    }
080    
081    public PartyTrainingClassSession getPartyTrainingClassSession(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
082            final Integer partyTrainingClassSessionSequence) {
083        return getPartyTrainingClassSession(eea, partyTrainingClass, partyTrainingClassSessionSequence, EntityPermission.READ_ONLY);
084    }
085    
086    public PartyTrainingClassSession getPartyTrainingClassSessionForUpdate(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
087            final Integer partyTrainingClassSessionSequence) {
088        return getPartyTrainingClassSession(eea, partyTrainingClass, partyTrainingClassSessionSequence, EntityPermission.READ_WRITE);
089    }
090    
091    private PartyTrainingClassSession getLatestPartyTrainingClassSession(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
092            final EntityPermission entityPermission) {
093        var partyTrainingClassStatus = trainingControl.getPartyTrainingClassStatus(partyTrainingClass);
094        var partyTrainingClassSessionSequence = partyTrainingClassStatus.getPartyTrainingClassSessionSequence();
095        var partyTrainingClassSession = partyTrainingClassStatus == null ? null
096                : trainingControl.getPartyTrainingClassSessionBySequence(partyTrainingClass, partyTrainingClassSessionSequence, entityPermission);
097
098        if(partyTrainingClass == null) {
099            handleExecutionError(UnknownPartyTrainingClassSessionSequenceException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionSequence.name(),
100                    partyTrainingClass.getLastDetail().getPartyTrainingClassName(), partyTrainingClassSessionSequence.toString());
101        }
102
103        return partyTrainingClassSession;
104    }
105    
106    public PartyTrainingClassSession getLatestPartyTrainingClassSession(final ExecutionErrorAccumulator eea,
107            final PartyTrainingClass partyTrainingClass) {
108        return getLatestPartyTrainingClassSession(eea, partyTrainingClass, EntityPermission.READ_ONLY);
109    }
110    
111    public PartyTrainingClassSession getLatestPartyTrainingClassSessionForUpdate(final ExecutionErrorAccumulator eea,
112            final PartyTrainingClass partyTrainingClass) {
113        return getLatestPartyTrainingClassSession(eea, partyTrainingClass, EntityPermission.READ_WRITE);
114    }
115    
116    private PartyTrainingClassSessionPage getPartyTrainingClassSessionPage(final ExecutionErrorAccumulator eea,
117            final PartyTrainingClassSession partyTrainingClassSession, final Integer partyTrainingClassSessionPageSequence,
118            final EntityPermission entityPermission) {
119        var partyTrainingClassSessionPage = trainingControl.getPartyTrainingClassSessionPage(partyTrainingClassSession,
120                partyTrainingClassSessionPageSequence, entityPermission);
121
122        if(partyTrainingClassSessionPage == null) {
123            var partyTrainingClassSessionDetail = partyTrainingClassSession.getLastDetail();
124            var partyTrainingClassDetail = partyTrainingClassSessionDetail.getPartyTrainingClass().getLastDetail();
125            
126            handleExecutionError(UnknownPartyTrainingClassSessionPageException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionPage.name(),
127                    partyTrainingClassDetail.getPartyTrainingClassName(), partyTrainingClassSessionDetail.getPartyTrainingClassSessionSequence().toString(),
128                    partyTrainingClassSessionPageSequence.toString());
129        }
130
131        return partyTrainingClassSessionPage;
132    }
133    
134    public PartyTrainingClassSessionPage getPartyTrainingClassSessionPage(final ExecutionErrorAccumulator eea,
135            final PartyTrainingClassSession partyTrainingClassSession, final Integer partyTrainingClassSessionPageSequence) {
136        return getPartyTrainingClassSessionPage(eea, partyTrainingClassSession, partyTrainingClassSessionPageSequence, EntityPermission.READ_ONLY);
137    }
138    
139    public PartyTrainingClassSessionPage getPartyTrainingClassSessionPageForUpdate(final ExecutionErrorAccumulator eea,
140            final PartyTrainingClassSession partyTrainingClassSession, final Integer partyTrainingClassSessionPageSequence) {
141        return getPartyTrainingClassSessionPage(eea, partyTrainingClassSession, partyTrainingClassSessionPageSequence, EntityPermission.READ_WRITE);
142    }
143    
144    private PartyTrainingClassSessionQuestion getPartyTrainingClassSessionQuestion(final ExecutionErrorAccumulator eea,
145            final PartyTrainingClassSession partyTrainingClassSession, final TrainingClassQuestion trainingClassQuestion,
146            final EntityPermission entityPermission) {
147        var partyTrainingClassSessionQuestion = trainingControl.getPartyTrainingClassSessionQuestion(partyTrainingClassSession,
148                trainingClassQuestion, entityPermission);
149
150        if(partyTrainingClassSessionQuestion == null) {
151            var partyTrainingClassSessionDetail = partyTrainingClassSession.getLastDetail();
152            var partyTrainingClassDetail = partyTrainingClassSessionDetail.getPartyTrainingClass().getLastDetail();
153            var trainingClassQuestionDetail = trainingClassQuestion.getLastDetail();
154            var trainingClassSectionDetail = trainingClassQuestionDetail.getTrainingClassSection().getLastDetail();
155            var trainingClassDetail = trainingClassSectionDetail.getTrainingClass().getLastDetail();
156            
157            handleExecutionError(UnknownPartyTrainingClassSessionQuestionException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionQuestion.name(),
158                    partyTrainingClassDetail.getPartyTrainingClassName(), partyTrainingClassSessionDetail.getPartyTrainingClassSessionSequence().toString(),
159                    trainingClassDetail.getTrainingClassName(), trainingClassSectionDetail.getTrainingClassSectionName(),
160                    trainingClassQuestionDetail.getTrainingClassQuestionName());
161        }
162
163        return partyTrainingClassSessionQuestion;
164    }
165    
166    public PartyTrainingClassSessionQuestion getPartyTrainingClassSessionQuestion(final ExecutionErrorAccumulator eea,
167            final PartyTrainingClassSession partyTrainingClassSession, final TrainingClassQuestion trainingClassQuestion) {
168        return getPartyTrainingClassSessionQuestion(eea, partyTrainingClassSession, trainingClassQuestion, EntityPermission.READ_ONLY);
169    }
170    
171    public PartyTrainingClassSessionQuestion getPartyTrainingClassSessionQuestionForUpdate(final ExecutionErrorAccumulator eea,
172            final PartyTrainingClassSession partyTrainingClassSession, final TrainingClassQuestion trainingClassQuestion) {
173        return getPartyTrainingClassSessionQuestion(eea, partyTrainingClassSession, trainingClassQuestion, EntityPermission.READ_WRITE);
174    }
175    
176    private PartyTrainingClassSessionAnswer getPartyTrainingClassSessionAnswer(final ExecutionErrorAccumulator eea,
177            final PartyTrainingClassSessionQuestion partyTrainingClassSessionQuestion, final Integer partyTrainingClassSessionAnswerSequence,
178            final EntityPermission entityPermission) {
179        var partyTrainingClassSessionAnswer = trainingControl.getPartyTrainingClassSessionAnswer(partyTrainingClassSessionQuestion,
180                partyTrainingClassSessionAnswerSequence, entityPermission);
181
182        if(partyTrainingClassSessionAnswer == null) {
183            var partyTrainingClassSessionQuestionDetail = partyTrainingClassSessionQuestion.getLastDetail();
184            var partyTrainingClassSessionDetail = partyTrainingClassSessionQuestionDetail.getPartyTrainingClassSession().getLastDetail();
185            var partyTrainingClassDetail = partyTrainingClassSessionDetail.getPartyTrainingClass().getLastDetail();
186            var trainingClassQuestionDetail = partyTrainingClassSessionQuestionDetail.getTrainingClassQuestion().getLastDetail();
187            var trainingClassSectionDetail = trainingClassQuestionDetail.getTrainingClassSection().getLastDetail();
188            var trainingClassDetail = trainingClassSectionDetail.getTrainingClass().getLastDetail();
189            
190            handleExecutionError(UnknownPartyTrainingClassSessionAnswerException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionAnswer.name(),
191                    partyTrainingClassDetail.getPartyTrainingClassName(), partyTrainingClassSessionDetail.getPartyTrainingClassSessionSequence().toString(),
192                    trainingClassDetail.getTrainingClassName(), trainingClassSectionDetail.getTrainingClassSectionName(),
193                    trainingClassQuestionDetail.getTrainingClassQuestionName(), partyTrainingClassSessionAnswerSequence.toString());
194        }
195
196        return partyTrainingClassSessionAnswer;
197    }
198    
199    public PartyTrainingClassSessionAnswer getPartyTrainingClassSessionAnswer(final ExecutionErrorAccumulator eea,
200            final PartyTrainingClassSessionQuestion partyTrainingClassSessionQuestion, final Integer partyTrainingClassSessionAnswerSequence) {
201        return getPartyTrainingClassSessionAnswer(eea, partyTrainingClassSessionQuestion, partyTrainingClassSessionAnswerSequence, EntityPermission.READ_ONLY);
202    }
203    
204    public PartyTrainingClassSessionAnswer getPartyTrainingClassSessionAnswerForUpdate(final ExecutionErrorAccumulator eea,
205            final PartyTrainingClassSessionQuestion partyTrainingClassSessionQuestion, final Integer partyTrainingClassSessionAnswerSequence) {
206        return getPartyTrainingClassSessionAnswer(eea, partyTrainingClassSessionQuestion, partyTrainingClassSessionAnswerSequence, EntityPermission.READ_WRITE);
207    }
208    
209    public PartyTrainingClassSessionStatus getLatestPartyTrainingClassSessionStatusForUpdate(final ExecutionErrorAccumulator eea, final String partyTrainingClassName) {
210        PartyTrainingClassSessionStatus partyTrainingClassSessionStatus = null;
211        var partyTrainingClass = partyTrainingClassLogic.getPartyTrainingClassByName(eea, partyTrainingClassName);
212        
213        if(!hasExecutionErrors(eea)) {
214            var partyTrainingClassSession = getLatestPartyTrainingClassSession(eea, partyTrainingClass);
215            
216            if(!hasExecutionErrors(eea)) {
217                partyTrainingClassSessionStatus = trainingControl.getPartyTrainingClassSessionStatusForUpdate(partyTrainingClassSession);
218                
219                if(partyTrainingClassSessionStatus == null) {
220                    handleExecutionError(UnknownPartyTrainingClassSessionStatusException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionStatus.name(),
221                            partyTrainingClass.getLastDetail().getPartyTrainingClassName(),
222                            partyTrainingClassSession.getLastDetail().getPartyTrainingClassSessionSequence().toString());
223                }
224            }
225        }
226        
227        return partyTrainingClassSessionStatus;
228    }
229    
230    public PartyTrainingClassSessionPage createPartyTrainingClassSessionPage(final Session session, final PartyTrainingClassSession partyTrainingClassSession,
231            final TrainingClassPage trainingClassPage, final BasePK createdBy) {
232        var partyTrainingClassSessionPage = trainingControl.createPartyTrainingClassSessionPage(partyTrainingClassSession,
233                trainingClassPage, session.getStartTime(), null, createdBy);
234
235        return partyTrainingClassSessionPage;
236    }
237
238    public PartyTrainingClassSessionQuestion createPartyTrainingClassSessionQuestion(final PartyTrainingClassSession partyTrainingClassSession,
239            final TrainingClassQuestion trainingClassQuestion, final Integer sortOrder, final BasePK createdBy) {
240        return trainingControl.createPartyTrainingClassSessionQuestion(partyTrainingClassSession, trainingClassQuestion, sortOrder, createdBy);
241    }
242
243    static class SortBySortOrder implements Comparator<TrainingClassQuestion>, Serializable {
244
245        @Override
246        public int compare(TrainingClassQuestion o1, TrainingClassQuestion o2) {
247            var s1 = o1.getLastDetail().getSortOrder();
248            var s2 = o2.getLastDetail().getSortOrder();
249
250            return s1.compareTo(s2);
251        }
252        
253    }
254
255    public void setupPartyTrainingClassSessionQuestions(final PartyTrainingClassSession partyTrainingClassSession, final BasePK createdBy) {
256        var trainingClass = partyTrainingClassSession.getLastDetail().getPartyTrainingClass().getLastDetail().getTrainingClass();
257        var trainingClassSections = trainingControl.getTrainingClassSections(trainingClass);
258        var overallQuestionCount = trainingClass.getLastDetail().getOverallQuestionCount();
259        List<TrainingClassQuestion> randomOverallQuestions = new ArrayList<>();
260        List<TrainingClassQuestion> finalTrainingClassQuestions = new ArrayList<>();
261        var random = EncryptionUtils.getInstance().getRandom();
262
263        var overallQuestionTotal = 0;
264        for(var trainingClassSection : trainingClassSections) {
265            var questionCount = trainingClassSection.getLastDetail().getQuestionCount();
266            var trainingClassQuestions = trainingControl.getTrainingClassQuestions(trainingClassSection);
267            List<TrainingClassQuestion> randomSectionQuestions = new ArrayList<>();
268
269            // Add in all required questions...
270            var questionTotal = 0;
271            for(var trainingClassQuestion : trainingClassQuestions) {
272                if(trainingClassQuestion.getLastDetail().getPassingRequired()) {
273                    finalTrainingClassQuestions.add(trainingClassQuestion);
274                    questionTotal++;
275                    overallQuestionTotal++;
276                } else {
277                    // If it isn't required, add it to the pool of questions used to fill in random ones.
278                    randomSectionQuestions.add(trainingClassQuestion);
279                }
280            }
281
282            // If there's a required minimum Question count, try to fill in the rest randomly.
283            if(questionCount != null) {
284                var remainingQuestions = questionCount - questionTotal;
285
286                // If there are random questions, add in a few from the section randomly.
287                while(remainingQuestions > 0) {
288                    if(randomSectionQuestions.isEmpty()) {
289                        break;
290                    } else {
291                        // pick a Question and add to finalTrainingClassQuestions
292                        var randomQuestion = random.nextInt(randomSectionQuestions.size());
293                        finalTrainingClassQuestions.add(randomSectionQuestions.get(randomQuestion));
294                        overallQuestionTotal++;
295
296                        // removed picked one from randomSectionQuestions
297                        randomSectionQuestions.remove(randomQuestion);
298
299                        remainingQuestions--;
300                    }
301                }
302            }
303
304            // Add any remaining Questions to the overall random Question Set.
305            randomOverallQuestions.addAll(randomSectionQuestions);
306        }
307
308        if(overallQuestionCount != null) {
309            var remainingQuestions = overallQuestionCount - overallQuestionTotal;
310
311            while(remainingQuestions > 0) {
312                if(randomOverallQuestions.isEmpty()) {
313                    break;
314                } else {
315                    // pick one and add to finalTrainingClassQuestions
316                    var randomQuestion = random.nextInt(randomOverallQuestions.size());
317                    finalTrainingClassQuestions.add(randomOverallQuestions.get(randomQuestion));
318
319                    // removed picked one from randomOverallQuestions
320                    randomOverallQuestions.remove(randomQuestion);
321
322                    remainingQuestions--;
323                }
324            }
325        }
326
327        Collections.sort(finalTrainingClassQuestions, new SortBySortOrder());
328
329        var sortOrder = 0;
330        for(var finalTrainingClassQuestion : finalTrainingClassQuestions) {
331            createPartyTrainingClassSessionQuestion(partyTrainingClassSession, finalTrainingClassQuestion, sortOrder++, createdBy);
332        }
333    }
334    
335    public PartyTrainingClassSession createPartyTrainingClassSession(final PartyTrainingClass partyTrainingClass, final BasePK createdBy) {
336        var partyTrainingClassSession = trainingControl.createPartyTrainingClassSession(partyTrainingClass, createdBy);
337        var partyTrainingClassStatus = trainingControl.getPartyTrainingClassStatusForUpdate(partyTrainingClass);
338
339        partyTrainingClassStatus.setLastPartyTrainingClassSession(partyTrainingClassSession);
340
341        setupPartyTrainingClassSessionQuestions(partyTrainingClassSession, createdBy);
342
343        return partyTrainingClassSession;
344    }
345
346    public void deletePartyTrainingClassSession(final PartyTrainingClassSession partyTrainingClassSession, final BasePK deletedBy) {
347        var partyTrainingClass = partyTrainingClassSession.getLastDetail().getPartyTrainingClass();
348
349        trainingControl.deletePartyTrainingClassSession(partyTrainingClassSession, deletedBy);
350
351        // Create a new PartyTrainingClassSession if the PartyTrainingClass is not yet complete (where "complete" means that the
352        // completedTime is null, and the LastPartyTrainingClassSession is equals to the one we're deleting in the PartyTrainingClassStatus.
353        if(partyTrainingClass.getLastDetail().getCompletedTime() == null) {
354            var partyTrainingClassStatus = trainingControl.getPartyTrainingClassStatusForUpdate(partyTrainingClass);
355
356            if(partyTrainingClassSession.equals(partyTrainingClassStatus.getLastPartyTrainingClassSession())) {
357                createPartyTrainingClassSession(partyTrainingClass, deletedBy);
358            }
359        }
360    }
361    
362    public void updatePartyTrainingClassSessionStatus(final Session session, final PartyTrainingClassSessionStatus partyTrainingClassSessionStatus,
363            final PartyTrainingClassSessionSection lastPartyTrainingClassSessionSection, final PartyTrainingClassSessionPage lastPartyTrainingClassSessionPage,
364            final PartyTrainingClassSessionQuestion lastPartyTrainingClassSessionQuestion) {
365        partyTrainingClassSessionStatus.setLastPartyTrainingClassSessionSection(lastPartyTrainingClassSessionSection);
366        partyTrainingClassSessionStatus.setLastPartyTrainingClassSessionPage(lastPartyTrainingClassSessionPage);
367        partyTrainingClassSessionStatus.setLastPartyTrainingClassSessionQuestion(lastPartyTrainingClassSessionQuestion);
368    }
369
370}