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

import java.io.File;
import java.io.IOException;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

/**
 * This class will hide certain complexities associated with the built in
 * java.util.Logger class.  This will allow us to override the functionality
 * defaults and at the same time allow us to hide the complexities associated
 * with the language properties being attached to this class.
 * <p>
 * @author Martin Foster
 * @version 1.0.1, %Revision, 85%, %LastChangedDate, 17/02/11 8:19PM%
 */
public class Ledger {
	
	// Current logging level
	private Levels myCurrent = Levels.CONFIG;
	
	// Direct to two sources
	private final static boolean DEBUG = true;
	
	// Default Logger Level
	private final static Level DEFAULT = Level.CONFIG;
	
	// Logger component
	private static Logger logger = Logger.getLogger("org.lyra");
	
	// Carriage Returns
	private static final String CR = System.getProperty("line.separator");

	
	/*
	 * The application seems unreliable when it comes to actually filtering
	 * out elements that are not at the required level.  So we implement it
	 * manually.
	 */
	private static enum Levels {
		SEVERE, WARNING, INFO, CONFIG, FINE, FINEST
	}
	
	/**
	 * Severe Level Entities
	 */
	private static final Set<Levels> SEVERE = EnumSet.range(Levels.SEVERE, Levels.FINEST);

	/**
	 * Warning Level Entities
	 */
	private static final Set<Levels> WARNING = EnumSet.range(Levels.WARNING, Levels.FINEST);

	
	/**
	 * Info Level Entities
	 */
	private static final Set<Levels> INFO = EnumSet.range(Levels.INFO, Levels.FINEST);


	/**
	 * Configure Level Entities
	 */
	private static final Set<Levels> CONFIG = EnumSet.range(Levels.CONFIG, Levels.FINEST);

	
	/**
	 * Fine Level Entities
	 */
	private static final Set<Levels> FINE = EnumSet.range(Levels.FINE, Levels.FINEST);


	/**
	 * Finest Level Entities
	 */
	private static final Set<Levels> FINEST = EnumSet.of(Levels.FINEST);

	
	
	/**
	 * Standard constructor for this class.   It will create a stored logger
	 * which will be called when appropriate.  
	 * <p>
	 * <b>NOTE:</b> This constructor will be configured regardless of improper
	 * handling by the configuration file.  Instead of a custom logger defaults
	 * are employed.
	 */
	public Ledger() {

	}
	// End of Constructor Method
	
	
	/**
	 * The logger system is used regardless of state of configuration.  It is
	 * necessary to configure it appropriately once the information has been
	 * retrieved.  If this is not the case, the Ledger class provides a
	 * constant interface no matter the situation.
	 * <p>
	 * @param prefs Contains the preferences for the application.
	 */
	public void Adjust(Map<String,String> prefs) {
		
		/* 
		 * For quick lookups it is nice to generate a series of used levels
		 * ahead of time in order to prevent re-generating them over and
		 * over.
		 */

		/* Conversion of Strings to Integers
		 * Initialised to defaults in case the conversion fails 
		 */
		int LimitRow;
		int LimitFile;
		
		// Properties file stores as string, must convert
		try {
			
			LimitRow 	= Integer.parseInt(prefs.get("Debug.Limit.Row"));
			LimitFile 	= Integer.parseInt(prefs.get("Debug.Limit.File"));
		}
		
		// Parsing has failed
		catch (NumberFormatException e) {
			
			// Initialise to defaults
			LimitRow = 5000;
			LimitFile = 10;
		}
		
		
		/* 
		 * Management of the logger components themselves.   This section will
		 * fall back onto defaults if need be. 
		 */
		
		// Determine filename to use, fall back to $TEMP otherwise
		String path = new File(Lyra.rootUser, Lyra.LOGS).canWrite()
		  ? Lyra.rootUser.getAbsolutePath() + Lyra.LOGS + prefs.get("Debug.File")
		  : System.getProperty("java.io.tmpdir") + prefs.get("Debug.File");
		
		// Attempt to create a custom file handle
		try {
			
			// Generate our own File Handler
			FileHandler fh = new FileHandler(path, LimitRow, LimitFile, false);
				
			// Set your formatter
			fh.setFormatter(new SimpleFormatter());
				
			// Override base defaults
			logger.setUseParentHandlers(false);
			
			// Add this to the handler
			logger.addHandler(fh);
		} 
			
		// Failure in writing to the file
		catch (SecurityException e) {
			
			// Fall back to the default console
			logger.warning(e.getMessage() + e.getCause());
		} 
			
		// Input/Output Error
		catch (IOException e) {
			
			// Fall back to the default console
			logger.warning(e.getMessage() + e.getCause());
		}
		
		// Not what we wanted
		catch (IllegalArgumentException e) {
			
			// Fall back to the default console
			logger.warning(e.getMessage() + e.getCause());
		}
	
		// Set the debugging level
		// Confirm level is where it should be
		try {

			// Adjust out level
			myCurrent = Levels.valueOf(prefs.get("Debug.Level"));
			
			// Set the level
			logger.setLevel(Level.parse(prefs.get("Debug.Level")));
		}
		
		// Assign the default and worry no longer
		catch (IllegalArgumentException e) {
			
			logger.setLevel(DEFAULT);
		}
	}
	// End of Adjust Method
	
	
	/**
	 * This method will display the contents of a HashMap as a singular entry 
	 * in order cut down on entry lines for the log files and make it easier
	 * to group the contents together.
	 * <p>
	 * @param source Map containing elements that have to be displayed.
	 */
	public void debugHash(Map<String,String> source) {
		/*
		 * We only do the processing if the proper debugging level has bee
		 * requested.  Otherwise we are just wasting time.
		 */
		if (isLoggable("CONFIG")) {
			// Make it once nice space
			StringBuffer sb = new StringBuffer();
			
			// Loop through every key
			for (String key: source.keySet())
				sb.append(key + " - " + source.get(key) + CR);
			
			// Send the contents to the logging utility
			Config(sb.toString());
		}
	}
	// End of debugHash Method
	
	
	/**
	 * Provides the isLoggable capability through the wrapper itself.  Without
	 * this method it would mean that certain operations would be costly
	 * processor time wise.
	 * <p>
	 * @param lvl String version of the level we are interested in.
	 * @return boolean Simple true or false for insertion into IF statements.
	 */
	public boolean isLoggable(String lvl) {
		
		/*
		 * We quickly implement our own method since it will match our
		 * methodology and figuring logging at a larger level.
		 */
		
		// Severe warnings
		if (lvl.equals("SEVERE"))
			return SEVERE.contains(
				Levels.valueOf(lvl));

		// Warning elements
		else if (lvl.equals("WARN"))
			return WARNING.contains(
				Levels.valueOf(lvl));

		// Information Elements
		else if (lvl.equals("INFO"))
			return INFO.contains(
				Levels.valueOf(lvl));

		// Configuration elements
		else if (lvl.equals("CONFIG"))
			return CONFIG.contains(
				Levels.valueOf(lvl));

		// Fine grain
		else if (lvl.equals("FINE"))
			return FINE.contains(
				Levels.valueOf(lvl));
		
		// Fine grain
		else if (lvl.equals("FINEST"))
			return FINEST.contains(
				Levels.valueOf(lvl));

		// Any other level a mismatch
		return false;
	}
	// End of isLoggable Method
	
	
	/**
	 * Outputs a given string to the logger property making use of the CONFIG
	 * logging level.
	 * <p>
	 * @param msg String containing the full message.
	 */
	public void Config(String msg) {
		
		// Only if Necessary
		if (CONFIG.contains(myCurrent))
			Write(Level.CONFIG, msg);
	}
	// End of Configure Method
	
	
	/**
	 * Outputs a given string to the logger property making use of the FINE
	 * logging level.
	 * <p>
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param t String containing message
	 */	
	public void Fine(String cname, String cmethod, String t) {
		
		// Only if Necessary
		if (FINE.contains(myCurrent))
			Write(Level.FINE, cname, cmethod, t);
	}
	// End of Fine Method

	
	/**
	 * Outputs a given string to the logger property making use of the Finest
	 * logging level.
	 * <p>
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param t String containing message
	 */	
	public void Finest(String cname, String cmethod, String t) {
		
		// Only if Necessary
		if (FINEST.contains(myCurrent))
			Write(Level.FINEST, cname, cmethod, t);
	}
	// End of Finest Method
	
	
	/**
	 * Outputs a given string to the logger property making use of the INFO
	 * logging level.
	 * <p>
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param t String containing message
	 */	
	public void Info(String cname, String cmethod, String t) {
		
		// Only if Necessary
		if (INFO.contains(myCurrent))
			Write(Level.INFO, cname, cmethod, t);
	}
	// End of Info Method


	/**
	 * Outputs a given string to the logger property making use of the SEVERE
	 * logging level.
	 * <p>
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param t Throwable class which details errors.
	 */
	public void Severe(String cname, String cmethod, Throwable t) {
		
		// Only if Necessary
		if (SEVERE.contains(myCurrent))
			Write(Level.SEVERE, cname, cmethod, t);
	}
	// End of Severe Method

	
	/**
	 * Outputs a given string to the logger property making use of the WARN
	 * logging level.
	 * <p>
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param t Throwable class which details errors.
	 */
	public void Warn(String cname, String cmethod, Throwable t) {
		
		// Only if Necessary
		if (WARNING.contains(myCurrent))
			Write(Level.WARNING, cname, cmethod, t);
	}
	// End of Warn Method

	
	/**
	 * Outputs a given string to the logger property making use of the WARN
	 * logging level.
	 * <p>
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param err String formatted with error.
	 */
	public void Warn(String cname, String cmethod, String err) {
		
		// Only if Necessary
		if (WARNING.contains(myCurrent))
			Write(Level.WARNING, cname, cmethod, err);
	}
	// End of Warn Method
	
	/**
	 * Actually performs the action of writing to the log file by calling
	 * logger proper.
	 * <p>
	 * @param lvl Logging level
	 * @param msg Category of the message code from the language properties.
	 */	
	private void Write (Level lvl, String msg) {
		
		// Write to the logger
		logger.log(lvl, msg);	
		
		// Additional write
		if (DEBUG) System.out.println(msg);
	} 
	// End of Write Method
	
	
	/**
	 * Actually performs the action of writing to the log file by calling
	 * logger proper.
	 * <p>
	 * @param lvl Logging Level
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param t Throwable class 
	 */
	private void Write (Level lvl, String cname, String cmethod, Throwable t) {
		
		// Build the string
		String msg = t.getMessage() + ": " + t.getCause();
		
		// Write to the logger
		logger.logp(lvl, cname, cmethod, msg);	
		
		// Additional write
		if (DEBUG) System.out.println(msg);
	} 
	// End of Write Method

	/**
	 * Actually performs the action of writing to the log file by calling
	 * logger proper.
	 * <p>
	 * @param lvl Logging Level
	 * @param cname Class Name
	 * @param cmethod Class Method
	 * @param t String containing text
	 */
	private void Write (Level lvl, String cname, String cmethod, String t) {
		
		// Write to the logger
		logger.logp(lvl, cname, cmethod, t);	
		
		// Additional write
		if (DEBUG) System.out.println(t);
	} 
	// End of Write Method
}

