001/*
002 *  Copyright 2018 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.helper;
017
018import java.util.Collections;
019import java.util.HashSet;
020import java.util.Objects;
021import java.util.Optional;
022import java.util.Set;
023import java.util.stream.Collectors;
024
025import org.apache.avalon.framework.activity.Initializable;
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.lang3.tuple.Pair;
032
033import org.ametys.cms.data.ContentValue;
034import org.ametys.core.cache.AbstractCacheManager;
035import org.ametys.core.cache.Cache;
036import org.ametys.odf.ODFHelper;
037import org.ametys.odf.ProgramItem;
038import org.ametys.odf.course.Course;
039import org.ametys.odf.enumeration.OdfReferenceTableEntry;
040import org.ametys.odf.enumeration.OdfReferenceTableHelper;
041import org.ametys.odf.program.Container;
042import org.ametys.runtime.i18n.I18nizableText;
043import org.ametys.runtime.plugin.component.AbstractLogEnabled;
044
045/**
046 * The pilotage helper.
047 */
048public class PilotageHelper extends AbstractLogEnabled implements Component, Serviceable, Initializable
049{
050    /** The avalon role */
051    public static final String ROLE = PilotageHelper.class.getName();
052    
053    /** MCC Course nature */
054    public static final String MCC_MODALITE_SESSION1 = "odf-enumeration.MccModaliteSession1";
055    
056    /** MCC Course nature */
057    public static final String MCC_MODALITE_SESSION2 = "odf-enumeration.MccModaliteSession2";
058    
059    /** MCC Course nature */
060    public static final String MCC_SESSION_NATURE = "odf-enumeration.MccSessionNature";
061    
062    /** Norme */
063    public static final String NORME = "odf-enumeration.Norme";
064
065    /** The cache id for step holders by program item */
066    protected static final String _STEP_HOLDERS_BY_ITEM_CACHE_ID = PilotageHelper.class.getName() + "$stepHoldersByItem";
067    
068    private static final String __STEPS_BY_ITEM_CACHE_ID = PilotageHelper.class.getName() + "$stepsByItem";
069
070    /** The helper for ODF contents */
071    protected ODFHelper _odfHelper;
072    /** The helper for reference tables from ODF */
073    protected OdfReferenceTableHelper _refTableHelper;
074    /** The cache manager */
075    protected AbstractCacheManager _cacheManager;
076
077    /**
078     * Enumeration for the step holder status
079     */
080    public enum StepHolderStatus
081    {
082        /** No step holder */
083        NONE,
084        /** Single step holder - Nominal behavior */
085        SINGLE,
086        /** Multiple steps holder */
087        MULTIPLE,
088        /** Unknown year id */
089        NO_YEAR,
090        /** Year is not an ascendant of the given element */
091        WRONG_YEAR;
092    }
093    
094    @Override
095    public void service(ServiceManager manager) throws ServiceException
096    {
097        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
098        _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE);
099        _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE);
100    }
101    
102    @Override
103    public void initialize() throws Exception
104    {
105        if (!_cacheManager.hasCache(__STEPS_BY_ITEM_CACHE_ID))
106        {
107            _cacheManager.createRequestCache(__STEPS_BY_ITEM_CACHE_ID,
108                new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEPS_BY_ITEM_LABEL"),
109                new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEPS_BY_ITEM_DESCRIPTION"),
110                false
111            );
112        }
113        if (!_cacheManager.hasCache(_STEP_HOLDERS_BY_ITEM_CACHE_ID)) 
114        {
115            _cacheManager.createRequestCache(_STEP_HOLDERS_BY_ITEM_CACHE_ID,
116                new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEP_HOLDERS_BY_ITEM_LABEL"),
117                new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEP_HOLDERS_BY_ITEM_DESCRIPTION"),
118                false
119            );
120        }
121    }
122
123    /**
124     * Get the step holder of a program item
125     * @param programItem The program item
126     * @return {@link Pair} with the step holder status as a key, and the step holder (if found) as a value
127     */
128    public Pair<StepHolderStatus, Container> getStepHolder(ProgramItem programItem)
129    {
130        Optional<String> yearId = getYearId();
131        
132        if (yearId.isEmpty())
133        {
134            return Pair.of(StepHolderStatus.NO_YEAR, null);
135        }
136        
137        Set<Container> stepsHolder = _getStepsHolder(programItem, yearId.get(), _cacheManager.get(_STEP_HOLDERS_BY_ITEM_CACHE_ID));
138
139        switch (stepsHolder.size())
140        {
141            case 0:
142                return Pair.of(StepHolderStatus.NONE, null);
143            case 1:
144                Container stepHolder = stepsHolder.stream().findFirst().get();
145                StepHolderStatus status = getSteps(programItem).contains(stepHolder)
146                        ? StepHolderStatus.SINGLE
147                        : StepHolderStatus.WRONG_YEAR;
148                return Pair.of(status, stepHolder);
149            default:
150                return Pair.of(StepHolderStatus.MULTIPLE, null);
151        }
152    }
153
154    /**
155     * Get the potential steps holder (step or field "etapePorteuse" in courses) of the {@link ProgramItem}.
156     * @param programItem The program item
157     * @param yearId The identifier of the year container nature
158     * @param cache The cache
159     * @return The list of potential steps holder linked to the programItem. It there are several, there is no defined step holder.
160     */
161    protected Set<Container> _getStepsHolder(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache)
162    {
163        String programItemId = programItem.getId();
164        
165        Set<Container> containers = cache.get(programItemId);
166        if (containers == null)
167        {
168            containers = _getStepHolderFromCourse(programItem) // If the element is a course and has a step holder
169                .or(() -> _getStepHolderFromContainer(programItem, yearId)) // If the element is a step (container of type year)
170                .map(Collections::singleton)
171                .orElseGet(() -> _getStepsHolderFromParentElements(programItem, yearId, cache)); // In all other cases, search in the parent elements
172            cache.put(programItemId, containers);
173        }
174        
175        return containers;
176    }
177    
178    private Optional<Container> _getStepHolderFromCourse(ProgramItem programItem)
179    {
180        return Optional.of(programItem)
181                .filter(Course.class::isInstance)
182                .map(Course.class::cast)
183                .map(c -> c.<ContentValue>getValue("etapePorteuse"))
184                .flatMap(ContentValue::getContentIfExists)
185                .map(Container.class::cast);
186    }
187    
188    private Optional<Container> _getStepHolderFromContainer(ProgramItem programItem, String yearId)
189    {
190        return Optional.of(programItem)
191                .filter(pi -> _isContainerOfNature(pi, yearId))
192                .map(Container.class::cast);
193    }
194
195    /**
196     * Filter the program item to keep only container with the given nature.
197     * @param programItem The program item
198     * @param natureId The container nature identifier
199     * @return <code>true</code> if it is a container of the given nature, <code>false</code> otherwise
200     */
201    protected boolean _isContainerOfNature(ProgramItem programItem, String natureId)
202    {
203        return Optional.of(programItem)
204            .filter(Container.class::isInstance)
205            .map(Container.class::cast)
206            .map(Container::getNature)
207            .map(natureId::equals)
208            .orElse(false);
209    }
210    
211    private Set<Container> _getStepsHolderFromParentElements(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache)
212    {
213        return _odfHelper.getParentProgramItems(programItem)
214                .stream()
215                .map(pi -> _getStepsHolder(pi, yearId, cache))
216                .flatMap(Set::stream)
217                .collect(Collectors.toSet());
218    }
219
220    /**
221     * Get the steps of a program item
222     * @param programItem The program item
223     * @return The steps of the given program item
224     */
225    public Set<Container> getSteps(ProgramItem programItem)
226    {
227        return getYearId()
228                .map(yearId -> _getSteps(programItem, yearId, _cacheManager.get(__STEPS_BY_ITEM_CACHE_ID)))
229                .orElseGet(() -> Set.of());
230    }
231    
232    /**
233     * Internal method to get the steps of a program item, can be directly called by subclasses.
234     * @param programItem The program item
235     * @param yearId The identifier of the year container nature
236     * @param cache The cache
237     * @return The steps of the given program item
238     */
239    protected Set<Container> _getSteps(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache)
240    {
241        String programItemId = programItem.getId();
242        
243        Set<Container> containers = cache.get(programItemId);
244        if (containers == null)
245        {
246            containers = _getStepsToCache(programItem, yearId, cache);
247            cache.put(programItemId, containers);
248        }
249        
250        return containers;
251    }
252    
253    /**
254     * Get the steps to add to the cache.
255     * @param programItem The program item to search on
256     * @param yearId The identifier of the year container nature
257     * @param cache The cache
258     * @return A {@link Set} of {@link Container} corresponding to the steps of the given program item.
259     */
260    protected Set<Container> _getStepsToCache(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache)
261    {
262        Set<Container> containers = new HashSet<>();
263        
264        if (programItem instanceof Container && Objects.equals(((Container) programItem).getNature(), yearId))
265        {
266            containers.add((Container) programItem);
267        }
268        else
269        {
270            for (ProgramItem parent : _odfHelper.getParentProgramItems(programItem))
271            {
272                containers.addAll(_getSteps(parent, yearId, cache));
273            }
274        }
275        
276        return containers;
277    }
278    
279    /**
280     * Get the year container nature identifier.
281     * @return an {@link Optional} of the year identifier
282     */
283    public Optional<String> getYearId()
284    {
285        return Optional.of(_refTableHelper)
286                .map(rth -> rth.getItemFromCode(OdfReferenceTableHelper.CONTAINER_NATURE, "annee"))
287                .map(OdfReferenceTableEntry::getId)
288                .filter(StringUtils::isNotBlank);
289    }
290    
291    /**
292     * Transform eqTD (can be a quotient) to a double value.
293     * @param eqTD The eqTD to parse
294     * @return The double value equivalent to the given eqTD
295     */
296    public static Double transformEqTD2Double(String eqTD)
297    {
298        Double eqTDDouble = null;
299        
300        if (StringUtils.isNotEmpty(eqTD))
301        {
302            // fraction
303            if (eqTD.indexOf('/') != -1)
304            {
305                Double numerator = Double.valueOf(eqTD.substring(0, eqTD.indexOf('/')));
306                Double denominator = Double.valueOf(eqTD.substring(eqTD.indexOf('/') + 1));
307                eqTDDouble = numerator / denominator;
308            }
309            // decimal ou entier
310            else
311            {
312                eqTDDouble = Double.valueOf(eqTD);
313            }
314        }
315        
316        return eqTDDouble;
317    }
318}