/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2012 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.log;

import java.io.File;
import java.io.PrintWriter;
import java.util.concurrent.locks.ReentrantLock;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;

import divconq.lang.TaskContext;
import divconq.lang.Memory;
import divconq.util.HexUtil;
import divconq.xml.XElement;

/**
 * When logging messages to the debug log each message has a debug level.
 * The logger has a filter level and messages of lower priority than the 
 * current debug level will not be logged.
 * 
 * Note that 99% of the time the "current" debug level is determined by
 * the current TaskContext.  The preferred way to log messages is through 
 * the TaskContext or through an OperationResult.  Ultimately a filter
 * is used to determine what should go in the log.  
 * 
 * In fact, when you call "void error(String message, String... tags)"
 * and other logging methods, theses methods will lookup the current
 * task context.  So it is more efficient to work directly with task
 * context, however, occasional calls to these global logger methods
 * are fine.
 * 
 * @author Andy
 *
 */
public class Logger {
    static private DebugLevel globalLevel = DebugLevel.Info;
    
    // typically task logging is handled by a service on the bus, but on occasions
    // we want it to log to the file as well, from settings change this to 'true' 
    static private boolean toFile = true;
    static private boolean toConsole = true;
    
    static protected PrintWriter logWriter = null;
    static protected ReentrantLock writeLock = new ReentrantLock();  
    
    static public DebugLevel getGlobalLevel() {
        return Logger.globalLevel; 
    }
   
    /**
     * Called from Hub.start this method configures the logging features.
     * 
     * @param config xml holding the configuration
     */
    static public void init(XElement config) {
    	// TODO return operation result
    	
    	// TODO load levels, path etc
    	// include a setting for startup logging - if present set the TC log level directly
    	
    	try {
    		File logfile = new File("./logs/" 
					+ DateTimeFormat.forPattern("yyyyMMdd'_'HHmmss").print(new DateTime(DateTimeZone.UTC))
					+ ".log"); 
    		
    		logfile.createNewFile();
    		
    		Logger.trace("Opening log file: " + logfile.getCanonicalPath());
    		
			Logger.logWriter = new PrintWriter(logfile, "utf-8");
			
			
		} 
    	catch (Exception x) {
			// TODO Auto-generated catch block
		}
    }
    
    /**
     *  In a distributed setup, DivConq may route logging to certain Hubs and
     *  bypass the local log file.  During shutdown logging returns to local
     *  log file so that the dcBus can shutdown and stop routing the messages.
     */
    static public void stop() {
    	// TODO return operation result
    	
    	Logger.toFile = true;		// go back to logging to file
    	
    	// TODO say no to database
    }
    
    /**
     * Insert a (string) message into the log
     * 
     * @param message error text
     * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent
     */
    static public void error(String message, String... tags) {
    	Logger.log(TaskContext.get(), DebugLevel.Error, message, tags);
    }
    
    /**
     * Insert a (string) message into the log
     * 
     * @param message warning text
     * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent
     */
    static public void warn(String message, String... tags) {
    	Logger.log(TaskContext.get(), DebugLevel.Warn, message, tags);
    }
    
    /**
     * Insert a (string) message into the log
     * 
     * @param message info text
     * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent
     */
    static public void info(String message, String... tags) {
    	Logger.log(TaskContext.get(), DebugLevel.Info, message, tags);
    }
    
    /**
     * Insert a (string) message into the log
     * 
     * @param message trace text
     * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent
     */
    static public void trace(String message, String... tags) {
    	Logger.log(TaskContext.get(), DebugLevel.Trace, message, tags);
    }
    
    /**
     * Insert a (string) message into the log
     * 
     * @param ctx context for log settings, null for none
     * @param level message level
     * @param message text to store in log
     * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent
     */
    static public void log(TaskContext ctx, DebugLevel level, String message, String... tags) {
    	DebugLevel setlevel = (ctx != null) ? ctx.getLevel() : Logger.globalLevel;
    	
    	// do not log, is being filtered
    	if (setlevel.getCode() < level.getCode())
    		return;
    	
    	String indicate = "M" + level.getIndicator();
    	
		/* TODO
    	if (Logger.toDatabase) {
			Message lmsg = new Message("Logger");
			lmsg.addHeader("Op", "Log");
			lmsg.addHeader("Indicator", indicate);
			lmsg.addHeader("Occurred", occur);
			lmsg.addHeader("Tags", tagvalue);			
			lmsg.addStringAttachment(message);
	        Hub.instance.getBus().sendMessage(lmsg);
    	}
    	*/

    	// write to file if not a Task or if File Tasks is flagged
    	if (Logger.toFile || Logger.toConsole) {
    		if (message != null)
    			message = message.replace("\n", "\n\t");		// tab sub-lines
	
	        Logger.write(ctx, indicate, message, tags);
    	}
    }

    /**
     * Insert a chunk of hex encoded memory into the log
     * 
     * @param ctx context for log settings, null for none
     * @param level message level
     * @param data memory to hex encode and store
     * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent
     */
    static public void log(TaskContext ctx, DebugLevel level, Memory data, String... tags) {
    	DebugLevel setlevel = (ctx != null) ? ctx.getLevel() : Logger.globalLevel;
    	
    	// do not log, is being filtered
    	if (setlevel.getCode() < level.getCode())
    		return;
    	
    	String indicate = "H" + level.getIndicator();
    	
		/* TODO
    	if (tc != null) {
			Message lmsg = new Message("Logger");
			lmsg.addHeader("Op", "Log");
			lmsg.addHeader("Indicator", indicate);
			lmsg.addHeader("Occurred", occur);
			lmsg.addHeader("Tags", tagvalue);			
			lmsg.addAttachment(data);
	        Hub.instance.getBus().sendMessage(lmsg);
    	}
    	*/

    	// write to file if not a Task or if File Tasks is flagged
    	if (Logger.toFile || Logger.toConsole) 
	        Logger.write(ctx, indicate, HexUtil.bufferToHex(data), tags);
    }

    /**
     * A boundary delineates in section of a task log from another, making it
     * easier for a log viewer to organize the content.  Boundary's are treated
     * like "info" messages, if only errors or warnings are being logged then 
     * the boundary entry will be skipped.
     *  
     * @param ctx context for log settings, null for none
     * @param tags searchable values associated with the message, key-value pairs can be created by putting two tags adjacent
     */
    static public void boundary(TaskContext ctx, String... tags) {
    	DebugLevel setlevel = (ctx != null) ? ctx.getLevel() : Logger.globalLevel;
    	
    	// do not log, is being filtered
    	if (setlevel.getCode() < DebugLevel.Info.getCode())
    		return;
    	
		/* TODO
    	if (tc != null) {
			Message lmsg = new Message("Logger");
			lmsg.addHeader("Op", "Log");
			lmsg.addHeader("Indicator", "B");
			lmsg.addHeader("Occurred", occur);
			lmsg.addHeader("Tags", tagvalue);			
	        Hub.instance.getBus().sendMessage(lmsg);
    	}
    	*/

    	// write to file if not a Task or if File Tasks is flagged
    	if (Logger.toFile || Logger.toConsole) 
	        Logger.write(ctx, "B   ", "", tags);
    }
    
    static protected void write(TaskContext ctx, String indicator, String message, String... tags) {
    	String taskid = (ctx != null) ? ctx.getTaskId() : "00000_197000101T000000000Z_000000000000000";
    	
    	DateTime occur = new DateTime(DateTimeZone.UTC);
    	String tagvalue = "";
    	
		if ((tags != null) && tags.length > 0) {
	        tagvalue = "|";
	
	        for (String tag : tags) 
	        	tagvalue += tag + "|";
		}
    	
        Logger.write(occur  + " " + taskid + " " + indicator + " " + tagvalue + " " + message);
    }
    
    static protected void write(String msg) {
    	if (Logger.toConsole)
    		System.out.println(msg);
    	
    	if (!Logger.toFile || (Logger.logWriter == null))
    		return;
    	
    	Logger.writeLock.lock();
    	
        try {
        	Logger.logWriter.println(msg);
        	Logger.logWriter.flush();
        }
        catch (Exception x) {
            // ignore, logger is broken  
        }
        
        Logger.writeLock.unlock();
    }
}
