/***************************************************************************
    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.                                   *
 *                                                                         *
 ***************************************************************************/

/*
 * RoundRobinPipelineElement.java
 *
 * Created on 20 May 2002, 14:29
 */

package com.neoworks.jukex.tracksource;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import com.neoworks.jukex.Track;
import com.neoworks.jukex.sqlimpl.InMemoryPlaylist;

import org.apache.log4j.Category;

/**
 * This pipeline element takes several InMemoryPlaylist track sources and interleaves
 * their requests.
 *
 * @author Nick Vincent <a href="mailto:nick@neoworks.com">nick@neoworks.com</a>
 */
public class RoundRobinPipelineElement extends TrackSourcePipelineElementSkeleton
{
	private static final Category log = Category.getInstance(RoundRobinPipelineElement.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();
	
	private List trackSources = null;
	private int trackSourceIndex = 0;
	
	/**
	 * Creates a new instance of RoundRobinPipelineElement
	 */
	public RoundRobinPipelineElement()
	{
		super("Round Robin");
		trackSources = new LinkedList();
	}
	
	/**
	 * Get the next track from the source.
	 *
	 * @return A Track object
	 */
	public Track getNextTrack()
	{
		Track retval = null;

		if (isEnabled())
		{
			synchronized ( this.trackSources ) 
			{
				int checked = 0;
				int sourceCount = this.trackSources.size();
				while ( retval == null && checked < sourceCount )
				{
					TrackSource ts = (TrackSource) this.trackSources.get( trackSourceIndex );
					if ( ++this.trackSourceIndex >= sourceCount ) this.trackSourceIndex = 0;
					checked++;
					retval = ts.getNextTrack();
				}
			}
		}
		
		return (retval == null) ? delegateGetNextTrack() : retval;
	}

	/**
	 * Add a track source to the list of sources to play from
	 *
	 * @param ts The TrackSource to add
	 */
	public void addTrackSource( TrackSource ts )
	{
		synchronized ( this.trackSources ) 
		{
			this.trackSources.add( ts );
			if (logDebugEnabled) log.debug( "RoundRobinTrackSource now has " + this.trackSources.size() + " sources registered" );
		}
	}

	/**
	 * Remove a TrackSource from the list of sources to play from
	 *
	 * @param ts The TrackSource to remove
	 */
	public void removeTrackSource( TrackSource ts )
	{
		synchronized ( this.trackSources ) 
		{
			this.trackSources.remove( ts );
		}
	}
	
	/**
	 * 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()
	{
		// Cannot store state for this as it contains other TrackSources, which
		// are meant to be memory only.

		// I beg to differ...
		Map retVal = new HashMap();
		retVal.put("name",name);
		retVal.put("enabled",new Boolean(isEnabled()));
		return retVal;
	}
	
	/**
	 * Peek at the next count tracks from this TrackSourcePipelineElement
	 *
	 * @param count The number of Tracks to peek ahead at
	 * @return A List of Track objects
	 */
	public List peekTracks(int count)
	{
		if (logDebugEnabled) log.debug( "Peeking tracks from "+this.trackSources.size()+" user playlists" );
		
		List retval = null;
		
		if (isEnabled()) {
			retval = new LinkedList();
			List tlist = new LinkedList();
		
			synchronized ( this.trackSources )
			{
				// Make a list of the sources with the current source first, this 
				// makes it much easier to do the next bit
				int currIdx = this.trackSourceIndex;
				while ( tlist.size() < this.trackSources.size() )
				{
					tlist.add( this.trackSources.get( currIdx ) );
					currIdx++;
					if ( currIdx >= this.trackSources.size() ) currIdx = 0;
				}
				
				//log.debug("Created a temporary list with "+tlist.size()+" playlists in");
				
				// Go through all the sources and take the last track until
				// we've got enough tracks or we run out of sources
				int currsize = 1;
				while ( retval.size() < count && tlist.size() > 0 )
				{
					Iterator i = tlist.iterator();
					while ( i.hasNext() )
					{
						TrackSourcePipelineElement tspe = (TrackSourcePipelineElement) i.next();
						List peek = tspe.peekTracks( currsize );
						//log.debug( "Got "+peek.size()+" tracks" );
						//log.debug( "Has "+((InMemoryPlaylist)tspe).size()+" tracks" );
						if ( peek.size() != currsize )
						{	
							i.remove();
						}
						else
						{
							retval.add( peek.get( peek.size()-1 ) );
						}
					}
					currsize++;
				}
			}
		
			// Check to see if we got enough tracks, if not get more
			if ( retval.size() < count )
			{
				if (logDebugEnabled) log.debug( "Only found "+retval.size()+" tracks.  Delegating for the rest..." );
				retval.addAll( super.delegatePeekTracks( count - retval.size() ) );
			}
		} else {
			retval = delegatePeekTracks(count);
		}
		
		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)
	{
		name = (String)state.get("name");
		if (((Boolean)state.get("enabled")).booleanValue())
		{
			enable();
		} else {
			disable();
		}
		return true;
	}

	/**
	 * Clone this TrackSourcePipelineElement
	 *
	 * @return A copy of this object
	 */
	public Object clone()
	{
		RoundRobinPipelineElement rrpe = new RoundRobinPipelineElement();
		Iterator i = this.trackSources.iterator();
		while ( i.hasNext() )
		{
			rrpe.addTrackSource( (TrackSource) i.next() );
		}
		
		return rrpe;
	}
	
		
	/**
	 * Return a String summarising the configuration of the task the source is 
	 * performing
	 *
	 * @return A String
	 */
	public String getSummary()
	{
		return "Currently monitoring "+this.trackSources.size()+" track sources";
	}
	
	/**
	 * Return a String describing what, in general terms, this TrackSource does
	 *
	 * @return A String
	 */
	public String getDescription()
	{
		return "RoundRobinPipelineElement: Takes tracks from a number of track sources with a round robin method";
	}
}

