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

package com.neoworks.jukex.client.html.standard;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

import com.neoworks.rdc.ParserException;
import com.neoworks.jukex.*;
import com.neoworks.jukex.query.JukeXParser;
import com.neoworks.jukex.tracksource.*;
import com.neoworks.jukex.sqlimpl.InMemoryPlaylist;
import com.neoworks.util.CollectionUtils;

import org.apache.log4j.Category;

/**
 * Administration servlet interface for JukeX.
 *
 * @author Nigel Atkinson (<a href="mailto:nigel@neoworks.com">nigel@neoworks.com</a>)
 */
public class JukeXAdminServlet extends HttpServlet
{
	private static final Category log = Category.getInstance(JukeXAdminServlet.class.getName());
	private static final boolean logDebugEnabled = log.isDebugEnabled();
	private static final boolean logInfoEnabled = log.isInfoEnabled();
	
	private static final String PIPELINE_NAME = "servlet";
	private static final String CONTROL_PLAYBACK_NAME = "office";
	private TrackStore ts = null;
	private TrackSourcePipeline pipeline = null;
	private String contextRootURL = null;
	private static int MAX_BROWSE_COLUMNS = 10;

	/**
	 * Handle a POST request
	 *
	 * @param req The HTTP request
	 * @param res The HTTP response
	 */
	public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		doGet(req,res);
	}

	/**
	 * Handle a GET request
	 *
	 * @param req The HTTP request
	 * @param res The HTTP response
	 */
	public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		if (logDebugEnabled) log.debug("JukeX Standard Administration interface Servlet");
		pipeline = TrackSourcePipeline.getPipeline( this.PIPELINE_NAME );

		// Setup the session
		HttpSession session = req.getSession();
		int upcomingTrackCount = 5;
		synchronized ( session )
		{
			if ( session.getAttribute("personalPlaylist") == null ) 
			{
				if (logDebugEnabled) log.debug("Setting up personal playlist");
				this.setupPersonalPlaylist( session );
			}
			else
			{
				if (logDebugEnabled) log.debug("Personal playlist found");
			}
			if ( session.getAttribute("upcomingTrackCount") == null)
			{
				if (logDebugEnabled) log.debug("Setting upcoming track count");
				session.setAttribute("upcomingTrackCount",new Integer(upcomingTrackCount));
			}
			else
			{
				upcomingTrackCount = ((Integer)session.getAttribute("upcomingTrackCount")).intValue();
				if (logDebugEnabled) log.debug("Showing " + upcomingTrackCount + " tracks in upcoming list");
			}
		}

		contextRootURL = req.getContextPath()+"/";
		String view = req.getParameter("view");
		String action = req.getParameter("action");
		RequestDispatcher rd = null;

		if (action != null)
		{
			ts = TrackStoreFactory.getTrackStore();
			if (action.equals("attributes.add")) 
			{
				String attributeName = req.getParameter("attributes.name");
				String attributeType = req.getParameter("attributes.type");
				int type = -1;
				if (attributeType.equals("String"))
				{
					type = Attribute.TYPE_STRING;
				} else if (attributeType.equals("Integer")) {
					type = Attribute.TYPE_INT;
				}
				try
				{
					ts.createAttribute(attributeName, type);
				}
				catch ( Exception e )
				{
					// TODO: How do we report an error?
				}
			}
			else if (action.equals("attributes.remove")) 
			{
			}
			else if ( action.equals( "pipeline.newElementChosen" ) )
			{
				if (logDebugEnabled) log.debug("Chosen new pipeline element");
				int insertPos = Integer.parseInt(req.getParameter("pipeline.pos"));
				String elementType = req.getParameter("pipeline.elementtype");
				view = "pipeline";
				
				if (logDebugEnabled)
				{
					log.debug("Inserting at position "+insertPos);
					Enumeration e = req.getAttributeNames();
					while ( e.hasMoreElements() )
					{
						log.debug( e.nextElement() );
					}
				}
				
				TrackSourcePipelineElement newElement = null;
				
				if (elementType != null)
				{
					if (elementType.equals("playlist"))
					{	
						String playlistName = req.getParameter("pipeline.playlistname");
						pipeline.add(insertPos, ts.getPlaylist(playlistName));
					} else if (elementType.equals("randomiser")) {
						newElement = new RandomiserPipelineElement();
					} else if (elementType.equals("searchrandomiser")) {
						newElement = new SearchRandomiserPipelineElement();
					} else if (elementType.equals("roundrobin")) {
						newElement = new RoundRobinPipelineElement();
					} else if (elementType.equals("filter")) {
						newElement = new FilterPipelineElement();
					} else if (elementType.equals("norepeat")) {
						newElement = new NoRepeatPipelineElement(200);
					} else if (elementType.equals("search")) {
						newElement = new OnetimeSearchPipelineElement();
					} else if (elementType.equals("annoying")) {
						newElement = new AnnoyingPipelineElement();
					} else if (elementType.equals("audiobanner")) {
						newElement = new AudioBannerPipelineElement();
					}
				}
				
				if ( newElement != null )
				{
					System.out.println( "Got new element, now to insert" );
					newElement.setName( req.getParameter("pipeline.elementName") );
					TrackSourcePipeline tsp = TrackSourcePipeline.getPipeline( req.getParameter("pipeline.name") );
					tsp.add( insertPos , newElement );
					tsp.storePipeline();
				}
			}
			else if (action.equals("pipeline.remove"))
			{
				int index = Integer.parseInt(req.getParameter("pipeline.index"));
				pipeline.remove(index);
				pipeline.storePipeline();
			}
			else if ( action.equals("pipeline.edit") )		// Form actions, hence no nice action description
			{
				int index = Integer.parseInt( req.getParameter("pipeline.index") );
				if ( req.getParameter( "pipeline.disable.x" ) != null )
				{
					((TrackSourcePipelineElement) pipeline.get( index )).disable();
					pipeline.storePipeline();
				}
				else if ( req.getParameter( "pipeline.enable.x" ) != null )
				{
					((TrackSourcePipelineElement) pipeline.get( index )).enable();
					pipeline.storePipeline();
				}
				else if ( req.getParameter( "pipeline.addElement.x" ) != null )
				{
					req.setAttribute( "pipeline.name" , req.getParameter( "pipeline.name" ) );
					view = "pipelineelements";
				}
			}
			else if (action.equals("norepeat.setinterval"))
			{
				int index = Integer.parseInt(req.getParameter("norepeat.index"));
				NoRepeatPipelineElement noRepeat = (NoRepeatPipelineElement)pipeline.get(index);
				noRepeat.setInterval(Integer.parseInt(req.getParameter("norepeat.interval")));
				pipeline.storePipeline();
			}
			else if (action.equals("search.setquery"))
			{
				JukeXParser p = JukeXParser.createParser("SELECT Title WHERE " + req.getParameter("search.query"));
				int index = Integer.parseInt(req.getParameter("search.index"));
				TrackSourcePipelineElement pe = (TrackSourcePipelineElement)pipeline.get(index);
				try {
					if (pe instanceof SearchPipelineElement)
					{
						((SearchPipelineElement)pe).setQuery(p.parse());
					}
					pipeline.storePipeline();
				} catch (Exception e) {
					log.warn("Exception encountered while setting query", e);
				}
			}
			else if (action.equals("browse.setcolumns")) 
			{
				String columnAttributeName = null;
				List columnAttributes = new ArrayList();
				for (int i = 0; i < MAX_BROWSE_COLUMNS; i++)
				{
					columnAttributeName = req.getParameter("browse.column." + String.valueOf(i));
					if (columnAttributeName.equals("none"))
					{
						break;
					}
					columnAttributes.add(ts.getAttribute(columnAttributeName));
				}
				columnAttributes = CollectionUtils.distinctList(columnAttributes);
				session.setAttribute("browseColumnAttributes",columnAttributes);
				String useGroupingStr = req.getParameter("browse.usegrouping");
				if (logDebugEnabled) log.debug("Use grouping checkbox is " + useGroupingStr);
				if (useGroupingStr != null)
				{
					session.setAttribute("browseUseGrouping", new Boolean(useGroupingStr.toUpperCase().equals("ON")));
				} else {
					session.setAttribute("browseUseGrouping", new Boolean(false));
				}
			}
			else 
			{
				req.setAttribute("pageContent","<h1>Unknown Action: " + action + "</h1>");
			}
		}

		if ( view.equals("pipelineelements") )
		{
			// JSP invoked here.
			rd = handlePipelineElements( req , res , TrackStoreFactory.getTrackStore());
		}
		else if ( view.equals("attributes") )
		{
			// JSP invoked here.
			rd = handleAttributes( req , res, TrackStoreFactory.getTrackStore() );
		}
		else if ( view.equals("adminsearch") )
		{
			rd = handleAdminSearch(req, res);
		}
		else if ( view.equals("searchpipeline") )
		{
			rd = handlePipelineSearch(req,res);
		}
		else if ( view.equals("pipeline") )
		{
			// JSP invoked here.
			rd = handlePipeline( req , res );
		}
		else if ( view.equals("norepeat") )
		{
			// JSP invoked here.
			rd = handleNoRepeat( req , res );
		}
		else if ( view.equals("left")  || view.equals("sidebar"))
		{
			if (logDebugEnabled) log.debug("Viewing left frame");
			ControlPlaybackManager cpm = new ControlPlaybackManager();
			ControlPlayback sh = cpm.getControlPlayback(CONTROL_PLAYBACK_NAME);
			Long remainingPlayTime = null;
			if (sh.isPaused())
			{
				// 30 seconds
				remainingPlayTime = new Long(30000);
			} else {
				remainingPlayTime = new Long(sh.getRemainingPlayingTime());
			}
			req.setAttribute( "timeremaining" , remainingPlayTime );
			req.setAttribute( "playing" , sh.getPlaying() );
			req.setAttribute( "upcoming" , pipeline.peekTracks(upcomingTrackCount) );
			req.setAttribute( "paused" , new Boolean(sh.isPaused()) );
			Playlist personalPlaylist = (Playlist) req.getSession().getAttribute( "personalPlaylist" );
			Playlist copyPlaylist = null;
			synchronized(personalPlaylist)
			{
				copyPlaylist = (Playlist)personalPlaylist.clone();
			}
			req.setAttribute( "pplaylist" , copyPlaylist );

			if (view.equals("sidebar"))
			{
				rd = req.getRequestDispatcher("/sidebarFrame.jsp");
			} else {
				rd = req.getRequestDispatcher("/leftFrame.jsp");
			}
		} 
		else if ( view.equals("browsecolumns") )
		{
			// JSP invoked here.
			rd = handleBrowseColumns( req , res);
		}
		else
		{
			rd = req.getRequestDispatcher("/unknown.jsp");
		}
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		req.setAttribute("resources", ResourceBundle.getBundle("InterfaceResources", req.getLocale(), loader));
		rd.forward(req,res);
	}

	// TODO: Find a way of handling browse column selection from admin views that does not require code duplication
	private RequestDispatcher handleBrowseColumns(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		req.setAttribute("servlet", "JukeXAdmin");
		req.setAttribute("allAttributes", TrackStoreFactory.getTrackStore().getAttributes());
		req.setAttribute("maxBrowseColumns", new Integer(MAX_BROWSE_COLUMNS));
		req.setAttribute("queryString", req.getParameter("query.letter"));
		req.setAttribute("useGrouping", req.getSession().getAttribute("browseUseGrouping"));
		req.setAttribute("browseColumnAttributes", req.getSession().getAttribute("browseColumnAttributes"));
		if ( req.getParameter("returnview")!=null && !req.getParameter("returnview").equals("") )
		{
			req.setAttribute("returnView", req.getParameter("returnview"));
		}
		return req.getRequestDispatcher("/browsecolumns.jsp");
	}

	private RequestDispatcher handlePipelineElements(HttpServletRequest req, HttpServletResponse res, TrackStore ts) throws ServletException, IOException
	{
		req.setAttribute("playlists", ts.getAllPlaylists());
		req.setAttribute("pipeline.insertPos", req.getParameter("pipeline.index"));
		return req.getRequestDispatcher("/pipelineelements.jsp");
	}

	private RequestDispatcher handleNoRepeat(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		req.setAttribute("norepeat", pipeline.get(Integer.parseInt(req.getParameter("norepeat.index"))));
		req.setAttribute("pipeline", pipeline);
		return req.getRequestDispatcher("/norepeat.jsp");
	}

	private RequestDispatcher handleAttributes(HttpServletRequest req, HttpServletResponse res, TrackStore ts) throws ServletException, IOException
	{
		req.setAttribute("attributes", ts.getAttributes());
		return req.getRequestDispatcher("/attributes.jsp");
	}

	private RequestDispatcher handleAdminSearch(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		HttpSession session = SearchHelper.prepareSearchSession(req);
		List columnAttributes = (List)session.getAttribute("browseColumnAttributes");
		String currentQuery = (String)session.getAttribute("currentQuery");
		String query = req.getParameter("adminsearch.query");
		StringBuffer queryString = new StringBuffer();
		Iterator cai = columnAttributes.iterator();
		Attribute a = null;
		boolean validQuery = false;
		
		queryString.append("SELECT ");
		while (cai.hasNext())
		{
			a = (Attribute)cai.next();
			queryString.append(a.getName());
			if (cai.hasNext())
			{
				queryString.append(",");
			}
		}
		queryString.append(" WHERE ");
		if (query != null)
		{
			queryString.append(query);
			validQuery = true;
		} else if (currentQuery != null) {
			if (logDebugEnabled) log.debug("Using saved query");
			query = currentQuery;
			queryString.append(query);
			validQuery = true;
		}
		queryString.append(" ORDER BY ").append(((Attribute)session.getAttribute("browseSortAttribute")).getName()).append(" ").append(session.getAttribute("browseSortOrder"));
		if (validQuery)
		{
			try {
				req.setAttribute( "tracklist" , SearchHelper.performSearch(queryString.toString()));
				session.setAttribute("currentQuery", query);
			} catch (ParserException pe) {
				req.setAttribute( "error" , "Badly formed query");
			}
		}
		req.setAttribute( "query" , query );
		req.setAttribute( "columns" , columnAttributes );
		return req.getRequestDispatcher("/adminsearch.jsp");
	}

	private RequestDispatcher handlePipeline(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		req.setAttribute("pipeline", pipeline);
		return req.getRequestDispatcher("/pipeline.jsp");
	}

	private RequestDispatcher handlePipelineSearch(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
	{
		TrackSourcePipelineElement element = (TrackSourcePipelineElement)pipeline.get(Integer.parseInt(req.getParameter("search.index")));
		String query = null;
		if (element instanceof SearchPipelineElement)
		{
			query = ((SearchPipelineElement)element).getQueryString();
		}
		req.setAttribute("search", element);
		req.setAttribute("pipeline", pipeline);
		req.setAttribute("query", query);
		return req.getRequestDispatcher("/pipelinesearch.jsp");
	}

	/**
	 * Create a personal playlist and associate it with the session. This may
	 * (but should not) involve creating the RoundRobinPipelineElement
	 *
	 * @param session The HttpSession
	 */
	private void setupPersonalPlaylist( HttpSession session )
	{
		log.debug("Setting up personal playlist...");
		Playlist myPlaylist = new InMemoryPlaylist();
		session.setAttribute( "personalPlaylist" , myPlaylist );
		ListIterator i = this.pipeline.listIterator();
		
		// See if we can find the Round Robin pipeline element, and if not
		// make a new one and add this playlist into it.
		
		RoundRobinPipelineElement rrpe = null;
		
		while ( i.hasNext() )
		{
			Object thing = i.next();
			if ( thing instanceof RoundRobinPipelineElement )
			{
				rrpe = (RoundRobinPipelineElement) thing;
				log.debug("Found RoundRobinPipelineElement...");
			}
		}
		
		if (rrpe != null)
		{
			rrpe.addTrackSource( myPlaylist );
		} else {
			log.warn("No RoundRobinPipelineElement found");
		}
	}
}
