/***************************************************************************
    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.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Iterator;
import java.sql.SQLException;
import org.apache.log4j.Category;

/**
 * PipelineElement that plays the results of a query at regular intervals.
 *
 * @author Nigel Atkinson <a href="mailto:nigel@neoworks.com">nigel@neoworks.com</a>
 */
public class AudioBannerPipelineElement extends TrackSourcePipelineElementSkeleton implements SearchPipelineElement
{
	private static final Category log = Category.getInstance(AudioBannerPipelineElement.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();

	private Query query;
	private List trackIds;
	private int interval = 5;
	private int playedSinceLastBanner = 0;
	private int trackIdIndex = 0;

	/**
	 * Public constructor
	 */
	public AudioBannerPipelineElement()
	{
		super("AudioBanner");
		trackIds = new ArrayList();
	}

	/**
	 * Public constructor
	 *
	 * @param q The query to execute
	 */
	public AudioBannerPipelineElement(Query q)
	{
		super("AudioBanner");
		query = q;
		trackIds = new ArrayList();
		executeQuery();
	}

	/**
	 *
	 */
	private void executeQuery()
	{
		if (query != null)
		{
			try {
				AttributeValueResultSet avrs = query.getAttributeValues();
				while (avrs.next())
				{
					trackIds.add(new Long(avrs.getTrackId()));
				}
			} catch (SQLException e) {
				log.warn("Exception encountered while executing query", e);
			}
		}
	}

	/**
	 * Set the query to use
	 *
	 * @param q The new query
	 */
	public synchronized void setQuery(Query q)
	{
		query = q;
		trackIds = new ArrayList();
		trackIdIndex = 0;
		executeQuery();
	}

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

	/**
	 * Get a track at random
	 *
	 * @return a Track object
	 */
	public synchronized Track getNextTrack()
	{
		Track retVal = null;
		if ((playedSinceLastBanner < interval) || !isEnabled() || trackIds.size() == 0)
		{
			retVal = delegateGetNextTrack();
			if (isEnabled())
			{
				playedSinceLastBanner++;
			}
		} else {
			if (trackIds.size() > trackIdIndex)
			{
				retVal = TrackStoreFactory.getTrackStore().getTrack(((Long)trackIds.get(trackIdIndex++)).longValue());
			} else {
				trackIdIndex = 0;
				playedSinceLastBanner = 1;
				retVal = delegateGetNextTrack();
			}
		}
		return retVal;
	}

	/**
	 * Peek up the track list
	 *
	 * @param count The number of tracks to peek
	 * @return A List of Track objects
	 */
	public synchronized List peekTracks(int count)
	{
		List retVal = null;
		
		if (isEnabled() && trackIds.size() > 0 && interval > 0)
		{
			if (logDebugEnabled) log.debug("We are enabled and have some tracks to play");
			retVal = new ArrayList();
			TrackStore ts = TrackStoreFactory.getTrackStore();
			int j = 0;
			int tracksToPeek = count - (((count/interval)-1) * trackIds.size());
			List peekedTracks = new ArrayList();
			peekedTracks.addAll(delegatePeekTracks(tracksToPeek));
			int peekedTracksIndex = 0;
			if (peekedTracks.size() > 0)
			{
			if (interval - playedSinceLastBanner > 0)
			{
				peekedTracksIndex = Math.min(interval-playedSinceLastBanner,count);
				if (logDebugEnabled) log.debug("Peeking for " + peekedTracksIndex + " upcoming non banner tracks");
				// there are more tracks to play before the next banner
				retVal.addAll(peekedTracks.subList(0,peekedTracksIndex));
			} else {
				if (logDebugEnabled) log.debug("Peeking for banner tracks");
				// the banner is currently playing
				ListIterator i = trackIds.listIterator(trackIdIndex);
				while (i.hasNext() && j < count)
				{
					retVal.add(ts.getTrack(((Long)i.next()).longValue()) );
					j++;
				}
				if (j < count)
				{
					peekedTracksIndex = Math.min(interval,count - j);
					if (logDebugEnabled) log.debug("Peeking for " +peekedTracksIndex + " non banner tracks");
					retVal.addAll(peekedTracks.subList(0,peekedTracksIndex));
				}
			}
			j = retVal.size();
			Iterator i = null;

			while (j < count)
			{
				i = trackIds.iterator();
				if (logDebugEnabled) log.debug("Peeking for banner tracks. j = " + j);
				while (i.hasNext() && j < count)
				{
					retVal.add( ts.getTrack(((Long)i.next()).longValue()) );
					j++;
				}
				if (j < count)
				{
					if (logDebugEnabled) log.debug("Peeking for " +Math.min(interval,count - j) + " non banner tracks. j = " + j);
					retVal.addAll(peekedTracks.subList(peekedTracksIndex, peekedTracksIndex + Math.min(interval,count - j)));
					peekedTracksIndex += Math.min(interval,count - j);
					j+= Math.min(interval,count - j);
				}
			}
			} else {
				if (logInfoEnabled) log.info("no tracks to peek");
				retVal = new ArrayList();
			}
		} else {
			retVal = delegatePeekTracks(count);
		}
		return retVal;
	}

	/**
	 * Get a String representation of the current query
	 *
	 * @return The JukeXQL query as a String
	 */
	public String getQueryString()
	{
		return (query != null)?query.getOriginalQuery():null;
	}

	/**
	 * Clone this RandomiserPipelineElement
	 *
	 * @return A clone of this RandomiserPipelineElement
	 */
	public Object clone()
	{
		return new AudioBannerPipelineElement();
	}

	/**
	 * Return a String describing what, in general terms, this TrackSource does
	 *
	 * @return A String
	 */
	public String getDescription()
	{
		return "AudioBannerPipelineElement: This element takes the results of a search, and plays it at regular intervals";
	}

	/**
	 * Return a String summarising the configuration of the task the source is
	 * performing
	 *
	 * @return A String
	 */
	public String getSummary()
	{
		if ( super.isEnabled() )
		{
			if ( this.trackIds.size() > 0 )
			{
				return this.trackIds.size()+" tracks from this search "+this.query.getOriginalQuery()+" will play every "+interval+" tracks. (In "+(interval - playedSinceLastBanner)+" tracks time.)";
			}
			else
			{
				return "There are currently no tracks for this element to play";
			}
		}
		else
		{
			return "Disabled";
		}
	}
}
