/***************************************************************************
    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.*;
import com.neoworks.jukex.query.*;

import java.util.Map;
import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.sql.SQLException;
import org.apache.log4j.Category;

/**
 * PipelineElement that provides a stream of randomly selected tracks from the results of a query.
 * This PipelineElement should never run out of tracks!
 *
 * @author Nigel Atkinson <a href="mailto:nigel@neoworks.com">nigel@neoworks.com</a>
 */
public class SearchRandomiserPipelineElement extends TrackSourcePipelineElementSkeleton implements SearchPipelineElement
{
	private static final Category log = Category.getInstance(SearchRandomiserPipelineElement.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();
	private LinkedList trackCache;
	private Query query;
	private long[] trackIds;

	/**
	 * Public constructor
	 */
	public SearchRandomiserPipelineElement()
	{
		super("SearchRandomiser");
		trackCache = new LinkedList();
	}

	/**
	 * 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("query",query);
		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 )
	{
		name = (String)state.get("name");
		query = (Query)state.get("query");
		if (((Boolean)state.get("enabled")).booleanValue())
		{
			enable();
		} else {
			disable();
		}
		executeQuery();
		return true;
	}

	/**
	 * Get a track at random
	 *
	 * @return a Track object
	 */
	public Track getNextTrack()
	{
		if (isEnabled())
		{
			if (trackCache.size() < 1)
			{
				cacheTracks(10);
			}
			return (Track)trackCache.removeFirst();
		} else {
			return delegateGetNextTrack();
		}
	}

	/**
	 * Peek up the track list
	 *
	 * @param count The number of tracks to peek
	 * @return A List of Track objects
	 */
	public List peekTracks(int count)
	{
		List retVal = null;
		
		if (isEnabled())
		{
			retVal = new ArrayList();
			if (trackCache.size() < count)
			{
				cacheTracks(count - trackCache.size());
			}
			count = Math.min(count,(trackCache.size() - 1));
			for (int i = 0; i < count; i++)
			{
				retVal.add(trackCache.get(i));
			}
		} else {
			retVal = delegatePeekTracks(count);
		}
		return retVal;
	}

	/**
	 * Get a string representation of the query
	 *
	 * @return Tne JukeXQL query String
	 */
	public String getQueryString()
	{
		return (query == null)?"":query.getOriginalQuery();
	}

	private void executeQuery()
	{
		if (query != null)
		{
			List trackIdList = new ArrayList();
			try {
				AttributeValueResultSet avrs = query.getAttributeValues();
				while (avrs.next())
				{
					trackIdList.add(new Long(avrs.getTrackId()));
				}
				trackIds = new long[trackIdList.size()];
				for (int i = 0; i < trackIds.length; i++)
				{
					trackIds[i] = ((Long)trackIdList.get(i)).longValue();
				}
			} catch (SQLException e) {
				log.warn("Exception encountered while executing query", e);
			}
		}
	}

	/**
	 * Set the query to use for selecting tracks
	 *
	 * @param q The Query
	 */
	public synchronized void setQuery(Query q)
	{
		query = q;
		executeQuery();
	}

	/**
	 * Pre-cache a number of tracks
	 *
	 * @param count The number of tracks to cache
	 */
	private void cacheTracks(int count)
	{
		TrackStore ts = TrackStoreFactory.getTrackStore();
		long trackCount = (trackIds == null)?0:trackIds.length;
		count = Math.min(count,(int)trackCount);
		if (count > 0)
		{
			long [] ids = new long[count];
			for (int i = 0; i < count; i++)
			{
				ids[i] = trackIds[(int)Math.round(Math.random() * (trackCount - 1))];
			}
			trackCache.addAll(ts.getTracks(ids));
		}
	}

	/**
	 * Clone this RandomiserPipelineElement
	 *
	 * @return A clone of this RandomiserPipelineElement
	 */
	public Object clone()
	{
		return new SearchRandomiserPipelineElement();
	}
	
	/**
	 * Return a String describing what, in general terms, this TrackSource does
	 *
	 * @return A String
	 */
	public String getDescription()
	{
		return "SearchRandomiserPipelineElement: Provides a random series of tracks from a search";
	}
	
	/**
	 * Return a String summarising the configuration of the task the source is
	 * performing
	 *
	 * @return A String
	 */
	public String getSummary()
	{
		return "Providing tracks chosen at random from the results of this search: " + getQueryString();
	}
}
