001// --------------------------------------------------------------------------------
002// Copyright 2002-2025 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;
048
049@ApplicationScoped
050public class PartyTrainingClassSessionLogic
051        extends BaseLogic {
052
053    protected PartyTrainingClassSessionLogic() {
054        super();
055    }
056
057    public static PartyTrainingClassSessionLogic getInstance() {
058        return CDI.current().select(PartyTrainingClassSessionLogic.class).get();
059    }
060
061    private PartyTrainingClassSession getPartyTrainingClassSession(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
062            final Integer partyTrainingClassSessionSequence, final EntityPermission entityPermission) {
063        var trainingControl = Session.getModelController(TrainingControl.class);
064        var partyTrainingClassSession = trainingControl.getPartyTrainingClassSessionBySequence(partyTrainingClass,
065                partyTrainingClassSessionSequence, entityPermission);
066
067        if(partyTrainingClass == null) {
068            handleExecutionError(UnknownPartyTrainingClassSessionSequenceException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionSequence.name(),
069                    partyTrainingClass.getLastDetail().getPartyTrainingClassName(), partyTrainingClassSessionSequence.toString());
070        }
071
072        return partyTrainingClassSession;
073    }
074    
075    public PartyTrainingClassSession getPartyTrainingClassSession(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
076            final Integer partyTrainingClassSessionSequence) {
077        return getPartyTrainingClassSession(eea, partyTrainingClass, partyTrainingClassSessionSequence, EntityPermission.READ_ONLY);
078    }
079    
080    public PartyTrainingClassSession getPartyTrainingClassSessionForUpdate(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
081            final Integer partyTrainingClassSessionSequence) {
082        return getPartyTrainingClassSession(eea, partyTrainingClass, partyTrainingClassSessionSequence, EntityPermission.READ_WRITE);
083    }
084    
085    private PartyTrainingClassSession getLatestPartyTrainingClassSession(final ExecutionErrorAccumulator eea, final PartyTrainingClass partyTrainingClass,
086            final EntityPermission entityPermission) {
087        var trainingControl = Session.getModelController(TrainingControl.class);
088        var partyTrainingClassStatus = trainingControl.getPartyTrainingClassStatus(partyTrainingClass);
089        var partyTrainingClassSessionSequence = partyTrainingClassStatus.getPartyTrainingClassSessionSequence();
090        var partyTrainingClassSession = partyTrainingClassStatus == null ? null
091                : trainingControl.getPartyTrainingClassSessionBySequence(partyTrainingClass, partyTrainingClassSessionSequence, entityPermission);
092
093        if(partyTrainingClass == null) {
094            handleExecutionError(UnknownPartyTrainingClassSessionSequenceException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionSequence.name(),
095                    partyTrainingClass.getLastDetail().getPartyTrainingClassName(), partyTrainingClassSessionSequence.toString());
096        }
097
098        return partyTrainingClassSession;
099    }
100    
101    public PartyTrainingClassSession getLatestPartyTrainingClassSession(final ExecutionErrorAccumulator eea,
102            final PartyTrainingClass partyTrainingClass) {
103        return getLatestPartyTrainingClassSession(eea, partyTrainingClass, EntityPermission.READ_ONLY);
104    }
105    
106    public PartyTrainingClassSession getLatestPartyTrainingClassSessionForUpdate(final ExecutionErrorAccumulator eea,
107            final PartyTrainingClass partyTrainingClass) {
108        return getLatestPartyTrainingClassSession(eea, partyTrainingClass, EntityPermission.READ_WRITE);
109    }
110    
111    private PartyTrainingClassSessionPage getPartyTrainingClassSessionPage(final ExecutionErrorAccumulator eea,
112            final PartyTrainingClassSession partyTrainingClassSession, final Integer partyTrainingClassSessionPageSequence,
113            final EntityPermission entityPermission) {
114        var trainingControl = Session.getModelController(TrainingControl.class);
115        var partyTrainingClassSessionPage = trainingControl.getPartyTrainingClassSessionPage(partyTrainingClassSession,
116                partyTrainingClassSessionPageSequence, entityPermission);
117
118        if(partyTrainingClassSessionPage == null) {
119            var partyTrainingClassSessionDetail = partyTrainingClassSession.getLastDetail();
120            var partyTrainingClassDetail = partyTrainingClassSessionDetail.getPartyTrainingClass().getLastDetail();
121            
122            handleExecutionError(UnknownPartyTrainingClassSessionPageException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionPage.name(),
123                    partyTrainingClassDetail.getPartyTrainingClassName(), partyTrainingClassSessionDetail.getPartyTrainingClassSessionSequence().toString(),
124                    partyTrainingClassSessionPageSequence.toString());
125        }
126
127        return partyTrainingClassSessionPage;
128    }
129    
130    public PartyTrainingClassSessionPage getPartyTrainingClassSessionPage(final ExecutionErrorAccumulator eea,
131            final PartyTrainingClassSession partyTrainingClassSession, final Integer partyTrainingClassSessionPageSequence) {
132        return getPartyTrainingClassSessionPage(eea, partyTrainingClassSession, partyTrainingClassSessionPageSequence, EntityPermission.READ_ONLY);
133    }
134    
135    public PartyTrainingClassSessionPage getPartyTrainingClassSessionPageForUpdate(final ExecutionErrorAccumulator eea,
136            final PartyTrainingClassSession partyTrainingClassSession, final Integer partyTrainingClassSessionPageSequence) {
137        return getPartyTrainingClassSessionPage(eea, partyTrainingClassSession, partyTrainingClassSessionPageSequence, EntityPermission.READ_WRITE);
138    }
139    
140    private PartyTrainingClassSessionQuestion getPartyTrainingClassSessionQuestion(final ExecutionErrorAccumulator eea,
141            final PartyTrainingClassSession partyTrainingClassSession, final TrainingClassQuestion trainingClassQuestion,
142            final EntityPermission entityPermission) {
143        var trainingControl = Session.getModelController(TrainingControl.class);
144        var partyTrainingClassSessionQuestion = trainingControl.getPartyTrainingClassSessionQuestion(partyTrainingClassSession,
145                trainingClassQuestion, entityPermission);
146
147        if(partyTrainingClassSessionQuestion == null) {
148            var partyTrainingClassSessionDetail = partyTrainingClassSession.getLastDetail();
149            var partyTrainingClassDetail = partyTrainingClassSessionDetail.getPartyTrainingClass().getLastDetail();
150            var trainingClassQuestionDetail = trainingClassQuestion.getLastDetail();
151            var trainingClassSectionDetail = trainingClassQuestionDetail.getTrainingClassSection().getLastDetail();
152            var trainingClassDetail = trainingClassSectionDetail.getTrainingClass().getLastDetail();
153            
154            handleExecutionError(UnknownPartyTrainingClassSessionQuestionException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionQuestion.name(),
155                    partyTrainingClassDetail.getPartyTrainingClassName(), partyTrainingClassSessionDetail.getPartyTrainingClassSessionSequence().toString(),
156                    trainingClassDetail.getTrainingClassName(), trainingClassSectionDetail.getTrainingClassSectionName(),
157                    trainingClassQuestionDetail.getTrainingClassQuestionName());
158        }
159
160        return partyTrainingClassSessionQuestion;
161    }
162    
163    public PartyTrainingClassSessionQuestion getPartyTrainingClassSessionQuestion(final ExecutionErrorAccumulator eea,
164            final PartyTrainingClassSession partyTrainingClassSession, final TrainingClassQuestion trainingClassQuestion) {
165        return getPartyTrainingClassSessionQuestion(eea, partyTrainingClassSession, trainingClassQuestion, EntityPermission.READ_ONLY);
166    }
167    
168    public PartyTrainingClassSessionQuestion getPartyTrainingClassSessionQuestionForUpdate(final ExecutionErrorAccumulator eea,
169            final PartyTrainingClassSession partyTrainingClassSession, final TrainingClassQuestion trainingClassQuestion) {
170        return getPartyTrainingClassSessionQuestion(eea, partyTrainingClassSession, trainingClassQuestion, EntityPermission.READ_WRITE);
171    }
172    
173    private PartyTrainingClassSessionAnswer getPartyTrainingClassSessionAnswer(final ExecutionErrorAccumulator eea,
174            final PartyTrainingClassSessionQuestion partyTrainingClassSessionQuestion, final Integer partyTrainingClassSessionAnswerSequence,
175            final EntityPermission entityPermission) {
176        var trainingControl = Session.getModelController(TrainingControl.class);
177        var partyTrainingClassSessionAnswer = trainingControl.getPartyTrainingClassSessionAnswer(partyTrainingClassSessionQuestion,
178                partyTrainingClassSessionAnswerSequence, entityPermission);
179
180        if(partyTrainingClassSessionAnswer == null) {
181            var partyTrainingClassSessionQuestionDetail = partyTrainingClassSessionQuestion.getLastDetail();
182            var partyTrainingClassSessionDetail = partyTrainingClassSessionQuestionDetail.getPartyTrainingClassSession().getLastDetail();
183            var partyTrainingClassDetail = partyTrainingClassSessionDetail.getPartyTrainingClass().getLastDetail();
184            var trainingClassQuestionDetail = partyTrainingClassSessionQuestionDetail.getTrainingClassQuestion().getLastDetail();
185            var trainingClassSectionDetail = trainingClassQuestionDetail.getTrainingClassSection().getLastDetail();
186            var trainingClassDetail = trainingClassSectionDetail.getTrainingClass().getLastDetail();
187            
188            handleExecutionError(UnknownPartyTrainingClassSessionAnswerException.class, eea, ExecutionErrors.UnknownPartyTrainingClassSessionAnswer.name(),
189                    partyTrainingClassDetail.getPartyTrainingClassName(), partyTrainingClassSessionDetail.getPartyTrainingClassSessionSequence().toString(),
190                    trainingClassDetail.getTrainingClassName(), trainingClassSectionDetail.getTrainingClassSectionName(),
191                    trainingClassQuestionDetail.getTrainingClassQuestionName(), partyTrainingClassSessionAnswerSequence.toString());
192        }
193
194        return partyTrainingClassSessionAnswer;
195    }
196    
197    public PartyTrainingClassSessionAnswer getPartyTrainingClassSessionAnswer(final ExecutionErrorAccumulator eea,
198            final PartyTrainingClassSessionQuestion partyTrainingClassSessionQuestion, final Integer partyTrainingClassSessionAnswerSequence) {
199        return getPartyTrainingClassSessionAnswer(eea, partyTrainingClassSessionQuestion, partyTrainingClassSessionAnswerSequence, EntityPermission.READ_ONLY);
200    }
201    
202    public PartyTrainingClassSessionAnswer getPartyTrainingClassSessionAnswerForUpdate(final ExecutionErrorAccumulator eea,
203            final PartyTrainingClassSessionQuestion partyTrainingClassSessionQuestion, final Integer partyTrainingClassSessionAnswerSequence) {
204        return getPartyTrainingClassSessionAnswer(eea, partyTrainingClassSessionQuestion, partyTrainingClassSessionAnswerSequence, EntityPermission.READ_WRITE);
205    }
206    
207    public PartyTrainingClassSessionStatus getLatestPartyTrainingClassSessionStatusForUpdate(final ExecutionErrorAccumulator eea, final String partyTrainingClassName) {
208        PartyTrainingClassSessionStatus partyTrainingClassSessionStatus = null;
209        var partyTrainingClass = PartyTrainingClassLogic.getInstance().getPartyTrainingClassByName(eea, partyTrainingClassName);
210        
211        if(!hasExecutionErrors(eea)) {
212            var partyTrainingClassSession = getLatestPartyTrainingClassSession(eea, partyTrainingClass);
213            
214            if(!hasExecutionErrors(eea)) {
215                var trainingControl = Session.getModelController(TrainingControl.class);
216                
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 trainingControl = Session.getModelController(TrainingControl.class);
233        var partyTrainingClassSessionPage = trainingControl.createPartyTrainingClassSessionPage(partyTrainingClassSession,
234                trainingClassPage, session.START_TIME_LONG, null, createdBy);
235
236        return partyTrainingClassSessionPage;
237    }
238
239    public PartyTrainingClassSessionQuestion createPartyTrainingClassSessionQuestion(final PartyTrainingClassSession partyTrainingClassSession,
240            final TrainingClassQuestion trainingClassQuestion, final Integer sortOrder, final BasePK createdBy) {
241        var trainingControl = Session.getModelController(TrainingControl.class);
242        
243        return trainingControl.createPartyTrainingClassSessionQuestion(partyTrainingClassSession, trainingClassQuestion, sortOrder, createdBy);
244    }
245
246    static class SortBySortOrder implements Comparator<TrainingClassQuestion>, Serializable {
247
248        @Override
249        public int compare(TrainingClassQuestion o1, TrainingClassQuestion o2) {
250            var s1 = o1.getLastDetail().getSortOrder();
251            var s2 = o2.getLastDetail().getSortOrder();
252
253            return s1.compareTo(s2);
254        }
255        
256    }
257
258    public void setupPartyTrainingClassSessionQuestions(final PartyTrainingClassSession partyTrainingClassSession, final BasePK createdBy) {
259        var trainingControl = Session.getModelController(TrainingControl.class);
260        var trainingClass = partyTrainingClassSession.getLastDetail().getPartyTrainingClass().getLastDetail().getTrainingClass();
261        var trainingClassSections = trainingControl.getTrainingClassSections(trainingClass);
262        var overallQuestionCount = trainingClass.getLastDetail().getOverallQuestionCount();
263        List<TrainingClassQuestion> randomOverallQuestions = new ArrayList<>();
264        List<TrainingClassQuestion> finalTrainingClassQuestions = new ArrayList<>();
265        var random = EncryptionUtils.getInstance().getRandom();
266
267        var overallQuestionTotal = 0;
268        for(var trainingClassSection : trainingClassSections) {
269            var questionCount = trainingClassSection.getLastDetail().getQuestionCount();
270            var trainingClassQuestions = trainingControl.getTrainingClassQuestions(trainingClassSection);
271            List<TrainingClassQuestion> randomSectionQuestions = new ArrayList<>();
272
273            // Add in all required questions...
274            var questionTotal = 0;
275            for(var trainingClassQuestion : trainingClassQuestions) {
276                if(trainingClassQuestion.getLastDetail().getPassingRequired()) {
277                    finalTrainingClassQuestions.add(trainingClassQuestion);
278                    questionTotal++;
279                    overallQuestionTotal++;
280                } else {
281                    // If it isn't required, add it to the pool of questions used to fill in random ones.
282                    randomSectionQuestions.add(trainingClassQuestion);
283                }
284            }
285
286            // If there's a required minimum Question count, try to fill in the rest randomly.
287            if(questionCount != null) {
288                var remainingQuestions = questionCount - questionTotal;
289
290                // If there are random questions, add in a few from the section randomly.
291                while(remainingQuestions > 0) {
292                    if(randomSectionQuestions.isEmpty()) {
293                        break;
294                    } else {
295                        // pick a Question and add to finalTrainingClassQuestions
296                        var randomQuestion = random.nextInt(randomSectionQuestions.size());
297                        finalTrainingClassQuestions.add(randomSectionQuestions.get(randomQuestion));
298                        overallQuestionTotal++;
299
300                        // removed picked one from randomSectionQuestions
301                        randomSectionQuestions.remove(randomQuestion);
302
303                        remainingQuestions--;
304                    }
305                }
306            }
307
308            // Add any remaining Questions to the overall random Question Set.
309            randomOverallQuestions.addAll(randomSectionQuestions);
310        }
311
312        if(overallQuestionCount != null) {
313            var remainingQuestions = overallQuestionCount - overallQuestionTotal;
314
315            while(remainingQuestions > 0) {
316                if(randomOverallQuestions.isEmpty()) {
317                    break;
318                } else {
319                    // pick one and add to finalTrainingClassQuestions
320                    var randomQuestion = random.nextInt(randomOverallQuestions.size());
321                    finalTrainingClassQuestions.add(randomOverallQuestions.get(randomQuestion));
322
323                    // removed picked one from randomOverallQuestions
324                    randomOverallQuestions.remove(randomQuestion);
325
326                    remainingQuestions--;
327                }
328            }
329        }
330
331        Collections.sort(finalTrainingClassQuestions, new SortBySortOrder());
332
333        var sortOrder = 0;
334        for(var finalTrainingClassQuestion : finalTrainingClassQuestions) {
335            createPartyTrainingClassSessionQuestion(partyTrainingClassSession, finalTrainingClassQuestion, sortOrder++, createdBy);
336        }
337    }
338    
339    public PartyTrainingClassSession createPartyTrainingClassSession(final PartyTrainingClass partyTrainingClass, final BasePK createdBy) {
340        var trainingControl = Session.getModelController(TrainingControl.class);
341        var partyTrainingClassSession = trainingControl.createPartyTrainingClassSession(partyTrainingClass, createdBy);
342        var partyTrainingClassStatus = trainingControl.getPartyTrainingClassStatusForUpdate(partyTrainingClass);
343
344        partyTrainingClassStatus.setLastPartyTrainingClassSession(partyTrainingClassSession);
345
346        setupPartyTrainingClassSessionQuestions(partyTrainingClassSession, createdBy);
347
348        return partyTrainingClassSession;
349    }
350
351    public void deletePartyTrainingClassSession(final PartyTrainingClassSession partyTrainingClassSession, final BasePK deletedBy) {
352        var trainingControl = Session.getModelController(TrainingControl.class);
353        var partyTrainingClass = partyTrainingClassSession.getLastDetail().getPartyTrainingClass();
354
355        trainingControl.deletePartyTrainingClassSession(partyTrainingClassSession, deletedBy);
356
357        // Create a new PartyTrainingClassSession if the PartyTrainingClass is not yet complete (where "complete" means that the
358        // completedTime is null, and the LastPartyTrainingClassSession is equals to the one we're deleting in the PartyTrainingClassStatus.
359        if(partyTrainingClass.getLastDetail().getCompletedTime() == null) {
360            var partyTrainingClassStatus = trainingControl.getPartyTrainingClassStatusForUpdate(partyTrainingClass);
361
362            if(partyTrainingClassSession.equals(partyTrainingClassStatus.getLastPartyTrainingClassSession())) {
363                createPartyTrainingClassSession(partyTrainingClass, deletedBy);
364            }
365        }
366    }
367    
368    public void updatePartyTrainingClassSessionStatus(final Session session, final PartyTrainingClassSessionStatus partyTrainingClassSessionStatus,
369            final PartyTrainingClassSessionSection lastPartyTrainingClassSessionSection, final PartyTrainingClassSessionPage lastPartyTrainingClassSessionPage,
370            final PartyTrainingClassSessionQuestion lastPartyTrainingClassSessionQuestion) {
371        partyTrainingClassSessionStatus.setLastPartyTrainingClassSessionSection(lastPartyTrainingClassSessionSection);
372        partyTrainingClassSessionStatus.setLastPartyTrainingClassSessionPage(lastPartyTrainingClassSessionPage);
373        partyTrainingClassSessionStatus.setLastPartyTrainingClassSessionQuestion(lastPartyTrainingClassSessionQuestion);
374    }
375
376}