/**
 * 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.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.lyra.Ledger;

/**
 * Deals with the basic status handling of the Music Player Daemon
 * protocol and its interaction with the rest of the system.  
 * <p>
 * @author Martin Foster
 * @version 1.0.4, %Revision, 106%, %LastChangedDate, 11/04/11 12:22AM%
 */
public class Status {

	// Storage Maps
	private Map<String,String> State;
	private Map<String,String> Stats;
	
	// Storage arrays
	private List<Map<String,String>> Current;
	private List<String> Playlist;
	private List<String> Enabled;
	private List<String> Disabled;
	
	// Package Classes
	private Ledger Logger;
	private Terminus Host;
	
	// Constants
	private static final String CLASS = "org.lyra.Host.Status";
	
	
	/**
	 * Primary constructor for this class.   Primarily responsible for passing
	 * up the base Classes for management and generating the initial maps.
	 * <p>
	 * @param logger Ledger instance for logging and debugging purposes.
	 * @param host Terminus instance for direct interaction with the Host.
	 */
	public Status(Ledger logger, Terminus host) {
		
		// Pass Up
		this.Logger 	= logger;
		this.Host 		= host;
		
		/*
		 * When an instance of this class is created we begin by populating
		 * all of the appropriate maps in order to guarantee availability when
		 * the interface is activated.
		 */
		updateEnabled();
		updateDisabled();
		updateStatistics();
		updateState();
		updateCurrent();
		updatePlaylist();
	}
	// End of Constructor Class
	
	
	/**
	 * An update variant that will extract information as necessary based on
	 * an event that has been generated.   This permits a greater locking
	 * capability forcing all newly update information to have the same
	 * state.
	 * <p>
	 * @param update UpdateEvent instance carrying all information on what to update.
	 */
	protected synchronized void updateByEvent(UpdateEvent update) {
		
		/*
		 * When dealing with an update event it becomes necessary to make sure
		 * that no one else is playing with this data as we update all of the
		 * necessary elements.
		 * 
		 * The State information is important for almost all updates so that 
		 * update is almost always updated no matter the call.
		 */
		if (!update.isUpdate())
			updateState();
		
		// Current playlist information
		if (update.isCurrent())
			updateCurrent();
		
		// Library Update
		if (update.isDatabase()) 
			updateStatistics();

		// Stored Playlist
		if (update.isPlaylist())
			updatePlaylist();
	}
	// End of updateByEvent Method
	
	
	/**
	 * Sends the appropriate command and parses the return value in order to
	 * extract status information from the Music Player Daemon. 
	 */
	protected synchronized void updateState() {
		// We may get some errors on the matter, so look for them.
		try {
			
			// Request status update
			Host.writeBatch("Status.Display");
			
			// Extract Status
			State = Host.readMap();
			
			// Debug the new element
			Logger.debugHash(State);
		} 
		
		// A failure in the connection
		catch (IOException e) {
			
			// User configured logging
			Logger.Severe(CLASS, "updateState", e);
		}
	}
	// End of updateState Method
	
	
	/**
	 * Returns the state element that has been generated by this class.  
	 * Used to generate the UI components and establish state. The 
	 * information that may be returned is as follows:
	 * <ul>
	 *  <li>volume: 0-100</li>
	 *  <li>repeat: 0 or 1</li>
	 *  <li>single: 0 or 1</li>
	 *  <li>consume: 0 or 1</li>
	 *  <li>playlist: 31-bit unsigned integer, the playlist version number</li>
	 *  <li>playlistlength: integer, the length of the playlist</li>
	 *  <li>state: play, stop, or pause</li>
	 *  <li>song: playlist song number of the current song stopped on or playing</li>
	 *  <li>songid: playlist songid of the current song stopped on or playing</li>
	 *  <li>nextsong: playlist song number of the next song to be played</li>
	 *  <li>nextsongid: playlist songid of the next song to be played</li>
	 *  <li>time: total time elapsed (of current playing/paused song)</li>
	 *  <li>elapsed:  Total time elapsed within the current song, but with higher resolution.</li>
	 *  <li>bitrate: instantaneous bitrate in kbps</li>
	 *  <li>xfade: crossfade in seconds</li>
	 *  <li>mixrampdb: mixramp threshold in dB</li>
	 *  <li>mixrampdelay: mixrampdelay in seconds</li>
	 *  <li>audio: sampleRate:bits:channels</li>
	 *  <li>updatings_db: job id</li>
	 *  <li>error: if there is an error, returns message here</li>
	 * </ul> 
	 * <p>
	 * This information changes with player state and version.  So it becomes
	 * necessary to check for the existence of tags prior to using them.
	 * <p>
	 * @return Map containing state information. 
	 */
	public synchronized Map<String,String> getState() {
		return State;
	}
	// End of getState Method
	
	
	/**
	 * Statistics can provide a great deal of information about the Music
	 * Player Daemon and provide a stating point to discover if the database
	 * needs updating or not.
	 */
	protected synchronized void updateStatistics() {
		// We may get some errors on the matter, so look for them.
		try {
			
			// Request status update
			Host.writeBatch("Status.Statistics");
			
			// Extract Status
			Stats = Host.readMap();
			
			// Debug the new element
			Logger.debugHash(Stats);
		} 
		
		// A failure in the connection
		catch (IOException e) {
			
			// User configured logging
			Logger.Severe(CLASS, "updateStatistics", e);
		}
	}
	// End of updateStatistics Method
	
	
	/**
	 * Returns the statistics element that has been generated by this class.  
	 * Used primarily to maintain sync with the database element.
	 * <ul>
     *  <li>artists: number of artists</li>
     *  <li>songs: number of songs</li>
     *  <li>uptime: daemon uptime in seconds</li>
     *  <li>db_playtime: sum of all song times in the database</li>
     *  <li>db_update: last database update in UNIX time</li>
     *  <li>playtime: time length of music played </li>
     * </ul>
	 * @return Map containing statistical information of the host.
	 */
	public synchronized Map<String,String> getStatistics() {
		return Stats;
	}
	// End of getStatistics Method
	
	
	/**
	 * Accesses the Music Player Daemon and gets a list of all commands which
	 * are enabled for use on the host.   This can be used to determine if
	 * certain functionality can be offered.
	 */
	protected synchronized void updateEnabled() {
		
		// Default to a blank list
		Enabled = new LinkedList<String>();
		
		// We may get some errors on the matter, so look for them.
		try {
			
			// Request status update
			Host.writeBatch("Status.Enabled");
			
			// Extract Status
			Enabled = Host.readList(1);			
		} 
		
		// A failure in the connection
		catch (IOException e) {
			
			// User configured logging
			Logger.Severe(CLASS, "getEnabled", e);
		}
	}
	// End of updateEnabled Method
	
	
	/**
	 * List containing all commands available for use.  Where an absence would
	 * indicate invalid command or one that is not supported by a certain
	 * version of the Music Player Daemon.
	 * <p>
	 * @return List containing enabled commands.
	 */
	public synchronized List<String> getEnabled() {
		
		// Return generated list
		return Enabled;
	}
	// End of getEnabled Method
	
	
	/**
	 * List containing all commands available for use.  Where an absence would
	 * indicate invalid command or one that is not supported by a certain
	 * version of the Music Player Daemon.
	 * <p>
	 * @return List containing enabled commands.
	 */
	public synchronized Set<String> getEnabledAsSet() {
		
		// Return generated list
		return new TreeSet<String>(Enabled);
	}
	// End of getEnabledAsSet Method
	
	
	/**
	 * Accesses the Music Player Daemon and gets a list of all commands which
	 * are not enabled for use on the host.   This can be used to determine if
	 * certain functionality can be offered.
	 */
	protected synchronized void updateDisabled() {
		
		// Default
		Disabled = new LinkedList<String>();
		
		// We may get some errors on the matter, so look for them.
		try {
			
			// Request status update
			Host.writeBatch("Status.Disabled");
			
			// Extract Status
			Disabled = Host.readList(1);	
		} 
		
		// A failure in the connection
		catch (IOException e) {
			
			// User configured logging
			Logger.Severe(CLASS, "updateDisabled", e);
		}
	}
	// End of updateDisabled Method
	
	
	/**
	 * Get a listing of all disabled commands.  This method is not the
	 * preferred way of working with restrictions, better overall to make
	 * use of getEnabled in order to determine commands that may not be
	 * supported in specific versions.
	 * <p>
	 * @return List containing disabled commands.
	 */
	public synchronized List<String> getDisabled() {
		
		// Return generated list
		return Disabled;
	}
	// End of getDisabled Method
	
	
	/**
	 * Get a listing of all disabled commands.  This method is not the
	 * preferred way of working with restrictions, better overall to make
	 * use of getEnabled in order to determine commands that may not be
	 * supported in specific versions.
	 * <p>
	 * @return List containing disabled commands.
	 */
	public synchronized Set<String> getDisabledAsSet() {
		
		// Return generated list
		return new TreeSet<String>(Disabled);
	}
	// End of getDisabledAsSet Method
	
	
	/**
	 * Playlist contain a lot of information that can be retained.  However
	 * while it is not always necessary to carry all of it we do so for code
	 * simplicity.  
	 */
	protected synchronized void updateCurrent() {
		
		// Always going to be updated
		Current = new LinkedList<Map<String,String>>();
		
		// Capture the appropriate length
		int length = (State.containsKey("playlistlength")) 
			? Integer.parseInt(State.get("playlistlength"))
			: 0;
		
		/*
		 * We only need to update the list if there are elements to update,
		 * otherwise we just pass on an empty array.
		 */
		if (length > 0) {
			
			// Ensure everything goes as planned
			try {
				// Time to request the change in details
				Host.writeBatch("Current.Details");
				
				// Extract all of the elements
				List<String> list = Host.readList();
				
				// Holder Class
				Map<String,String> rows = new HashMap<String,String>();
				
				
				/*
				 * The terminus class will deal with returning the lines
				 * untouched.  However there is a lot of additional 
				 * processing to do.
				 */
				while (list.size() > 0) {
					// Always remove the first element
					String line = list.remove(0);
					
					/*
					 * One of the core elements that will be returned will be
					 * a few file identifier.   When that line has been reached
					 * the processing will begin.
					 */
					if ((line.startsWith(Terminus.SECTION))
					 && (rows.size() > 0)){
						
						// Drop the completed Map into the List
						Current.add(rows);
							
						// Debug the new element
						Logger.debugHash(rows);
						
						// Generate a new type
						rows = new HashMap<String,String>();
					}
					
					/*
					 * Otherwise we just do a nice and simple parse job and 
					 * pass on the results as a hash.
					 */
					String[] cols = line.split(Terminus.SEPARATOR, Terminus.MAX_COL);

					// Quick safety check
					if (cols.length > 1)
						rows.put(cols[0], cols[1]);
				}
				
				// Include the last generated entry into the pot
				if (rows.size() > 1)
					Current.add(rows);
			} 
			
			// A failure in the connection
			catch (IOException e) {
				// User configured logging
				Logger.Severe(CLASS, "updateCurrent", e);
			}	
		}
	}
	// End of updateCurrent Method
	
	
	/**
	 * Returns the generated playlist from the Music Player Daemon host
	 * typically used for synchronisation of database elements.
	 * <ul>
	 *  <li>file: Location identifier, used as a unique key for the database</li>
	 *  <li>Time: Time in seconds for the runtime</li>
	 *  <li>Artist: Artist name</li>
	 *  <li>Title: Song's title</li>
	 *  <li>Album: Song's retaining album</li>
	 *  <li>Track: Track number of the song</li>
	 *  <li>Date: Release date of the song</li>
	 *  <li>Genre: Genre of the song</li>
	 *  <li>Pos: Position in the playlist</li>
	 *  <li>Id: Sequential order assigned to songs in playlist</li>
	 * </ul>
	 * @return List of Map<String,String> elements.
	 */
	public synchronized List<Map<String,String>> getCurrent() {
		// Returns the generated playlist element
		return Current;
	}
	// End of getCurrent Method
	
	
	/**
	 * Provides an update mechanism for stored playlists on the hosts enabling
	 * a user to get select them for playback and so forth.
	 * 
	 */
	protected synchronized void updatePlaylist() {
		
		// Always going to be updated
		Playlist = new LinkedList<String>();
		
		/*
		 * Normally we would save a bit of processing time and check for the 
		 * number of playlists currently stored.   However this does not seem
		 * to be a state option.
		 */
		try {
			// Time to request the change in details
			Host.writeBatch("Playlist.List");
			
			// Extract all of the elements
			List<String> list = Host.readList();
			

			/*
			 * The terminus class will deal with returning the lines
			 * untouched.  However there is a lot of additional 
			 * is a fair amount of information we could care less
			 * about.
			 */
			while (list.size() > 0) {
				
				// Always remove the first element
				String line = list.remove(0);
				
				// We could care less about the other lines
				if (!line.startsWith(Terminus.PLAYLIST)) continue;
				
				// Separate the elements and store name
				Playlist.add(line.split(Terminus.SEPARATOR, 2)[1]);
			}
		} 
		
		// A failure in the connection
		catch (IOException e) {
			// User configured logging
			Logger.Severe(CLASS, "updatePlaylist", e);
		}	
	}
	// End of updatePlaylist Method
	
	
	/**
	 * Returns a list of all stored playlists in the host.
	 * <p>
	 * @return List containing named items.
	 */
	public synchronized List<String> getPlaylist() {
		
		// Return element
		return Playlist;
	}
	// End of getPlaylist Method
	
	
	/**
	 * Returns the state of an element by first taking the string value
	 * and providing the user with a proper boolean.  Due to the protocol
	 * of the Music Player Daemon these strings represent a 1 or 0. 
	 * <p>
	 * @param state String indicating state of an element.
	 * @return Boolean indicating state of an element.
	 */
	public static boolean fetchState(String state) {
		
		// If we get no such element return false 
		if (state == null)
			return false;
		
		// Translate and allow method to process
		return fetchState(Integer.parseInt(state));
	}
	// End of fetchState Method
	
	
	/**
	 * Returns the state of an element by first taking the numeric value
	 * and providing the user with a proper boolean. 
	 * <p>
	 * @param state Integer indicating state of an element.
	 * @return Boolean indicating state of an element.
	 */
	public static boolean fetchState(int state) {
		
		// Return true when over 0
		if (state > 0)
			return true;
		
		// Default to return false
		return false;
	}
	// End of fetchState Method
}
