001/*
002 *  Copyright 2024 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.odf.properties.section.content;
017
018import java.util.Comparator;
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Locale;
023import java.util.Map;
024import java.util.Optional;
025import java.util.function.Predicate;
026
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.LocaleUtils;
031
032import org.ametys.cms.properties.section.AbstractDefaultPropertySection;
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.repository.Content.ReferencingContentsSearch;
035import org.ametys.odf.ODFHelper;
036import org.ametys.odf.ProgramItem;
037import org.ametys.plugins.repository.AmetysObject;
038import org.ametys.plugins.repository.AmetysObjectResolver;
039
040/**
041 * The referencer items for {@link ProgramItem}.
042 * It displays educational paths, children and other references to the content.
043 */
044public class ProgramItemReferencerSection extends AbstractDefaultPropertySection implements Serviceable
045{
046    private static final ListProgramItemComparator __LIST_PROGRAM_ITEM_COMPARATOR = new ListProgramItemComparator();
047
048    private AmetysObjectResolver _resolver;
049    private ODFHelper _odfHelper;
050
051    public void service(ServiceManager smanager) throws ServiceException
052    {
053        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
054        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
055    }
056    
057    public boolean supports(AmetysObject ametysObject)
058    {
059        return ametysObject instanceof ProgramItem;
060    }
061    
062    @Override
063    protected Map<String, Object> buildData(AmetysObject ametysObject)
064    {
065        ProgramItem programItem = (ProgramItem) ametysObject;
066        
067        Map<String, Object> resultMap = new LinkedHashMap<>();
068
069        resultMap.put("educationalPaths", _getEducationalPaths(programItem));
070        resultMap.put("children", _getChildren(programItem));
071        resultMap.put("otherReferences", _getOtherReferences((Content) ametysObject));
072        
073        return resultMap;
074    }
075
076    private List<List<Map<String, Object>>> _getEducationalPaths(ProgramItem programItem)
077    {
078        // To avoid to return path only with the program item itself
079        if (_odfHelper.hasParentProgramItems(programItem))
080        {
081            return _odfHelper.getEducationalPaths(programItem)
082                .stream()
083                .map(p -> p.getProgramItems(_resolver))
084                .sorted(__LIST_PROGRAM_ITEM_COMPARATOR)
085                .map(this::_programItemList2JSON)
086                .toList();
087        }
088        
089        return List.of();
090    }
091    
092    private List<Map<String, Object>> _getChildren(ProgramItem programItem)
093    {
094        return _programItemList2JSON(_odfHelper.getChildProgramItems(programItem));
095    }
096
097    @SuppressWarnings("unlikely-arg-type")
098    private Map<String, Object> _getOtherReferences(Content content)
099    {
100        Map<String, Object> resultMap = new LinkedHashMap<>();
101        
102        ReferencingContentsSearch referencingContents = content.searchReferencingContents(100);
103
104        ProgramItem programItem = (ProgramItem) content;
105        List<ProgramItem> children2 = _odfHelper.getChildProgramItems(programItem);
106        List<ProgramItem> parents2 = _odfHelper.getParentProgramItems(programItem);
107        
108        Locale locale = Optional.of(content)
109            .map(Content::getLanguage)
110            .map(LocaleUtils::toLocale)
111            .orElse(null);
112        
113        resultMap.put(
114            "referencingContents",
115            referencingContents.referencingContents()
116                .stream()
117                // Remove direct children
118                .filter(Predicate.not(children2::contains))
119                // Remove direct parents
120                .filter(Predicate.not(parents2::contains))
121                .map(c -> _content2JSON(c, locale))
122                .toList()
123        );
124        
125        if (referencingContents.hasOtherReferences())
126        {
127            resultMap.put("hasOther", true);
128        }
129        return resultMap;
130    }
131    
132    private List<Map<String, Object>> _programItemList2JSON(List<ProgramItem> programItems)
133    {
134        return programItems.stream()
135                .map(this::_programItem2JSON)
136                .toList();
137    }
138    
139    private Map<String, Object> _programItem2JSON(ProgramItem programItem)
140    {
141        Map<String, Object> json = new HashMap<>();
142        json.put("id", programItem.getId());
143        json.put("title", ((Content) programItem).getTitle());
144        json.put("code", programItem.getCode());
145        return json;
146    }
147    
148    private Map<String, Object> _content2JSON(Content content, Locale locale)
149    {
150        return Map.of(
151            "id", content.getId(),
152            "title", content.getTitle(locale)
153        );
154    }
155
156    /**
157     * Comparator of {@link List} composing of {@link ProgramItem} by title.
158     */
159    private static final class ListProgramItemComparator implements Comparator<List<ProgramItem>>
160    {
161        public int compare(List<ProgramItem> l1, List<ProgramItem> l2)
162        {
163            int l1Size = l1.size();
164            int l2Size = l2.size();
165            
166            for (int i = 0; i < l1Size; i++)
167            {
168                if (l2Size <= i)
169                {
170                    // l1 is greater than l2
171                    return 1;
172                }
173                else
174                {
175                    // Compare title
176                    int comparing = ((Content) l1.get(i)).getTitle().compareTo(((Content) l2.get(i)).getTitle());
177                    if (comparing != 0)
178                    {
179                        return comparing;
180                    }
181                }
182            }
183            
184            // Lists are the same at the end of reading l1
185            // Then compare size of the lists
186            return Integer.compare(l1Size, l2Size);
187        }
188    }
189}