//-------------------------------------------------------------------------------------------------
// 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.userinteraction;

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

//-------------------------------------------------------------------------------------------------
/**
 * TODO comment
 *
 * @version $Revision: $ $Date: $
 * @author  2011, last modified by $Author: $
 */
//-------------------------------------------------------------------------------------------------
@SuppressWarnings("serial")
public class InteractionEventList
  extends ArrayList<InteractionEvent>
  implements List<InteractionEvent>
{
  /** 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(InteractionEvent event)
  {
    if (event.getInteraction() 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, InteractionEvent event)
  {
    List<InteractionEvent> remaining = new ArrayList<InteractionEvent>();
    
    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 InteractionEvent> events)
  {
    int initialSize = super.size();
    
    for (InteractionEvent 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 InteractionEvent> events)
  {
    int initialSize = super.size();
    
    List<InteractionEvent> remaining = new ArrayList<InteractionEvent>();
    
    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 interaction event */
    private InteractionEvent mFirst;
    
    /** the second interaction event */
    private InteractionEvent mSecond;

    /** the children, this event pair may have */
    private List<KeyEventPair> mChildren = new ArrayList<KeyEventPair>();
    
    //---------------------------------------------------------------------------------------------
    /**
     *
     */
    //---------------------------------------------------------------------------------------------
    private KeyEventPair(InteractionEvent first)
    {
      if (!(first.getInteraction() 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(InteractionEvent event)
    {
      if (!(event.getInteraction() 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.getInteraction() instanceof KeyReleased) &&
          (((KeyInteraction) mFirst.getInteraction()).getKey() ==
           ((KeyInteraction) event.getInteraction()).getKey()) &&
          (mSecond == null))
      {
        // this is the release of the represented key. Store it and notify the processing.
        mSecond = event;
        return true;
      }
      else if ((event.getInteraction() instanceof KeyPressed) &&
               (((KeyInteraction) mFirst.getInteraction()).getKey() ==
                ((KeyInteraction) event.getInteraction()).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 InteractionEvent
          (mFirst.getGUIElement(),
           new KeyReleased(((KeyInteraction) event.getInteraction()).getKey()));
        return false;
      }
      else if (((KeyInteraction) mFirst.getInteraction()).getKey().isCombinationKey() &&
               (((KeyInteraction) mFirst.getInteraction()).getKey() !=
                ((KeyInteraction) event.getInteraction()).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.getInteraction() 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;
      }
    }

  }
}
