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