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