/**
 * Copyright (c) 2011, Martin Foster <martin@ethereal-realms.org>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice, 
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright 
 *       notice, this list of conditions and the following disclaimer in the 
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Ethereal-Realms.org nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.lyra.Host;

import java.io.IOException;
import java.lang.Exception;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.lyra.Ledger;
import org.lyra.Database.Comptroller;

/**
 * This class deals with the interaction between the application and play
 * lists.  Actions such as: Creating; Removing; and Maintaining of play lists
 * are here.
 * <p>
 * Since the currently queued songs are a play list as well the ability to add
 * and remove elements from that list will be enabled through here.  This class
 * will function much as does <i>org.lyra.Database.Comptroller</i> does.
 * <p>
 * @author Martin Foster
 * @version 1.0.4, %Revision, 134%, %LastChangedDate, 24/09/11 1:47AM%
 * @see org.lyra.Database.Comptroller
 */
public class Playlist {

	// Package Classes
	private Ledger Logger;
	private Comptroller Manager;
	private Terminus Host;
	private Map<String,String> Lang;
	
	// Identifier
	private static final String CLASS = "org.lyra.Host.Playlist";
	
	
	/**
	 * Core constructor for the class.  Will establish all necessary elements
	 * in order to be able to start playing with play lists and the currently
	 * playing list.
	 * <p>
	 * @param logger Ledger instance for logging and debugging purposes.
	 * @param manager Comptroller instance for interacting with the database.
	 * @param host Terminus instance for direct interaction with the Host.
	 * @param lang Language elements.
	 */
	public Playlist(Ledger logger, Comptroller manager, Terminus host, Map<String,String> lang) {
		
		// Pass Up
		this.Logger  = logger;
		this.Manager = manager;
		this.Host    = host;
		this.Lang    = lang;
	}
	// End of Constructor Class
	
	
	/*
	 * CURRENT PLAY LIST METHODS
	 */
	
	/**
	 * Internal method that will pass on the commands necessary to write out
	 * the structure to the host in order to add songs to the Current playlist.
	 * <p>
	 * @param songs List of strings containing the <i>File</i> attribute.
	 */
	public void addCurrent(List<String> songs) {
		
		// If we extract nothing then we do not proceed
		if (songs.size() > 0) {
			
			// Attempt the connection
			try {
				
				// Logging buffer
				StringBuffer sb = new StringBuffer();
				
				/*
				 * Here we make the choice to batch all of the additions into one
				 * command group.  This will prevent the update class from flagging
				 * changes endlessly instead of just adding the group.
				 */
				
				// Start the batch command
				Host.writeBatch("Batch.Begin");

				// Loop through and add songs
				for (int count=0; count < songs.size(); count++) {
					
					// Logging
					if (Logger.isLoggable("INFO")) 
						sb.append(songs.get(count) + "\n");

					
					// We add in the quotes
					Host.writeArgument(
						"Current.Add",
						'"' + songs.get(count) + '"'
					  );
				}
				
				// End with the batch command
				Host.writeBatch("Batch.End");
				
				// Log the event in full
				Host.readResult();
				
				// Log the success
				Logger.Info(
					CLASS, 
					"addCurrentAlbum", 
					Lang.get("Playlist.Current.Insert").replaceAll("MLIST", sb.toString())
				  );
			} 
			
			// Recover and try again
			catch (Exception e) {
				
				// Recover connect
				Host.Recover(e);
				
				// Try again
				//addCurrent(songs);
			}
		}
	}
	// End of addCurrent Method
	
	
	/**
	 * Will take an array of <i>File</i> elements and append them directly
	 * to the Current play list.
	 * <p>
	 * @param songs List of strings containing the <i>File</i> attribute.
	 */
	public void addCurrent(String...songs) {
		
		// If we extract nothing then we do not proceed
		if (songs.length > 0) {
			
			// Attempt the connection
			try {
				
				// Logging buffer
				StringBuffer sb = new StringBuffer();
				
				/*
				 * Here we make the choice to batch all of the additions into one
				 * command group.  This will prevent the update class from flagging
				 * changes endlessly instead of just adding the group.
				 */
				
				// Start the batch command
				Host.writeBatch("Batch.Begin");

				// Loop through and add songs
				for (String song: songs) {
					
					// Logging
					if (Logger.isLoggable("INFO")) 
						sb.append(song + "\n");

					
					// We add in the quotes
					Host.writeArgument(
						"Current.Add",
						'"' + song + '"'
					  );
				}
				
				// End with the batch command
				Host.writeBatch("Batch.End");
				
				// Log the event in full
				Host.readResult();
				
				// Log Success
				Logger.Info(
					CLASS, 
					"addCurrentAlbum", 
					Lang.get("Playlist.Current.Insert").replaceAll("MLIST", sb.toString())
					 );
			} 
			
			// Recover and try again
			catch (IOException e) {
				
				// Recover connect
				Host.Recover(e);
				
				// Try again
				//addCurrent(songs);
			}
		}
	}
	// End of addCurrent Method
	
	
	/**
	 * Adds an album and all associated songs into the currently playing queue.
	 * By default this task will be done by sorting the song numbers in order
	 * maintain original structure.
	 * <p>
	 * @param albums String containing album name.
	 */
	public void addCurrentAlbum(String... albums) {
		
		// Container Element
		List<String> songs = new LinkedList<String>();
		
		// Loop through all album entries and append 
		for (String album: albums)
			Manager.getSongAlbum(songs, album);

		// Insert the songs
		addCurrent(songs);
	}
	// End of addCurrentAlbum Method
	
	
	/**
	 * Adds an album and all associated songs into the currently playing queue.
	 * By default this task will be done by sorting the song numbers in order
	 * maintain original structure.
	 * <p>
	 * @param filter Map containing filtering elements.
	 */
	public void addCurrentAlbum(Map<String,String> filter) {
		
		// Container Element
		List<String> songs = new LinkedList<String>();
		
		// Populate the container
		Manager.getSongAlbum(songs, filter);

		// Insert the songs
		addCurrent(songs);
	}
	// End of addCurrentAlbum Method
	
	
	/**
	 * Adds an artist and all associated songs into the currently playing queue.
	 * By default this task will be done by sorting the album names and then 
	 * song numbers in order maintain original structure.
	 * <p>
	 * @param artists String containing artist name.
	 */
	public void addCurrentArtist(String... artists) {
		
		// Container Element
		List<String> songs = new LinkedList<String>();
		
		// Loop through all artist entries and append 
		for (String artist: artists)
			Manager.getSongArtist(songs, artist);

		// Insert the songs
		addCurrent(songs);
	}
	// End of addCurrentArtist Method


	/**
	 * Adds an artist and all associated songs into the currently playing queue.
	 * By default this task will be done by sorting the album names and then 
	 * song numbers in order maintain original structure.
	 * <p>
	 * @param filter Map containing filtering elements.
	 */
	public void addCurrentArtist(Map<String,String> filter) {
		
		// Container Element
		List<String> songs = new LinkedList<String>();

		// Populate container
		Manager.getSongArtist(songs, filter);

		// Insert the songs
		addCurrent(songs);
	}
	// End of addCurrentArtist Method
	
	
	/**
	 * Adds an genre and all associated songs into the currently playing queue.
	 * By default this task will be done by sorting the album names and then 
	 * song numbers in order maintain original structure.
	 * <p>
	 * @param genres String containing genre name.
	 */
	public void addCurrentGenre(String... genres) {
		
		// Container Element
		List<String> songs = new LinkedList<String>();
		
		// Loop through all genre entries and append 
		for (String genre: genres)
			Manager.getSongGenre(songs, genre);

		// Insert the songs
		addCurrent(songs);
	}
	// End of addCurrentGenre Method
	
	
	/**
	 * Adds an genre and all associated songs into the currently playing queue.
	 * By default this task will be done by sorting the album names and then 
	 * song numbers in order maintain original structure.
	 * <p>
	 * @param filter Map containing filtering elements.
	 */
	public void addCurrentGenre(Map<String,String> filter) {
		
		// Container Element
		List<String> songs = new LinkedList<String>();
		
		// Populate Container
		Manager.getSongGenre(songs, filter);

		// Insert the songs
		addCurrent(songs);
	}
	// End of addCurrentGenre Method
	
	
	/**
	 * This specific method is designed to clear out all entries in the current
	 * playlist.   This corresponds to a built-in command which requires no
	 * knowledge of what is in the list. 
	 * <p>
	 * @return boolean Results of command.
	 */
	public boolean clearCurrent() {
		
		// Attempt the connection
		try {
			
			// Send the single command
			Host.writeBatch("Current.Clear");
			
			// Return results
			return Host.readResult();
		} 
		
		// Recover and try again
		catch (Exception e) {
			
			// Recover connect
			Host.Recover(e);
			
			// Try again
			return clearCurrent();
		}
	}
	// End of clearCurrent Method
	
	
	/**
	 * Clear entries as specified.  These are related to their positions in
	 * the list itself.
	 * <p>
	 * @param pos Array of integers indicating which elements.
	 * @return boolean Results of command.
	 */
	public boolean clearCurrent(int...pos) {
		
		// Attempt the connection
		try {
			
			// Start the batch command
			Host.writeBatch("Batch.Begin");

			// Sort the array for safety
			Arrays.sort(pos);
			
			// We do this in reverse order to prevent collisions
			for (int count=pos.length-1; count >= 0; count--)
				Host.writeArgument("Current.Delete", Integer.toString(pos[count]));

			// End with the batch command
			Host.writeBatch("Batch.End");
			
			// Return results
			return Host.readResult();
		} 
		
		// Recover and try again
		catch (Exception e) {
			
			// Recover connect
			Host.Recover(e);
		}
		
		// If we make it here there has been a problem
		return false;
	}
	// End of clearCurrent Method
	
	
	/**
	 * Clear entries as specified.  These are related to their positions in
	 * the list itself.
	 * <p>
	 * @param pos Array of string indicating which elements.
	 * @return boolean Results of command.
	 */
	public boolean clearCurrent(String...pos) {
		
		// Passage array
		int[] pass = new int[pos.length];
		
		// Loop through and convert to an Integer
		for (int count=0; count < pos.length; count++)
				pass[count] = Integer.parseInt(pos[count]);
		
		// Pass to Integer handler
		return clearCurrent(pass);
	}
	// End of clearCurrent Method


    /**
     * Shuffles the songs in the current playlist.  This is tied directly to
     * a protocol command.
	 * <p>
	 * @return boolean Results of command.
     */
    public boolean shuffleCurrent() {
    	
		// Attempt the connection
		try {
			
			// Send the single command
			Host.writeBatch("Current.Shuffle");
			
			// Return results
			return Host.readResult();
		} 
		
		// Recover and try again
		catch (Exception e) {
			
			// Recover connect
			Host.Recover(e);
			
			// Try again
			return shuffleCurrent();
		}
    }
    // End of shuffleCurrent Method
    
    
    /*
     * REMOTE PLAYLISTS
     * 
     * Remote playlists differ from the current playlist as they are a stored
     * collection of songs held on the server.
     */
    
    /**
     * This is the only method in this section that will not be sharing the
     * remote name.   Essentially it is used to save the current playlist as
     * a remote list, hence the name.
     * <p>
     * @param name String containing playlist name.
     * @return boolean indicating success.
     */
    public boolean saveCurrent(String name) {
    
		// Attempt the connection
		try {
			
			// Send the single command
			Host.writeArgument("Playlist.Save", '"' + name + '"');
			
			// Return results
			return Host.readResult();
		} 
		
		// Recover and try again
		catch (Exception e) {
			
			// Recover connect
			Host.Recover(e);
			
			// Try again
			return saveCurrent(name);
		}
    }
    // End of saveCurrent Method
    
    
    /**
     * This is the same method as saveCurrent with a different name to hopefully
     * cause less confusion and maintain consistency.
     * <p>
     * @param name String containing playlist name.
     * @return boolean indicating success.
     */
    public boolean saveRemote(String name) {
    	
    	// Straight pass-through
    	return saveCurrent(name);
    }
    // End of saveRemote Method
    
    
    /**
     * Has the exact opposite effect of the saveCurrent and saveRemote methods
     * as this will remove a playlist from the host entry.
     * <p>
     * @param name String containing playlist name.
     * @return boolean indicating success.
     */
    public boolean removeRemote(String name) {
    	
		// Attempt the connection
		try {
			
			// Send the single command
			Host.writeArgument("Playlist.Remove", '"' + name + '"');
			
			// Return results
			return Host.readResult();
		} 
		
		// Recover and try again
		catch (Exception e) {
			
			// Recover connect
			Host.Recover(e);
			
			// Try again
			return removeRemote(name);
		}
    }
    // End of removeRemote Method
    
    
    /**
     * Loads the contents of the listed playlist into the current playlist.
     * <p>
     * @param name String containing playlist name.
     * @return boolean indicating success.
     */
    public boolean loadRemote(String name) {
    	
		// Attempt the connection
		try {
			
			// Send the single command
			Host.writeArgument(
				"Playlist.Load", 
				'"' + name + '"'
			  );
			
			// Return results
			return Host.readResult();
		} 
		
		// Recover and try again
		catch (Exception e) {
			
			// Recover connect
			Host.Recover(e);
			
			// Try again
			return loadRemote(name);
		}
    }
    // End of loadRemote Method
    
    
    /**
     * Loads the contents specified by a list of songs into the current
     * playlist.   This method is part of this class due to the fact that this
     * list would normally originate from a locally stored list.
     * <p>
     * @param names List of file names to be added.
     */
    public void loadLocal(List<String> names) {
    	
    	// Simple pass-through
    	addCurrent(names);
    }
    // End of loadLocal Method
}
