/***************************************************************************
    Copyright          : (C) 2002 by Neoworks Limited. All rights reserved
    URL                : http://www.neoworks.com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * TrackSourcePipelineElementSkeleton.java
 *
 * Created on 02 May 2002, 14:48
 */

package com.neoworks.jukex.tracksource;

import com.neoworks.jukex.tracksource.filter.*;
import com.neoworks.jukex.*;

import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import org.apache.log4j.Category;

/**
 * PipelineElement that filters Tracks out of the pipeline according to some scheme.
 *
 * @author Nigel Atkinson <a href="mailto:nigel@neoworks.com">nigel@neoworks.com</a>
 */
public class FilterPipelineElement extends TrackSourcePipelineElementSkeleton
{
	private static final Category log = Category.getInstance(FilterPipelineElement.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();

	private List filters;

	/**
	 * Public constructor
	 */
	public FilterPipelineElement()
	{
		super("Filter");
		filters = new ArrayList();
	}

	/**
	 * Add a TrackFilter
	 *
	 * @param f The TrackFilter to add
	 */
	public void addFilter(TrackFilter f)
	{
		filters.add(f);
	}

	/**
	 * Remove a TrackFilter
	 *
	 * @param index The index of the trackfilter to remove
	 */
	public void removeFilter(int index)
	{
		filters.remove(index);
	}

	/**
	 * Get an iterator on the TrackFilter List
	 *
	 * @return An Iterator on the TrackFilter List
	 */
	public Iterator iterator()
	{
		return filters.iterator();
	}

	/**
	 * Get the index of a TrackFilter
	 *
	 * @param o The object (TrackFilter) to get the index of
	 * @return The index
	 */
	public int indexOf(Object o)
	{
		return filters.indexOf(o);
	}

	/**
	 * Get the current state of this PipelineElement for persisting
	 *
	 * @return a String keyed Map of objects representing the state of 
	 * this PipelineElement, e.g. for a Playlist this would be information
	 * allowing the reconstruction of the current playlist queue.
	 */
	public Map getState()
	{
		Map retVal = new HashMap();
		retVal.put("name",name);
		retVal.put("filters",filters);
		retVal.put("enabled",new Boolean(isEnabled()));
		return retVal;
	}

	/**
	 * Load the configuration of this PipelineElement
	 *
	 * @param state a Map of values keyed by Strings representing the state of this PipelineElement.
	 * @return success
	 */
	public boolean setState( Map state )
	{
		filters = (List)state.get("filters");
		if (filters == null)
		{
			log.warn("Restored filter list is null");
			filters = new ArrayList();
		}
		if (((Boolean)state.get("enabled")).booleanValue())
		{
			enable();
		} else {
			disable();
		}
		return true;
	}

	/**
	 * Get a track at random
	 *
	 * @return a Track object
	 */
	public Track getNextTrack()
	{
		if (isEnabled())
		{
			return getNextFilteredTrack();
		} else {
			return delegateGetNextTrack();
		}
	}

	private Track getNextFilteredTrack()
	{
		Track retVal = null;
		boolean matches = false;
		do {
			retVal = delegateGetNextTrack();
			matches = applyFilters(retVal);
		} while (retVal != null && matches);
		return retVal;
	}

	private boolean applyFilters(Track t)
	{
		boolean retVal = false;
		if ( filters != null )
		{
			Iterator i = filters.iterator();
			TrackFilter f = null;

			while (i.hasNext())
			{
				f = (TrackFilter)i.next();
				retVal |= f.match(t);
			}
		}
		return retVal;
	}

	private List peekFilteredTracks(int count)
	{
		List retVal = null;
		Iterator i = null;
		int peekCount = count;
		boolean runOutOfTracksToPeek = false;

		if (isEnabled())
		{
			do {
				retVal = delegatePeekTracks(peekCount);
				if (retVal.size() != peekCount)
				{
					if (logDebugEnabled) log.debug("Not enough tracks in the pipeline to peek");
					runOutOfTracksToPeek = true;
				}
				peekCount = count;
				i = retVal.iterator();
				while (i.hasNext())
				{
					if (applyFilters((Track)i.next()))
					{
						i.remove();
						peekCount++;
					}
				}
					if (runOutOfTracksToPeek)
				{
					break;
				}
			} while (retVal.size() < count);
		} else {
			retVal = delegatePeekTracks(count);
		}
		return retVal;
	}

	/**
	 * Peek up the track list
	 *
	 * @param count The number of tracks to peek
	 * @return A List of Track objects
	 */
	public List peekTracks(int count)
	{
		return peekFilteredTracks(count);
	}

	/**
	 * Clone this FilterPipelineElement
	 *
	 * @return A clone of this FilterPipelineElement
	 */
	public Object clone()
	{
		return new FilterPipelineElement();
	}
	
	/**
	 * Return a String describing what, in general terms, this TrackSource does
	 *
	 * @return A String
	 */
	public String getDescription()
	{
		return "FilterPipelineElement: Filters out tracks according to user specified criteria";
	}
	
	/**
	 * Return a String summarising the configuration of the task the source is
	 * performing
	 *
	 * @return A String
	 */
	public String getSummary()
	{
		if ( super.isEnabled() )
		{
			StringBuffer sb = new StringBuffer();
			sb.append( "Currently filtering on the following:\n" );
			if ( this.filters != null )
			{
				Iterator i = this.filters.iterator();
				if ( !i.hasNext() )
				{
					sb.append("No filters");
				}
				while ( i.hasNext() )
				{
					sb.append( i.next() ).append( '\n' );
				}
			}
			else
			{
				sb.append( "No filters");
			}
			
			return sb.toString();
		}
		else
		{
			return "Disabled";
		}
	}
}
