package edu.uprm.walsaip.vte.core.messagebus;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

/**
 * Singleton class which coordinates message passing between objects. Listener register themselves with the MessageBus to recieve messages. Messages are sent by invoking the MessageBus.sendMessage methods.
 * @author Ricardo Veguilla
 * @see Message,MessageListener
 */
public class MessageBus {


	
	private static Map<MessageListener,List<String>> listenerRegistry = new HashMap<MessageListener,List<String>>();
	private static Map<String,List<MessageListener>> channelRegistry = new HashMap<String,List<MessageListener>>();
	
	/**
	 * Register a listener with a particular channel. Channels are generally the ModuleType of interest. In other words, a Camera implementation may want to register itself with a Camera channel.
	 * @param listener Object wishing to be notified of messages.
	 * @param channel Channel or module tpye to listen to.
	 */
	public synchronized static void addListener(MessageListener listener,String channel) {
		MessageBus.registerChannelToListner(channel, listener);
		MessageBus.registerListenerToChannel(listener, channel);
		
	}
	/**
	 * Remove the listener from the registry.
	 * @param listener Object to be removed.
	 */
	public synchronized static void removeListener(MessageListener listener) {
		
		for( String channel: listenerRegistry.get(listener)) {
			channelRegistry.get(channel).remove(listener);
		}
		listenerRegistry.remove(listener);
	}
	
	/**
	 * Send a message through the MessageBus
	 * @param m Message object to be delivered.
	 */
	public synchronized static void sendMessage(Message<?> m) {
		String channel = m.getChannel();
		if (channelRegistry.containsKey(channel)) {
			for (MessageListener l: channelRegistry.get(channel)) {
				l.processEvent(m);
			}
		}
	}
	
	  private static final void registerListenerToChannel(MessageListener listener, String channel) {
		  List<MessageListener> list = null;
          if (channelRegistry.containsKey(channel)) {
        	  list =  channelRegistry.get(channel);     
          }
          else {
              list = new Vector<MessageListener>();
              channelRegistry.put(channel, list);
          }
          list.add(listener);
	  }
	  private static final void registerChannelToListner(String channel, MessageListener listener) {
          List<String> list = null;
          if (listenerRegistry.containsKey(listener)) {
                  list = listenerRegistry.get(listener);     
          }
          else {
                  list = new Vector<String>();
                  listenerRegistry.put(listener, list);
          }
          list.add(channel);
	  }
	/**
	 * Utility method that sends a specific number of messages with an specific number of milliseconds between messages.
	 * @param m Message object to be delivered.
	 * @param delay delay between sending message in milliseconds
	 * @param count number of messages to sent
	 */  
	  public static void sendFixRateMessage(final Message<?> m, final int delay, final int count) {
		  
		  
		  TimerTask task = new TimerTask() {
			int count = 0;
			@Override
			public void run() {
				
				if (this.count < count) {
					MessageBus.sendMessage(m);
					this.count++;
				}
				else
					this.cancel();
			}  
		  };

		  if (task != null) {
			  Timer timer = new Timer();
			  timer.scheduleAtFixedRate(task, 0, delay);
		  }
	  }
	  /**
	   * Utility method that sends a message after an specific number of milliseconds
	   * @param m  Message object to be delivered.
	   * @param delay time delay before sending the message in milliseconds
	   */
	  public static void sendTimeSequenceMessage(final Message<?> m, final int delay) {
		  
		  final Message<?> startMsg = m.getStartMsg();
		  final Message<?> stopMsg = m.getStopMsg();
		  
		  MessageBus.sendMessage(startMsg);
		  TimerTask task = new TimerTask() {
			
			@Override
			public void run() {
				
				MessageBus.sendMessage(stopMsg);
			}  
		  };

		  if (task != null) {
			  Timer timer = new Timer();
			  timer.schedule(task, 0, delay);
		  }
	  }
	  
}
