
package de.ugoe.cs.quest.eventcore.gui;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.logging.Level;

import de.ugoe.cs.quest.eventcore.Event;
import de.ugoe.cs.tasktree.keyboardmaps.VirtualKey;
import de.ugoe.cs.util.console.Console;

/**
 * 
 * <p>
 * TODO comment
 * </p>
 * 
 * @version $Revision: $ $Date: Sep 4, 2012$
 * @author 2012, last modified by $Author: sherbold$
 */
public class KeyInteractionSorter {

    /**
     * <p>
     * Describes the clean-up mode.
     * </p>
     * 
     * @version $Revision: $ $Date: Sep 3, 2012$
     * @author 2012, last modified by $Author: sherbold$
     */
    public static enum CleanupMode {
        REMOVAL, ADDITION
    };

    private final CleanupMode mode;

    public KeyInteractionSorter() {
        this(CleanupMode.ADDITION);
    }

    public KeyInteractionSorter(CleanupMode mode) {
        this.mode = mode;
    }

    public List<Event> sortKeyInteractions(final List<Event> sequence) {
        List<Event> sortedSequence = new LinkedList<Event>(sequence);

        handleIncompleteKeyPairs(sortedSequence);
        sortCombinationKeyPairs(sortedSequence);

        return sortedSequence;
    }

    private void sortCombinationKeyPairs(List<Event> sequence) {
        LinkedList<VirtualKey> pressedCombinationKeys = new LinkedList<VirtualKey>();

        for (int i = 0; i < sequence.size(); i++) {
            Event event = sequence.get(i);
            if (event.getType() instanceof KeyPressed) {
                final VirtualKey key = ((KeyPressed) event.getType()).getKey();
                if (key.isCombinationKey()) {
                    pressedCombinationKeys.add(key);
                }
            }
            if (event.getType() instanceof KeyReleased) {
                final VirtualKey key = ((KeyReleased) event.getType()).getKey();
                if (key.isCombinationKey()) {
                    /*
                     * if( pressedCombinationKeys.isEmpty() ) { Console.traceln(Level.INFO, "" + i);
                     * for( int j=i-30 ; j<=i ; j++ ) { Console.traceln(Level.INFO,
                     * sequence.get(i).toString()); } }
                     */
                    if (key.equals(pressedCombinationKeys.getLast())) {
                        pressedCombinationKeys.removeLast();
                    }
                    else {
                        // look-ahead to find new position
                        int offset;
                        for (offset = 1; offset + i < sequence.size(); offset++) {
                            Event lookaheadEvent = sequence.get(i + offset);
                            if (lookaheadEvent.getType() instanceof KeyReleased) {
                                if (((KeyReleased) lookaheadEvent.getType()).getKey()
                                    .equals(pressedCombinationKeys.getLast()))
                                {
                                    break;
                                }
                            }
                        }
                        sequence.add(i + offset + 1, event);
                        sequence.remove(i);
                        i--;
                    }
                }
            }
        }
    }

    private void handleIncompleteKeyPairs(List<Event> sequence) {
        Set<VirtualKey> pressedKeys = new HashSet<VirtualKey>();
        int firstPressedIndex = -1;

        Set<VirtualKey> pressedCombinationKeysSession = new HashSet<VirtualKey>();

        for (int i = 0; i < sequence.size(); i++) {
            Event event = sequence.get(i);
            if (event.getType() instanceof KeyboardFocusChange) {
                if (firstPressedIndex != -1) {
                    sequence.remove(i);
                    sequence.add(firstPressedIndex, event);
                }
            }

            if (event.getType() instanceof KeyPressed) {
                if (pressedKeys.isEmpty()) {
                    firstPressedIndex = i;
                }
                VirtualKey key = ((KeyPressed) event.getType()).getKey();
                if (!key.isCombinationKey()) {
                    ListIterator<Event> iter = sequence.listIterator(i);
                    iter.next();
                    iter.set(new Event(new KeyTyped(key), event.getTarget()));
                }
                else {
                    pressedCombinationKeysSession.add(key);
                    if (pressedKeys.contains(key)) {
                        sequence.remove(i);
                        i--;
                    }
                }
                pressedKeys.add(key);
            }
            if (event.getType() instanceof KeyReleased) {
                VirtualKey key = ((KeyReleased) event.getType()).getKey();
                if (!key.isCombinationKey()) {
                    if (pressedKeys.contains(key)) {
                        sequence.remove(i);
                        i--;
                    }
                    else {
                        // found KeyReleased event without KeyPressed
                        switch (mode)
                        {
                            case REMOVAL:
                                sequence.remove(i);
                                i--;
                                break;
                            case ADDITION:
                                ListIterator<Event> iter = sequence.listIterator(i);
                                iter.next();
                                iter.set(new Event(new KeyTyped(key), event.getTarget()));
                                break;
                            default:
                                throw new AssertionError(
                                                         "reached source code that should be unreachable");
                        }
                    }
                }
                else {
                    if (!pressedKeys.contains(key)) {
                        if (pressedCombinationKeysSession.contains(key)) {
                            Console.traceln(Level.SEVERE, "Found a " + key +
                                " KeyReleased event without a KeyPressed event." +
                                "The event will be dropped and the session is possibly faulty.");
                            sequence.remove(i);
                            i--;
                        }
                        else {
                            Console
                                .traceln(Level.SEVERE,
                                         "Found a " +
                                             key +
                                             " KeyReleased event without a KeyPressed event." +
                                             "Since no KeyPressed of key " +
                                             key +
                                             " has been part of the session " +
                                             "till now, we assume that the key has been pressed since the beginning " +
                                             "of the session and add a KeyPressed event for " +
                                             key + " to the start " + "of the session.");
                            sequence.add(0, new Event(new KeyPressed(key), event.getTarget()));
                            i++;
                        }
                    }
                }
                pressedKeys.remove(key);
                if (pressedKeys.isEmpty()) {
                    firstPressedIndex = -1;
                }
            }
        }
        if (!pressedKeys.isEmpty()) {
            Console
                .traceln(Level.WARNING,
                         "There was probably a failure during the handling of incomplete key event pairs.");
        }
    }
}
