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                Marker am = a.getMarker();
121                Marker bm = b.getMarker();
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                return NamedElementComparator.INSTANCE.compare(a, b);
138        }
139        
140        /**
141         * All sources for this target with distances.
142         * @param source
143         * @param target
144         * @param traversed
145         * @return
146         */
147        private static Map<FlowElement<?>, Integer> sources(FlowElement<?> target, Set<FlowElement<?>> traversed) {
148                Map<FlowElement<?>, Integer> ret = new HashMap<>();
149                if (traversed.add(target)) {
150                        for (Transition input: target.getInputs()) {
151                                FlowElement<?> source = (FlowElement<?>) input.eContainer().eContainer();
152                                ret.put(source, 1);
153                                for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) {
154                                        Integer existingDistance = ret.get(se.getKey());
155                                        if (existingDistance == null) {
156                                                ret.put(se.getKey(), se.getValue() + 1);                                                
157                                        } else if (se.getValue() < existingDistance) {
158                                                ret.put(se.getKey(), se.getValue());
159                                        }
160                                }                               
161                        }
162                        
163                        for (Call invocation: target.getInvocations()) {
164                                FlowElement<?> source = (FlowElement<?>) invocation.eContainer().eContainer();
165                                ret.put(source, 1);
166                                for (Entry<FlowElement<?>, Integer> se: sources(source, traversed).entrySet()) {
167                                        Integer existingDistance = ret.get(se.getKey());
168                                        if (existingDistance == null) {
169                                                ret.put(se.getKey(), se.getValue() + 1);                                                
170                                        } else if (se.getValue() < existingDistance) {
171                                                ret.put(se.getKey(), se.getValue());
172                                        }
173                                }                                                               
174                        }
175                }
176                return ret;
177        }
178        
179        /**
180         * 
181         * @param source
182         * @param target
183         * @param traversed
184         * @return Number of transitions/calls between the source and the target. -1 if target is not reacheable from the source. 
185         */
186        private static int distance(FlowElement<?> source, FlowElement<?> target, Set<FlowElement<?>> traversed) {
187                if (source == target) {
188                        return 0;
189                }
190                int distance = -1;
191                if (traversed.add(source)) {
192                        for (Transition output: source.getOutputs().values()) {
193                                int targetDistance = distance(output.getTarget(), target, traversed);
194                                if (targetDistance != -1) {
195                                        if (distance == -1) {
196                                                distance = targetDistance + 1;
197                                        } else {
198                                                distance = Math.min(distance, targetDistance + 1);
199                                        }
200                                }
201                        }
202                        for (Call call: source.getCalls().values()) {
203                                int targetDistance = distance(call.getTarget(), target, traversed);
204                                if (targetDistance != -1) {
205                                        if (distance == -1) {
206                                                distance = targetDistance + 1;
207                                        } else {
208                                                distance = Math.min(distance, targetDistance + 1);
209                                        }
210                                }
211                        }
212                }
213                return distance;
214        }
215                
216        @Override
217        protected void populateRepresentation(Diagram representation, FlowStateDiagramGenerator flowStateDiagramGenerator) {
218                if (getTarget().isPartition()) {
219                        super.populateRepresentation(representation, flowStateDiagramGenerator);
220                } else {
221                        flowStateDiagramGenerator.generateDiagram(getTarget(), representation, null);
222                }
223        }
224        
225        @Override
226        protected void resolve(
227                        Action action, 
228                        org.nasdanika.html.emf.EObjectActionResolver.Context context,
229                        ProgressMonitor progressMonitor) throws Exception {
230                super.resolve(action, context, progressMonitor);
231                
232                if (getTarget().eContainmentFeature() == FlowPackage.Literals.ACTIVITY_ENTRY__VALUE && getTarget().eContainer().eContainmentFeature() == FlowPackage.Literals.SERVICE_PROVIDER__SERVICES) {
233                        // Left content panel with children.
234                        NavigationPanel leftNavigation = action.getLeftNavigation();
235                        if (leftNavigation == null) {
236                                leftNavigation = AppFactory.eINSTANCE.createNavigationPanel();
237                                action.setLeftNavigation(leftNavigation);                       
238                                int subChildren = getTarget().getElements().values().stream().filter(Flow.class::isInstance).map(Flow.class::cast).map(Flow::getElements).mapToInt(Collection::size).sum();
239                                leftNavigation.setStyle(subChildren == 0 ? NavigationPanelStyle.AUTO : NavigationPanelStyle.TREE);
240                        }
241                        Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance;
242                        for (FlowElement<?> element: getTarget().getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) {
243                                leftNavigation.getItems().add(createItem(action, context, progressMonitor, element));
244                        }
245                }
246
247        }
248
249        private EObject createItem(
250                        Action action, 
251                        org.nasdanika.html.emf.EObjectActionResolver.Context context,
252                        ProgressMonitor progressMonitor, 
253                        FlowElement<?> element) throws Exception {
254                EObject item = renderValue(action, FlowPackage.Literals.FLOW__ELEMENTS, element, context, progressMonitor);
255                if (element instanceof Flow && item instanceof Label) {
256                        Predicate<FlowElement<?>> isPseudoState= PseudoState.class::isInstance;
257                        for (FlowElement<?> child: ((Flow) element).getElements().values().stream().filter(isPseudoState.negate()).sorted(FlowActionBuilder::compareFlowElements).collect(Collectors.toList())) {
258                                ((Label) item).getChildren().add(createItem(action, context, progressMonitor, child));
259                        }
260                }
261                return item;
262        }
263
264}