/*****************************************************************
JADE - Java Agent DEvelopment Framework is a framework to develop 
multi-agent systems in compliance with the FIPA specifications.
Copyright (C) 2000 CSELT S.p.A. 

GNU Lesser General Public License

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, 
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA  02111-1307, USA.
 *****************************************************************/

package jade.core;


//#APIDOC_EXCLUDE_FILE

// We use Hashtables since we need synchronized access
import java.util.Hashtable; 

/**
   Processes JADE kernel-level commands, managing a filter/sink system
   to support dynamically configurable platform services.

   @author Giovanni Rimassa - FRAMeTech s.r.l.
   @author Giovanni Caire - TILAB
 */
class CommandProcessor {
	private Filter firstDownFilter;
	private Filter firstUpFilter;

	private final Hashtable downSinks;
	private final Hashtable upSinks;
	private SinksFilter lastDownFilter;
	private SinksFilter lastUpFilter;


	public CommandProcessor() {
		downSinks = new Hashtable(4);
		upSinks = new Hashtable(4);
		lastDownFilter = new SinksFilter(downSinks);
		lastUpFilter = new SinksFilter(upSinks);
		firstDownFilter = lastDownFilter;
		firstUpFilter = lastUpFilter;
	}


	/**
       Add a new filter to the filter chain.

       @param f The new filter to add.
       @param direction Whether to add this filter to the outgoing or
       incoming filter chain.
	 */
	public void addFilter(Filter f, boolean direction) {
		if(direction == Filter.INCOMING) {
			firstUpFilter = insertFilter(f, firstUpFilter);
		}
		else {
			firstDownFilter = insertFilter(f, firstDownFilter);
		}
	}

	/**
       Insert a Filter in the chain started by <code>first</code>.
       @return The Filter at the beginning of the chain (may have
       changed)
	 */
	private synchronized Filter insertFilter(Filter f, Filter first) {
		if (f != null) {
			if (f.getPreferredPosition() < first.getPreferredPosition()) {
				// Insert at the beginning of the filter chain
				f.setNext(first);
				return f;
			}
			else {
				Filter current = first;
				Filter next = current.getNext();
				while (true) {
					if (f.getPreferredPosition() < next.getPreferredPosition()) {
						// Insert between current and next
						f.setNext(next);
						current.setNext(f);
						break;
					}
					else {
						current = next;
						next = current.getNext();
					}
				}
			}
		}
		return first;
	}

	/**
       Remove a filter from the filter chain.

       @param f The filter to remove.
	 */
	public void removeFilter(Filter f, boolean direction) {
		if(direction == Filter.INCOMING) {
			firstUpFilter = removeFilter(f, firstUpFilter);
		}
		else {
			firstDownFilter = removeFilter(f, firstDownFilter);
		}
	}

	/**
       Remove a Filter from the chain started by <code>first</code>.
       @return The Filter at the beginning of the chain (may have
       changed)
	 */
	private synchronized Filter removeFilter(Filter f, Filter first) {
		if (first != null) {
			if (f != null) {
				if (f.equals(first)) {
					// Remove the first element of the filter chain
					return first.getNext();
				}
				else {
					Filter current = first;
					Filter next = current.getNext();
					while (true) {
						if (f.equals(next)) {
							// Remove next
							current.setNext(next.getNext());
							break;
						}
						else {
							current = next;
							next = current.getNext();
						}
					}
				}
			}
		}
		return first;
	}

	/**
       Register a command sink object to handle the given vertical commmand set.

       @param snk A service-specific implementation of the
       <code>Sink</code> interface, managing a set of vertical
       commands.
       @param side One of the two constants
       <code>Sink.COMMAND_SOURCE</code> or
       <code>Sink.COMMAND_TARGET</code>, to state whether this sink
       will handle locally issued commands or commands incoming from
       remote nodes.
       @param commandNames An array containing all the names of the
       vertical commands the new sink wants to handle.
       @throws ServiceException If some other sink is already
       registered for a member of the <code>commandNames</code> set.
	 */
	public synchronized void registerSink(Sink snk, boolean side, String serviceName) throws ServiceException {

		Hashtable sinks;
		if(side == Sink.COMMAND_SOURCE) {
			sinks = downSinks;
		}
		else {
			sinks = upSinks;
		}
		sinks.put(serviceName, snk);
	}

	/**
       Deregister a sink that is currently handling a given set of vertical commands.

       @param side One of the two constants
       <code>Sink.COMMAND_SOURCE</code> or
       <code>Sink.COMMAND_TARGET</code>, to state whether the sink to
       be removed is handling locally issued commands or commands
       incoming from remote nodes.
       @param commandNames An array containing all the names of the
       vertical commands currently handled by the sink to be removed.
       @throws ServiceException If a member of the
       <code>commandNames</code> set has no associated command sink.
	 */
	public synchronized void deregisterSink(boolean side, String serviceName) throws ServiceException {

		Hashtable sinks;
		if(side == Sink.COMMAND_SOURCE) {
			sinks = downSinks;
		}
		else {
			sinks = upSinks;
		}

		sinks.remove(serviceName);
	}

	/**
       Process an outgoing command object, carrying out the action it
       represents.  This method is not synchronized and it must be
       reentrant and as fast as possible, because it is going to be a
       bottleneck of the kernel call flow.

       @param cmd The <code>VerticalCommand</code> object to process.
	 */
	public Object processOutgoing(VerticalCommand cmd) {
		firstDownFilter.filter(cmd);
		return cmd.getReturnValue();
	}

	/**
       Process an incoming command object, carrying out the action it
       represents.  This method is not synchronized and it must be
       reentrant and as fast as possible, because it is going to be a
       bottleneck of the kernel call flow.

       @param cmd The <code>VerticalCommand</code> object to process.
	 */
	public Object processIncoming(VerticalCommand cmd) {
		firstUpFilter.filter(cmd);
		return cmd.getReturnValue();
	}

	/**
	  Inner class SinksFilter.
	  This class makes the set of sinks in a given direction
	  look like a single filter that always stands at the end of 
	  the filter chain.
	 */
	private class SinksFilter extends Filter {
		private Hashtable mySinks;

		private SinksFilter(Hashtable ht) {
			mySinks = ht;
			preferredPosition = LAST+1;
		}

		protected boolean accept(VerticalCommand cmd) {
			String service = cmd.getService();
			if (service != null) {
				Sink s = (Sink) mySinks.get(service);
				if (s != null) {
					//System.out.println("Sink "+s+" consuming command "+cmd.getName());
					s.consume(cmd);
				}
			}
			// Do not propagate since this is always the last filter in the chain
			return false;
		}
	} // END of inner class SinksFilter	
}
