001package org.nasdanika.html.flow;
002
003import java.util.Collection;
004import java.util.HashMap;
005import java.util.HashSet;
006import java.util.Map;
007import java.util.Map.Entry;
008import java.util.Set;
009import java.util.function.BiConsumer;
010import java.util.function.Consumer;
011import java.util.function.Predicate;
012import java.util.stream.Collectors;
013
014import org.eclipse.emf.common.util.EList;
015import org.eclipse.emf.ecore.EObject;
016import org.nasdanika.common.Context;
017import org.nasdanika.common.ProgressMonitor;
018import org.nasdanika.common.Util;
019import org.nasdanika.diagram.Diagram;
020import org.nasdanika.flow.Call;
021import org.nasdanika.flow.Flow;
022import org.nasdanika.flow.FlowElement;
023import org.nasdanika.flow.FlowPackage;
024import org.nasdanika.flow.PseudoState;
025import org.nasdanika.flow.Transition;
026import org.nasdanika.flow.util.FlowStateDiagramGenerator;
027import org.nasdanika.html.model.app.Action;
028import org.nasdanika.html.model.app.AppFactory;
029import org.nasdanika.html.model.app.Label;
030import org.nasdanika.html.model.app.NavigationPanel;
031import org.nasdanika.html.model.app.NavigationPanelStyle;
032import org.nasdanika.ncore.Marker;
033import org.nasdanika.ncore.util.NamedElementComparator;
034
035public class FlowActionBuilder extends ActivityActionBuilder<Flow> {
036        
037        public FlowActionBuilder(Flow value, Context context) {
038                super(value, context);
039        }
040        
041        @Override
042        protected Action buildAction(
043                        Action action,
044                        BiConsumer<EObject, Action> registry,
045                        Consumer<org.nasdanika.common.Consumer<org.nasdanika.html.emf.EObjectActionResolver.Context>> resolveConsumer,
046                        ProgressMonitor progressMonitor) throws Exception {
047                
048                action = super.buildAction(action, registry, resolveConsumer, progressMonitor);
049                EList<EObject> children = action.getChildren(); 
050                Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance;
051                for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) {
052                        children.add(createChildAction(element, registry, resolveConsumer, progressMonitor));
053                }
054                EList<Action> anonymous = action.getAnonymous(); 
055                for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState).collect(Collectors.toList())) {
056                        anonymous.add(createChildAction(element, registry, resolveConsumer, progressMonitor));
057                }
058                
059                return action;
060        }
061                
062        public static int compareFlowElements(FlowElement<?> a, FlowElement<?> b) {
063                if (a == b) {
064                        return 0;
065                }
066
067                String asg = a.getSortGroup();
068                String bsg = b.getSortGroup();
069                if (Util.isBlank(asg)) {
070                        if (!Util.isBlank(bsg)) {
071                                return -1;
072                        }
073                } else {
074                        if (Util.isBlank(bsg)) {
075                                return 1;
076                        }
077                        int cmp = asg.compareTo(bsg);
078                        if (cmp != 0) {
079                                return cmp;
080                        }
081                }
082                
083                int abDistance = distance(a, b, new HashSet<>());
084                int baDistance = distance(b, a, new HashSet<>());
085                
086                if (abDistance != -1 && baDistance == -1) {
087                        return -1;
088                }
089                
090                if (baDistance != -1 && abDistance == -1) {
091                        return 1;
092                }
093                
094                // Cycle
095                if (abDistance != -1 && baDistance != -1) {
096                        return abDistance - baDistance;
097                }
098                
099                // Common source - shortest sum.
100                Map<FlowElement<?>, Integer> aSources = sources(a, new HashSet<>());
101                int da = 0;
102                int db = 0;
103                FlowElement<?> cs = null;
104                for (Entry<FlowElement<?>, Integer> bse: sources(b, new HashSet<>()).entrySet()) {
105                        for (Entry<FlowElement<?>, Integer> ase: aSources.entrySet()) {
106                                if (ase.getKey() == bse.getKey()) {
107                                        int sd = ase.getValue() + bse.getValue();
108                                        if (cs == null || sd < da + db) {
109                                                da = ase.getValue();
110                                                db = bse.getValue();
111                                                cs = ase.getKey();
112                                        }
113                                }
114                        }                                       
115                }                               
116                if (cs != null && da != db) {
117                        return da - db;
118                }
119                
120                for (Marker am: a.getMarkers()) {
121                        for (Marker bm: b.getMarkers()) {
122                                if (am != null 
123                                                && bm != null 
124                                                && !Util.isBlank(am.getLocation())
125                                                && am.getLocation().equals(bm.getLocation())) {
126                                        
127                                        int lineDiff = am.getLine() - bm.getLine();
128                                        if (lineDiff != 0) {
129                                                return lineDiff;
130                                        }
131                                        int colDiff = am.getColumn() - bm.getColumn();
132                                        if (colDiff != 0) {
133                                                return colDiff;
134                                        }                       
135                                }
136                        }
137                }
138                
139                return NamedElementComparator.INSTANCE.compare(a, b);
140        }
141        
142        /**
143         * All sources for this target with distances.
144         * @param source
145         * @param target
146         * @param traversed
147         * @return
148         */
149        private static Map<FlowElement<?>, Integer> sources(FlowElement<?> target, Set<FlowElement<?>> traversed) {
150                Map<FlowElement<?>, Integer> ret = new HashMap<>();
151                if (traversed.add(target)) {
152                        for (Transition input: target.getInputs()) {
153                                FlowElement<?> source = (FlowElement<?>) input.eContainer().eContainer();
154                                ret.put(source, 1);
155                                for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) {
156                                        Integer existingDistance = ret.get(se.getKey());
157                                        if (existingDistance == null) {
158                                                ret.put(se.getKey(), se.getValue() + 1);                                                
159                                        } else if (se.getValue() < existingDistance) {
160                                                ret.put(se.getKey(), se.getValue());
161                                        }
162                                }                               
163                        }
164                        
165                        for (Call invocation: target.getInvocations()) {
166                                FlowElement<?> source = (FlowElement<?>) invocation.eContainer().eContainer();
167                                ret.put(source, 1);
168                                for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) {
169                                        Integer existingDistance = ret.get(se.getKey());
170                                        if (existingDistance == null) {
171                                                ret.put(se.getKey(), se.getValue() + 1);                                                
172                                        } else if (se.getValue() < existingDistance) {
173                                                ret.put(se.getKey(), se.getValue());
174                                        }
175                                }                                                               
176                        }
177                }
178                return ret;
179        }
180        
181        /**
182         * 
183         * @param source
184         * @param target
185         * @param traversed
186         * @return Number of transitions/calls between the source and the target. -1 if target is not reacheable from the source. 
187         */
188        private static int distance(FlowElement<?> source, FlowElement<?> target, Set<FlowElement<?>> traversed) {
189                if (source == target) {
190                        return 0;
191                }
192                int distance = -1;
193                if (traversed.add(source)) {
194                        for (Transition output: source.getOutputs().values()) {
195                                int targetDistance = distance(output.getTarget(), target, traversed);
196                                if (targetDistance != -1) {
197                                        if (distance == -1) {
198                                                distance = targetDistance + 1;
199                                        } else {
200                                                distance = Math.min(distance, targetDistance + 1);
201                                        }
202                                }
203                        }
204                        for (Call call: source.getCalls().values()) {
205                                int targetDistance = distance(call.getTarget(), target, traversed);
206                                if (targetDistance != -1) {
207                                        if (distance == -1) {
208                                                distance = targetDistance + 1;
209                                        } else {
210                                                distance = Math.min(distance, targetDistance + 1);
211                                        }
212                                }
213                        }
214                }
215                return distance;
216        }
217                
218        @Override
219        protected void populateRepresentation(Diagram representation, FlowStateDiagramGenerator flowStateDiagramGenerator) {
220                if (getTarget().isPartition()) {
221                        super.populateRepresentation(representation, flowStateDiagramGenerator);
222                } else {
223                        flowStateDiagramGenerator.generateDiagram(getTarget(), representation, null);
224                }
225        }
226        
227        @Override
228        protected void resolve(
229                        Action action, 
230                        org.nasdanika.html.emf.EObjectActionResolver.Context context,
231                        ProgressMonitor progressMonitor) throws Exception {
232                super.resolve(action, context, progressMonitor);
233                
234                if (getTarget().eContainmentFeature() == FlowPackage.Literals.ACTIVITY_ENTRY__VALUE && getTarget().eContainer().eContainmentFeature() == FlowPackage.Literals.SERVICE_PROVIDER__SERVICES) {
235                        // Left content panel with children.
236                        NavigationPanel leftNavigation = action.getLeftNavigation();
237                        if (leftNavigation == null) {
238                                leftNavigation = AppFactory.eINSTANCE.createNavigationPanel();
239                                action.setLeftNavigation(leftNavigation);                       
240                                int subChildren = getTarget().getElements().values().stream().filter(Flow.class::isInstance).map(Flow.class::cast).map(Flow::getElements).mapToInt(Collection::size).sum();                             
241                                leftNavigation.setStyle(subChildren == 0 ? NavigationPanelStyle.AUTO : flowElements(getTarget()) > 25 ? NavigationPanelStyle.SEARCHABLE_TREE : NavigationPanelStyle.TREE);
242                        }
243                        Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance;
244                        for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) {
245                                leftNavigation.getItems().add(createItem(action, context, progressMonitor, element));
246                        }
247                }
248        }
249        
250        /**
251         * Recursively calculates number of flow elements excluding the flow itself
252         * @param flow
253         * @return
254         */
255        private static int flowElements(Flow flow) {
256                return flow.getElements().stream().mapToInt(e -> e instanceof Flow ? flowElements((Flow) e) + 1 : 1).sum();
257        }
258
259        private EObject createItem(
260                        Action action, 
261                        org.nasdanika.html.emf.EObjectActionResolver.Context context,
262                        ProgressMonitor progressMonitor, 
263                        FlowElement<?> element) throws Exception {
264                EObject item = renderValue(action, FlowPackage.Literals.FLOW__ELEMENTS, element, context, progressMonitor);
265                if (element instanceof Flow && item instanceof Label) {
266                        Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance;
267                        for (FlowElement<?> child: ((Flow) element).getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) {
268                                ((Label) item).getChildren().add(createItem(action, context, progressMonitor, child));
269                        }
270                }
271                return item;
272        }
273
274}