// Module    : $RCSfile: InteractionEventList.java,v $
// Version   : $Revision: 0.0 $  $Author: Patrick $  $Date: 04.12.2011 10:20:14 $
// Project   : TaskTreePerformanceTest
// Creation  : 2011 by Patrick
// Copyright : Patrick Harms, 2011

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import de.ugoe.cs.quest.eventcore.Event;

/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: $
 * @author 2011, last modified by $Author: $
 */
@SuppressWarnings("serial")
public class InteractionEventList extends ArrayList<Event> implements List<Event> {
    /** stores the events to be handled later in the order, they should be processed */
    private List<KeyEventPair> mEventPairs = new ArrayList<KeyEventPair>();

    /**
     * this method sorts interaction events for pressed keys. The reason is, that sometimes the
     * release of one key comes after the pressing of the succeeding key. This only makes sense for
     * shift, ctrl or alt keys, but not for usual characters. So the events are sorted.<br/>
     * Furthermore, this methods creates key released events in the specific case, where a key is
     * pressed for a long time. Here, many subsequent key pressed events for the same key can be
     * observed, for which there are no key released events.
     */
    @Override
    public boolean add(Event event) {
        if (event.getType() instanceof KeyInteraction) {
            for (int i = 0; i < mEventPairs.size(); i++) {
                KeyEventPair eventPair = mEventPairs.get(i);

                if (eventPair.process(event)) {
                    // handle all leading and completed event pairs, if any
                    while ((mEventPairs.size() > 0) && (mEventPairs.get(0).isComplete())) {
                        addEventPair(mEventPairs.get(0));
                        mEventPairs.remove(0);
                    }
                    return true;
                }
            }

            mEventPairs.add(new KeyEventPair(event));
            return true;
        }
        else {
            // any other event is simply added
            return super.add(event);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.util.ArrayList#add(int, java.lang.Object)
     */
    @Override
    public void add(int index, Event event) {
        List<Event> remaining = new ArrayList<Event>();

        while (index < super.size()) {
            remaining.add(super.remove(index));
        }

        add(event);
        addAll(remaining);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.util.ArrayList#addAll(java.util.Collection)
     */
    @Override
    public boolean addAll(Collection<? extends Event> events) {
        int initialSize = super.size();

        for (Event event : events) {
            add(event);
        }

        return initialSize != super.size();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.util.ArrayList#addAll(int, java.util.Collection)
     */
    @Override
    public boolean addAll(int index, Collection<? extends Event> events) {
        int initialSize = super.size();

        List<Event> remaining = new ArrayList<Event>();

        while (index < super.size()) {
            remaining.add(super.remove(index));
        }

        addAll(events);
        addAll(remaining);

        return initialSize != super.size();
    }

    /**
     *
     */
    private void addEventPair(KeyEventPair eventPair) {
        super.add(eventPair.mFirst);

        for (KeyEventPair child : eventPair.mChildren) {
            addEventPair(child);
        }

        super.add(eventPair.mSecond);
    }

    /**
     *
     */
    private static class KeyEventPair {
        
        /** the first key interaction event */
        private Event mFirst;

        /** the second key interaction event */
        private Event mSecond;

        /** the children, this event pair may have */
        private List<KeyEventPair> mChildren = new ArrayList<KeyEventPair>();

        /**
         *
         */
        private KeyEventPair(Event first) {
            if (!(first.getType() instanceof KeyPressed)) {
                throw new IllegalArgumentException
                  ("can only handle key pressed interaction events as first element of an" +
                   "event pair");
            }
            mFirst = first;
        }

        /**
         * sorts the event as the second of the pair or as the closing of this. Returns true, if
         * this matched, false else
         */
        private boolean process(Event event) {
            if (!(event.getType() instanceof KeyInteraction)) {
                throw new IllegalArgumentException("can only handle key interaction events");
            }

            if (this.isComplete()) {
                // do not process events, if there are no more events expected
                return false;
            }

            if ((event.getType() instanceof KeyReleased) &&
                (((KeyInteraction) mFirst.getType()).getKey() ==
                 ((KeyInteraction) event.getType()).getKey()) &&
                 (mSecond == null))
            {
                // this is the release of the represented key. Store it and notify the processing.
                mSecond = event;
                return true;
            }
            else if ((event.getType() instanceof KeyPressed) &&
                (((KeyInteraction) mFirst.getType()).getKey() ==
                 ((KeyInteraction) event.getType()).getKey()) &&
                 (mSecond == null))
            {
                // the key was pressed before it was released again. This happens, if the key
                // stays pressed for a long time. Generate a key released event but do not mark
                // the new event as processed
                mSecond = new Event
                    (new KeyReleased(((KeyInteraction) event.getType()).getKey()),
                     mFirst.getTarget());
                
                return false;
            }
            else if (((KeyInteraction) mFirst.getType()).getKey().isCombinationKey() &&
                     (((KeyInteraction) mFirst.getType()).getKey() !=
                      ((KeyInteraction) event.getType()).getKey()))
            {
                // this pair may have children. Let the event be processed by the children. If this
                // doesn't work, add the event as a new child pair, if it is a new key pressed

                for (KeyEventPair child : mChildren) {
                    if (child.process(event)) {
                        return true;
                    }
                }

                if (event.getType() instanceof KeyPressed) {
                    mChildren.add(new KeyEventPair(event));
                    return true;
                }
                else {
                    return false;
                }
            }
            else {
                // this pair may not have children
                return false;
            }
        }

        /**
         * @return
         */
        private boolean isComplete() {
            if ((mFirst != null) && (mSecond != null)) {
                for (KeyEventPair child : mChildren) {
                    if (!child.isComplete()) {
                        return false;
                    }
                }

                return true;
            }
            else {
                return false;
            }
        }
    }
}
