//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.plugin.uml;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.emf.common.util.EList;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.Transition;
import org.eclipse.uml2.uml.Vertex;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType;
import de.ugoe.cs.autoquest.plugin.uml.eventcore.UMLTransitionType;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @author Steffen Herbold
 */
public class UMLUtils {

    public static List<Event> createUMLTransitionSequence(List<Event> sequence, StateMachine stateMachine) {
        List<List<Transition>> matchingSequences = determineMatchingTransitionSequences(sequence, stateMachine);

        if (matchingSequences.size() != 1) {
            throw new RuntimeException("no unique match found; " + matchingSequences.size() +
                " matches");
        }
        List<Event> umlEventSequence = new LinkedList<>();
        for (Transition transition : matchingSequences.get(0)) {
            umlEventSequence.add(new Event(new UMLTransitionType(transition)));
        }
        return umlEventSequence;
    }
    
    public static void convertStateMachineToUsageProfile(Collection<List<Event>> sequences, StateMachine stateMachine) {
        // create state->outgoings hashmap
        Map<Vertex, Map<Transition,Integer>> stateMap = new HashMap<>();
        for( Region region : stateMachine.getRegions() ) {
            for( Vertex state : region.getSubvertices() ) {
                stateMap.put(state, new HashMap<Transition,Integer>());
            }
        }
        
        // create counters for each transition
        for( List<Event> sequence : sequences) {
            for( Event event : sequence ) {
                if( event.getType() instanceof UMLTransitionType ) {
                    Transition transition = ((UMLTransitionType) event.getType()).getTransition();
                    Map<Transition,Integer> transitionMap = stateMap.get(transition.getSource());
                    Integer value = transitionMap.get(transition);
                    if( value==null ) {
                        value = 0;
                    }
                    transitionMap.put(transition, value+1);
                } else {
                    throw new RuntimeException("Wrong event type. Only UMLTransitionType supported but was: " +
                            event.getType().getClass().getName());
                }
            }
        }

        // calculate probabilities
        for( Region region : stateMachine.getRegions() ) {
            for( Vertex state : region.getSubvertices() ) {
                Map<Transition,Integer> transitionMap = stateMap.get(state);
                int totalCount = 0;
                for( Entry<Transition,Integer> entry : transitionMap.entrySet() ) {
                    totalCount += entry.getValue();
                }
                if( totalCount!=0 ) {
                    for( Transition transition : state.getOutgoings() ) {
                        double prob = 0.0d;
                        if( transitionMap.containsKey(transition)) {
                            prob = ((double) transitionMap.get(transition))/totalCount;
                        }
                        Comment comment = transition.createOwnedComment();
                        comment.setBody("" + prob );
                    }
                } else {
                    // system has never been in this state, all transitions equally likely
                    int numOutgoings = state.getOutgoings().size();
                    for( Transition transition : state.getOutgoings() ) {
                        Comment comment = transition.createOwnedComment();
                        comment.setBody("" + (1.0d/numOutgoings) );
                    }
                }
            }
        }
    }
    
    public static List<List<Transition>> determineMatchingTransitionSequences(List<Event> sequence, StateMachine stateMachine) {
        EList<Region> regions = stateMachine.getRegions();
        EList<Vertex> states = null;
        for (Region region : regions) {
            if (states == null) {
                states = region.getSubvertices();
            }
            else {
                states.addAll(region.getSubvertices());
            }
        }
        List<Transition> allTransitions = new LinkedList<>();
        for (Vertex state : states) {
            allTransitions.addAll(state.getOutgoings());
        }

        List<List<Transition>> matchingSequences = null;
        List<Transition> currentTransitions = null;

        // first, we try to find a single unique transition that we can match using the method name
        for (Iterator<Event> eventIterator = sequence.iterator(); eventIterator.hasNext();) {
            Event event = eventIterator.next();
            if (event.getType() instanceof SOAPEventType) {
                SOAPEventType eventType = (SOAPEventType) event.getType();
                if (matchingSequences == null) {
                    matchingSequences = new LinkedList<>();
                    List<Transition> initialMatches = matchTransitions(allTransitions, eventType);
                    for (Transition transition : initialMatches) {
                        List<Transition> candidate = new LinkedList<>();
                        candidate.add(transition);
                        matchingSequences.add(candidate);
                    }
                    currentTransitions = initialMatches;
                }
                else {
                    List<List<Transition>> nextMatchingSequences = new LinkedList<>();
                    List<Transition> nextCurrentTransitions = new LinkedList<>();
                    Iterator<Transition> currentTransitionIterator = currentTransitions.iterator();
                    Iterator<List<Transition>> currentMatchingSequencesIterator =
                        matchingSequences.iterator();
                    while (currentTransitionIterator.hasNext()) {
                        Transition currentTransition = currentTransitionIterator.next();
                        List<Transition> currentMatch = currentMatchingSequencesIterator.next();

                        List<Transition> matches =
                            matchTransitions(currentTransition.getTarget().getOutgoings(),
                                             eventType);
                        if (matches.isEmpty()) {
                            throw new RuntimeException("no matches found");
                        }
                        for (Transition matchingTransition : matches) {
                            List<Transition> candidate = new LinkedList<>(currentMatch);
                            candidate.add(matchingTransition);
                            nextMatchingSequences.add(candidate);
                            nextCurrentTransitions.add(matchingTransition);
                        }
                    }
                    matchingSequences = nextMatchingSequences;
                    currentTransitions = nextCurrentTransitions;
                }
            }
            else {
                throw new RuntimeException("Wrong event type. Only UMLTransitionType supported but was: " +
                    event.getType().getClass().getName());
            }
        }
        return matchingSequences;
    }
    
    private static List<Transition> matchTransitions(List<Transition> transitions, SOAPEventType eventType)
    {
        List<Transition> matching = new LinkedList<>();
        for (Transition transition : transitions) {
            // String serviceName = transition.getName().split("\\.")[0]; // TODO service name check
            String methodName = transition.getName().split("\\.")[1];
            if (methodName.equals(eventType.getCalledMethod())) {
                matching.add(transition);
            }
        }
        return matching;
    }
    
}
