001/*
002 *  Copyright 2019 Anyware Services
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 */
016package org.ametys.plugins.odfpilotage.report.consistency.impl;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025import org.ametys.cms.data.ContentValue;
026import org.ametys.cms.repository.Content;
027import org.ametys.odf.ProgramItem;
028import org.ametys.odf.course.Course;
029import org.ametys.odf.courselist.CourseList;
030import org.ametys.odf.courselist.CourseList.ChoiceType;
031import org.ametys.odf.enumeration.OdfReferenceTableEntry;
032import org.ametys.odf.program.AbstractProgram;
033import org.ametys.odf.program.Container;
034import org.ametys.odf.program.Program;
035import org.ametys.plugins.odfpilotage.report.consistency.AbstractConsistencyAnalysis;
036import org.ametys.plugins.odfpilotage.report.consistency.ConsistencyAnalysisResult;
037import org.ametys.plugins.odfpilotage.report.consistency.ConsistencyAnalysisStatus;
038import org.ametys.runtime.i18n.I18nizableText;
039
040/**
041 * Analysis on ECTS sums at each level of the {@link Program}.
042 */
043public class ECTSSumAnalysis extends AbstractConsistencyAnalysis
044{
045    private static final Pattern __ECTS_PATTERN = Pattern.compile("^.*?([0-9]+(?:[\\.,][0-9]+)?).*$");
046    
047    @Override
048    public ConsistencyAnalysisResult analyze(Program program)
049    {
050        ConsistencyAnalysisResult result = new ConsistencyAnalysisResult();
051        
052        // Introduction
053        result.setIntroText(new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_INTRO"));
054        
055        // Columns
056        result.addColumn("parentName", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_PARENT_NAME"), true);
057        result.addColumn("parentCode", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_PARENT_CODE"));
058        result.addColumn("parentECTS", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_PARENT_ECTS"));
059        result.addColumn("childrenECTS", new I18nizableText("plugin." + _pluginName, "PLUGINS_ODF_PILOTAGE_CONSISTENCY_ANALYSIS_COLUMN_CHILDREN_ECTS"));
060        
061        // Errors
062        List<Map<String, Object>> errors = _controlECTS(program, 0);
063        result.addLines(errors);
064        
065        // Status
066        result.setStatus(errors.isEmpty() ? ConsistencyAnalysisStatus.OK : ConsistencyAnalysisStatus.KO);
067        
068        return result;
069    }
070    
071    /**
072     * Get the {@link I18nizableText} representing the error message to display (with parameters).
073     * @param programItem The program element which is inconsistency
074     * @param parentECTS Number of ECTS on the parent
075     * @param childrenECTS Number of ECTS on the children
076     * @param level the level of the line to retrieve (hierarchical level begins at 0 for the program)
077     * @return The error message
078     */
079    protected Map<String, Object> _getErrorMessage(ProgramItem programItem, Double parentECTS, Double childrenECTS, int level)
080    {
081        Map<String, Object> error = new HashMap<>();
082        error.put("parentName", ((Content) programItem).getTitle());
083        error.put("parentCode", programItem.getCode());
084        error.put("parentECTS", parentECTS);
085        error.put("childrenECTS", childrenECTS);
086        error.put(ConsistencyAnalysisResult.INDENTATION_LEVEL_DATA_NAME, level);
087        return error;
088    }
089    
090    /**
091     * Control the ECTS consistency between the current content and its children.
092     * @param programItem The {@link ProgramItem} to check ECTS consistency
093     * @param level the level of the program item to control (hierarchical level begins at 0 for the program)
094     * @return The {@link List} of error messages
095     */
096    protected List<Map<String, Object>> _controlECTS(ProgramItem programItem, int level)
097    {
098        List<Map<String, Object>> errors = new ArrayList<>();
099        
100        // Children ECTS control (if there are children)
101        List<ProgramItem> children = _odfHelper.getChildProgramItems(programItem);
102        if (!children.isEmpty())
103        {
104            // Current content ECTS computation and control
105            Double parentECTS = _getECTS((Content) programItem);
106            // If there is no ECTS on the current content, there is no reason to control it.
107            if (parentECTS != null)
108            {
109                Double childrenECTS = _getChildrenECTSSum(programItem);
110                
111                if (!parentECTS.equals(childrenECTS))
112                {
113                    errors.add(_getErrorMessage(programItem, parentECTS, childrenECTS, level));
114                }
115            }
116            
117            for (ProgramItem child : children)
118            {
119                errors.addAll(_controlECTS(child, level + 1));
120            }
121        }
122        
123        return errors;
124    }
125    
126    /**
127     * Get the sum of the children of the current item. It calculates the coef if it's a {@link CourseList}.
128     * @param programItem The parent {@link ProgramItem}.
129     * @return The sum of children ECTS
130     */
131    protected Double _getChildrenECTSSum(ProgramItem programItem)
132    {
133        // Ignore optional course list and avoid useless expensive calls
134        if (programItem instanceof CourseList && ChoiceType.OPTIONAL.equals(((CourseList) programItem).getType()))
135        {
136            return 0.0;
137        }
138        
139        List<ProgramItem> children = _odfHelper.getChildProgramItems(programItem);
140
141        Double coef = 1.0;
142        Double childrenECTS = 0.0;
143        if (!children.isEmpty())
144        {
145            if (programItem instanceof CourseList)
146            {
147                CourseList courseList = (CourseList) programItem;
148                switch (courseList.getType())
149                {
150                    case CHOICE:
151                        // Apply the average of ECTS from children multiply by the minimum ELP to select
152                        coef = ((double) courseList.getMinNumberOfCourses()) / children.size();
153                        break;
154                    case MANDATORY:
155                    default:
156                        // Add all ECTS from children
157                        break;
158                }
159            }
160            
161            for (ProgramItem child : children)
162            {
163                Double childECTS = _getECTS((Content) child);
164                if (childECTS == null)
165                {
166                    childECTS = _getChildrenECTSSum(child);
167                }
168                childrenECTS += childECTS;
169            }
170        }
171        
172        return coef * childrenECTS;
173    }
174    
175    /**
176     * Get the ECTS of the current {@link Content}. The method is different if it's a {@link Program} or a {@link Course} for example.
177     * @param content The content
178     * @return The value in the ECTS field
179     */
180    protected Double _getECTS(Content content)
181    {
182        // ECTS : program, subProgram (enumeration)
183        // ECTS : container, course (enumeration)
184        // Nothing on courseList
185        
186        if (!content.hasNonEmptyValue("ects"))
187        {
188            return null;
189        }
190        
191        Object ects = content.getValue("ects");
192        
193        if (content instanceof Course || content instanceof Container)
194        {
195            return (Double) ects;
196        }
197        
198        if (content instanceof AbstractProgram)
199        {
200            String code = new OdfReferenceTableEntry(((ContentValue) ects).getContent()).getCode();
201            Matcher matcher = __ECTS_PATTERN.matcher(code);
202            if (matcher.matches())
203            {
204                return Double.parseDouble(matcher.group(1).replace(",", "."));
205            }
206            return null;
207        }
208
209        return null;
210    }
211}