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

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;

import org.lyra.Ledger;
import org.lyra.Host.Status;
import org.lyra.Host.Terminus;
import org.lyra.Host.UpdateEvent;
import org.lyra.Host.UpdateListener;


/**
 * The snooper is a direct tie in into Last.fm service that will provide the 
 * 'Scrobbling' capability.   This will inform the service of a new song being
 * played.
 * <p>
 * @author Martin Foster
 * @version 1.0.4, %Revision, 137%, %LastChangedDate, 05/10/11 5:55PM%
 * @since 1.0.1
 * @see org.lyra.LastDotFm.Demarcation
 * @see org.lyra.Host.UpdateListener
 */
public class Snooper implements UpdateListener {

	
	/*
	 * PACKAGE INSTANCES
	 */
	
	// Logger instance
	private Status Info;
	private Ledger Logger;
	
	// Language and Settings
	private Map<String,String> Global;
	private Map<String,String> Lang;
	
	
	/*
	 * CLASS INSTANCE
	 */
	
	// Last.fm Interface
	private Demarcation Deploy;
	
	// Track what is playing
	private Map<String,String> CurrentSong;
	
	// What is our current state
	private String CurrentState;
	
	// Start Time
	private Long StartTime;
	
	/*
	 * CONSTANTS
	 */
	
	// Thirty second song length
	private final static int MIN_SONG = 30;
	
	// Identification class for logging
	private final static String CLASS = "org.lyra.LastDotFm.Snooper";
	
	
	/**
	 * Base constructor for the Snooper.
	 * <p>
	 * @param info Information handle on the daemon.
	 * @param logger Ledger instance for logging.
	 * @param global Global settings map.
	 * @param lang Language dependent map.
	 */
	public Snooper(Status info, Ledger logger, Map<String,String> global, Map<String,String> lang) {
	
		// Pass up
		this.Info   = info;
		this.Logger = logger;
		this.Global = global;
		this.Lang   = lang;
		
		
		// Extract the state information
		Map<String,String> state = Info.getState();
		
		// Capture current state
		CurrentState = state.get("state");
		
		// Initial establishment
		CurrentSong 
			= new HashMap<String,String>(updateDetails(state));
		
		
		// Create Instances
		this.Deploy = Demarcation.getInstance(Logger, Global, Lang);
	}
	// End of Snooper Method

	
	/**
	 * Extracts the information from the org.lyra.Host.Status class and extracts
	 * the currently playing song.   Based on state and currently playing 
	 * elements.
	 * <p>
	 * @return Map containing current song details.
	 */
	private Map<String,String> updateDetails(Map<String,String> state) {
		
		// Confirm this element exists
		if (state.get("song") != null) {
			
			// Extract and parse song
			int num = Integer.parseInt(state.get("song"));
			
			// Extract the specific song
			if (Info.getCurrent().get(num) != null) {
				
				// Extract song detail
				return Info.getCurrent().get(num);
			}
		}
		
		// Return nothing if any of the above fails
		return new HashMap<String,String>();
	}
	// End of updateFilename Method
	
	
	/* (non-Javadoc)
	 * @see org.lyra.Host.UpdateListener#hostUpdated(org.lyra.Host.UpdateEvent)
	 */
	public void hostUpdated(UpdateEvent update) {
		
		/*
		 * Player updates will provide the greatest amount of opportunity for
		 * us to record the element.
		 */
		if (update.isPlayer()) {
			
			// Extract the state information
			Map<String,String> state = Info.getState();
			
			// Extract Map
			Map<String,String> current = updateDetails(state);
			
			/*
			 * We only care about recording state information if we are
			 * currently playing.
			 */
			if (state.get("state").equals(Terminus.STATE_PLAY)) {
				
				/*
				 * This condition should never fail.  Especially if we are
				 * still playing.
				 */
				if (current.get("file") != null) {
					
					/*
					 * Without a start time part of the process is missing
					 * which may take place when you start Lyra as a song
					 * is playing.
					 */
					if ((StartTime != null)
					 && ((System.currentTimeMillis()/1000 - StartTime) > MIN_SONG)){
						
						// Close out the old file entry
						Deploy.transmitTrackScrobble(
							StartTime,
							CurrentSong.get("Title"), 
							CurrentSong.get("Artist"), 
							CurrentSong.get("Album"),
							CurrentSong.get("Track"), 
							CurrentSong.get("Time")
						  );
						
						// Log Entry
						Logger.Info(
							CLASS,
							"hostUpdated",
							Lang.get("Snooper.Close")
								.replaceAll(
									"MFILE", 
									Matcher.quoteReplacement(current.get("file"))
								  )
						  );
					}
					

					/*
					 * We always run this part.  Registers the song with
					 * the Last.fm service.
					 */
					
					// Pass on the new track as playing
					Deploy.transmitUpdateNowPlaying(
						current.get("Title"), 
						current.get("Artist"), 
						current.get("Album"),
						current.get("Track"), 
						current.get("Time")
					  );
					
					// Update Start Time
					StartTime = System.currentTimeMillis() / 1000;
				 
					// Log Entry
					Logger.Fine(
						CLASS,
						"hostUpdated",
						Lang.get("Snooper.Update")
							.replaceAll(
								"MFILE", 
								Matcher.quoteReplacement(current.get("file"))
							  )
					  );
				}
			 }
			
			/*
			 * In this case we know that the state has moved to a stop state
			 * meaning that the old song is effectively stopped.  That's 
			 * other Last.fm milestone.  Indicating Scrobbling. 
			 */
			else if ((state.get("state").equals(Terminus.STATE_STOP)) 
			  && (CurrentState.equals(Terminus.STATE_PLAY))) {
				
				/*
				 * We want to make sure that the Map contains the necessary
				 * entries.  Checking for 'file' will ensure we are dealing with
				 * the right structure at least and confirm this song was
				 * recorded as being started.
				 */
				if ((CurrentSong.containsKey("file"))
				 && (StartTime != null)
				 && ((System.currentTimeMillis()/1000 - StartTime) > MIN_SONG)) {
								
					// Close out song
					Deploy.transmitTrackScrobble(
						StartTime,
						CurrentSong.get("Title"), 
						CurrentSong.get("Artist"), 
						CurrentSong.get("Album"),
						CurrentSong.get("Track"), 
						CurrentSong.get("Time")
					  );
					

					// Log Entry
					Logger.Info(
						CLASS,
						"hostUpdated",
						Lang.get("Snooper.Close")
							.replaceAll(
								"MFILE", 
								Matcher.quoteReplacement(CurrentSong.get("file"))
							  )
					  );

					// Reset Start Time
					StartTime = null;
				}
			}
			
			// Record State
			CurrentState = state.get("state");
			
			// Replace Current Song for next use
			CurrentSong = 
				new HashMap<String,String>(updateDetails(state));
		}
	}
	// End of hostUpdated Method

}
