package jist.swans.misc;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import jist.runtime.JistAPI;

/**
 * Abstract base class for all event. If you want to add an event, derive from
 * this class, create an instance of your event class only once. Define a
 * custom handle() method with all apropriate parameters, store the parameters
 * internally and call Event.handle() for processing.
 *
 * Any potential handler must call Event.addHandler() to subscribe to your
 * event.
 *
 * @author kurth
 */
public abstract class Event {

  private static Logger log = Logger.getLogger(Event.class.getName());

  /**
   * Interface for all event handlers. Whenever an event occurs, the handler
   * is called via handle(). The handler must not alter the event data.
   *
   * @author kurth
   */
  public interface Handler {
    void handle(final Event event);
  }

  /** maps event classes to list of handlers */
  private static Map mapClassHandlers = new HashMap();

  /** maps event classes to list of event instances */
  private static Map mapEvents = new HashMap();

  /** indicator whether the event should be processed */
  private boolean enabled;

  /** indicator whether the event should be processed */
  private boolean active;

  /** local list of handlers for this event */
  private Handler[] handlers;

  /** point in time */
  public long time;

  public int nodeId;

  /**
   * Constructs an event.
   */
  protected Event() {
    this.enabled = true;

    // Add instance to list of events for this class
    List lstEvents = (List) mapEvents.get(this.getClass());
    if (null == lstEvents)
      lstEvents = new ArrayList();
    lstEvents.add(this);
    mapEvents.put(this.getClass(), lstEvents);

    // Update handler list
    updateHandlers();
  }

  protected Event(boolean enabled) {
    this.enabled = enabled;
  }

  private void updateHandlers() {
    List listHandlers = (List) mapClassHandlers.get(this.getClass());
    if (null != listHandlers)
      this.handlers = (Handler[]) listHandlers.toArray(new Handler[0]);
    this.active = (null != this.handlers && 0 != this.handlers.length);
  }

  /**
   * called by subclasses to inform all handlers about this event.
   */
  protected final void handle(int nodeId) {
    this.time = JistAPI.getTime();
    this.nodeId = nodeId;
    for (int i = 0; i < handlers.length; i++) {
      Handler handler = (Handler) handlers[i];
      handler.handle(this);
    }
  }

  /**
   * Indicates whether this event should be processed.
   * @return whether this event should be processed.
   */
  public final boolean isActive() {
    return active && enabled;
  }

  /**
   * Installs a custom handler for the given event class.
   *
   * @param eventClass the event class to subscribe to.
   * @param handler the handler to process the event.
   */
  public static void addHandler(Class eventClass, Handler handler) {
    if (eventClass.isAssignableFrom(Event.class))
      throw new RuntimeException("Given event class must be derived from Event");

    // Add handler to the list of handlers for the given class
    List lstHandlers = (List) mapClassHandlers.get(eventClass);
    if (null == lstHandlers)
      lstHandlers = new ArrayList();

    if (lstHandlers.contains(handler))
      log.error("Handler " + handler + " registered more than once for event " +
          eventClass);

    lstHandlers.add(handler);
    mapClassHandlers.put(eventClass, lstHandlers);

    // update all class instances
    List lstEvents = (List) mapEvents.get(eventClass);
    for (int i = 0; lstEvents != null && i < lstEvents.size(); i++) {
      Event event = (Event) lstEvents.get(i);
      event.updateHandlers();
    }
  }

  public boolean isEnabled() {
    return enabled;
  }

  /**
   * Turns on event processing for this event.
   *
   * @param enabled whether to turn on the event
   */
  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }
}
