package org.rsbot.event;

import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;

import org.rsbot.event.events.RSEvent;
import org.rsbot.event.listeners.AllListener;
import org.rsbot.event.listeners.CharacterMovedListener;
import org.rsbot.event.listeners.PaintListener;
import org.rsbot.event.listeners.ServerMessageListener;
import org.rsbot.event.listeners.TextPaintListener;

public class EventMulticaster implements AllListener {
	public static final long CHARACTER_MOVED_EVENT = 0x400;
	public static final long FOCUS_EVENT = 0x10;
	public static final long FOCUS_EVENT_USER = 0x200;
	public static final long KEY_EVENT = 0x08;
	public static final long KEY_EVENT_USER = 0x100;

	public static final long MOUSE_EVENT = 0x01;
	public static final long MOUSE_EVENT_USER = 0x20;
	public static final long MOUSE_MOTION_EVENT = 0x02;
	public static final long MOUSE_MOTION_EVENT_USER = 0x40;
	public static final long MOUSE_WHEEL_EVENT = 0x04;

	public static final long MOUSE_WHEEL_EVENT_USER = 0x80;
	public static final long PAINT_EVENT = 0x1000;
	public static final long SERVER_MESSAGE_EVENT = 0x800;
	public static final long TEXT_PAINT_EVENT = 0x2000;

	private static final Object treeLock = new Object();

	/**
	 * Gets the default mask for an event listener.
	 * */
	public static long getDefaultMask(final EventListener el) {
		if (el instanceof EventMulticaster) {
			final EventMulticaster em = (EventMulticaster) el;
			return em.enabledMask;
		}
		int mask = 0;
		if (el instanceof MouseListener) {
			mask |= EventMulticaster.MOUSE_EVENT;
		}
		if (el instanceof MouseMotionListener) {
			mask |= EventMulticaster.MOUSE_MOTION_EVENT;
		}
		if (el instanceof MouseWheelListener) {
			mask |= EventMulticaster.MOUSE_WHEEL_EVENT;
		}
		if (el instanceof KeyListener) {
			mask |= EventMulticaster.KEY_EVENT;
		}
		if (el instanceof FocusListener) {
			mask |= EventMulticaster.FOCUS_EVENT;
		}

		if (el instanceof CharacterMovedListener) {
			mask |= EventMulticaster.CHARACTER_MOVED_EVENT;
		}
		if (el instanceof ServerMessageListener) {
			mask |= EventMulticaster.SERVER_MESSAGE_EVENT;
		}
		if (el instanceof PaintListener) {
			mask |= EventMulticaster.PAINT_EVENT;
		}
		if (el instanceof TextPaintListener) {
			mask |= EventMulticaster.TEXT_PAINT_EVENT;
		}

		if (el instanceof AllListener) {
			mask = -1;
		}
		return mask;
	}

	/**
	 * Gets the default mask for an event.
	 * */
	public static long getDefaultMask(final EventObject e) {
		long mask = 0;
		if (e instanceof MouseEvent) {
			final MouseEvent me = (MouseEvent) e;
			switch (me.getID()) {
				case MouseEvent.MOUSE_PRESSED:
				case MouseEvent.MOUSE_RELEASED:
				case MouseEvent.MOUSE_CLICKED:
				case MouseEvent.MOUSE_ENTERED:
				case MouseEvent.MOUSE_EXITED:
					mask |= EventMulticaster.MOUSE_EVENT;
					break;

				case MouseEvent.MOUSE_MOVED:
				case MouseEvent.MOUSE_DRAGGED:
					mask |= EventMulticaster.MOUSE_MOTION_EVENT;
					break;

				case MouseEvent.MOUSE_WHEEL:
					mask |= EventMulticaster.MOUSE_WHEEL_EVENT;
					break;
			}
		} else if (e instanceof FocusEvent) {
			final FocusEvent fe = (FocusEvent) e;
			switch (fe.getID()) {
				case FocusEvent.FOCUS_GAINED:
				case FocusEvent.FOCUS_LOST:
					mask |= EventMulticaster.FOCUS_EVENT;
					break;
			}
		} else if (e instanceof KeyEvent) {
			final KeyEvent ke = (KeyEvent) e;
			switch (ke.getID()) {
				case KeyEvent.KEY_TYPED:
				case KeyEvent.KEY_PRESSED:
				case KeyEvent.KEY_RELEASED:
					mask |= EventMulticaster.KEY_EVENT;
					break;
			}
		} else if (e instanceof RSEvent) {
			final RSEvent rse = (RSEvent) e;
			mask |= rse.getMask();
		}
		return mask;
	}

	private long enabledMask;
	private final List<Long> listenerMasks = new ArrayList<Long>();

	private final List<EventListener> listeners = new ArrayList<EventListener>(5);

	private EventMulticaster parent;

	/**
	 * Adds the listener to the tree with a default mask.
	 * */
	public void addListener(final EventListener el) {
		long mask = 0;
		if (el instanceof EventMulticaster) {
			final EventMulticaster em = (EventMulticaster) el;
			mask = em.enabledMask;
		} else {
			mask = EventMulticaster.getDefaultMask(el);
		}
		addListener(el, mask);
	}

	/**
	 * Adds the listener with the specified mask. If its an EventMulticaster the
	 * specified mask will be ignored.
	 * */
	public void addListener(final EventListener el, long mask) {
		synchronized (EventMulticaster.treeLock) {
			if (listeners.contains(el))
				return;

			if (el instanceof EventMulticaster) {
				final EventMulticaster em = (EventMulticaster) el;
				addMulticaster(em);
				mask = em.enabledMask;
			} else {
				listeners.add(el);
			}
			listenerMasks.add(mask);
			// log.info(("Added mask: " + mask + " " +
			// listenerMasks.get(listenerMasks.size()-1));
			cleanMasks();
		}
	}

	/**
	 * Ensures the multicaster tree is clean and adds it.
	 * 
	 * Has to hold tree lock.
	 * */
	private void addMulticaster(final EventMulticaster em) {
		if (em.parent != null)
			throw new IllegalArgumentException("adding multicaster to multiple multicasters");
		for (EventMulticaster cur = this; cur != null; cur = cur.parent) {
			if (cur == em)
				throw new IllegalArgumentException("adding multicaster's parent to itself");
		}
		listeners.add(em);
		em.parent = this;
	}

	/**
	 * Walks up the tree as necessary reseting the masks to the minimum.
	 * 
	 * Has to hold treelock.
	 * */
	private void cleanMasks() {
		for (EventMulticaster cur = this; cur != null; cur = cur.parent) {
			int mask = 0;
			final int len = cur.listeners.size();
			for (int i = 0; i < len; i++) {
				final EventListener el = cur.listeners.get(i);
				long m = cur.listenerMasks.get(i);
				if (el instanceof EventMulticaster) {
					final EventMulticaster em = (EventMulticaster) el;
					if (em.enabledMask != m) {
						m = em.enabledMask;
						cur.listenerMasks.set(i, m);
					}
				}
				mask |= m;
			}
			if (mask == cur.enabledMask) {
				break;
			}
			cur.enabledMask = mask;
		}
	}

	/**
	 * Fires an event to all applicable listeners.
	 * */
	public void fireEvent(final EventObject e) {
		fireEvent(e, EventMulticaster.getDefaultMask(e));
	}

	/**
	 * Fires an event to all listeners, restricted by the mask.
	 * */
	public void fireEvent(final EventObject e, final long mask) {
		synchronized (EventMulticaster.treeLock) {
			final int len = listeners.size();
			// log.info(("L: " + listenerMasks.toString());
			for (int i = 0; i < len; i++) {
				final long m = listenerMasks.get(i);
				// if (mask != PAINT_EVENT && mask != TEXT_PAINT_EVENT)
				// log.info(("" + m + " " + mask);
				if ((m != 12288) && ((m & mask) == 0)) {
					continue;
				}
				// if (mask != PAINT_EVENT && mask != TEXT_PAINT_EVENT)
				// log.info(("HIT");
				final EventListener el = listeners.get(i);
				if (el instanceof AllListener) {
					final AllListener em = (AllListener) el;
					if (em.isEnabled(mask)) {
						em.fireEvent(e, mask);
					}
				} else if (e instanceof MouseEvent) {
					final MouseEvent me = (MouseEvent) e;
					switch (me.getID()) {
						case MouseEvent.MOUSE_PRESSED:
							((MouseListener) el).mousePressed(me);
							break;
						case MouseEvent.MOUSE_RELEASED:
							((MouseListener) el).mouseReleased(me);
							break;
						case MouseEvent.MOUSE_CLICKED:
							((MouseListener) el).mouseClicked(me);
							break;
						case MouseEvent.MOUSE_ENTERED:
							((MouseListener) el).mouseEntered(me);
							break;
						case MouseEvent.MOUSE_EXITED:
							((MouseListener) el).mouseEntered(me);
							break;

						case MouseEvent.MOUSE_MOVED:
							((MouseMotionListener) el).mouseMoved(me);
							break;
						case MouseEvent.MOUSE_DRAGGED:
							((MouseMotionListener) el).mouseDragged(me);
							break;

						case MouseEvent.MOUSE_WHEEL:
							((MouseWheelListener) el).mouseWheelMoved((MouseWheelEvent) me);
							break;
					}
				} else if (e instanceof FocusEvent) {
					final FocusEvent fe = (FocusEvent) e;
					switch (fe.getID()) {
						case FocusEvent.FOCUS_GAINED:
							((FocusListener) el).focusGained(fe);
							break;
						case FocusEvent.FOCUS_LOST:
							((FocusListener) el).focusLost(fe);
							break;
					}
				} else if (e instanceof KeyEvent) {
					final KeyEvent ke = (KeyEvent) e;
					switch (ke.getID()) {
						case KeyEvent.KEY_TYPED:
							((KeyListener) el).keyTyped(ke);
							break;
						case KeyEvent.KEY_PRESSED:
							((KeyListener) el).keyPressed(ke);
							break;
						case KeyEvent.KEY_RELEASED:
							((KeyListener) el).keyReleased(ke);
							break;
					}
				} else if (e instanceof RSEvent) {
					final RSEvent rse = (RSEvent) e;
					rse.dispatch(el);
				}
			}
		}
	}

	/**
	 * Gets the masks enabled for this multicaster.
	 * */
	public long getEnabledMask() {
		return enabledMask;
	}

	/**
	 * Returns an unmodifiable list of the backing list of listeners.
	 * */
	public List<EventListener> getListeners() {
		return Collections.unmodifiableList(listeners);
	}

	/**
	 * Returns if the mask is enabled on this multicaster.
	 * */
	public final boolean isEnabled(final long mask) {
		return (enabledMask & mask) != 0;
	}

	/**
	 * Removes a listener. Cleans up the masks.
	 * */
	public void removeListener(EventListener el) {
		synchronized (EventMulticaster.treeLock) {
			final int idx = listeners.indexOf(el);
			if (idx == -1)
				return;
			el = listeners.remove(idx);
			if (el instanceof EventMulticaster) {
				final EventMulticaster em = (EventMulticaster) el;
				em.parent = null;
			}
			listenerMasks.remove(idx);
			cleanMasks();
		}
	}
}
