package routines;

import java.util.HashMap;

import org.apache.log4j.Logger;
/**
 * A wrapper for Log4J for simple use in tJava, tJavaRow components.
 * It maps LCAG log levels to Apache Log levels so that all trace and debug message map to 'info' 
 * in Apache (other are mapped 1:1).  This avoids messing up LCAG business log output 
 * with Talend internal og messages in all Talend jobs. 
 * 	 
 * Change History:
 * V3.5.1: Full Redesign supporting LOG_LEVEL strings and restore
 * 
 * @author U401455
 *
 */
public class LcagLogger {
	public enum LOG_TYPE {
		LOG_JOB,
		LOG_JAVA,
		LOG_ANY
	}	
	
	private Logger loggerV1 = null;								// for backward compatibility using Log4j1
	private org.apache.logging.log4j.Logger loggerV2 = null;	// to use log4j2
	
	private LOG_TYPE logType = LOG_TYPE.LOG_JAVA;

	
	/* LOG Level, sequence taken over from apache commons logging */
	public static final int LOG_TRACE = 2;
	public static final int LOG_DEBUG = 1;
	public static final int LOG_INFO  = 0;
	public static final int LOG_WARN  = -1;
	public static final int LOG_ERROR = -2;
	public static final int LOG_FATAL = -3;
	public static final int LOG_OFF = -99;
	public static final int ILLEGAL_LEVEL = -999;
	
	private static HashMap<Integer, String>	int2Str = initInt2Str();
	private static HashMap<String, Integer>	str2Int = initStr2Int();
	
	private static int 	minJavaLevel = LOG_WARN;  // By default only log Java warnings or more critical
	private static int 	minJobLevel = LOG_TRACE;
	
	// backup values to use in restore LogLevel
	private static int 	lastJavaLevel = minJavaLevel; 
	private static int 	lastJobLevel = minJobLevel;

	private static final LcagLogger myLogger = getLogger(LcagLogger.class);
	
	private LcagLogger(Class<? extends Logger> aClass) { 
  		loggerV1 = Logger.getLogger(aClass);
  	}

  	private LcagLogger(Object aObject) {
  		if (aObject instanceof org.apache.logging.log4j.spi.AbstractLogger) {
  			loggerV2= (org.apache.logging.log4j.Logger)aObject;
  	  		logType = LOG_TYPE.LOG_JOB; 
  		} else if (aObject instanceof Logger) {
  			loggerV1 =  (Logger)aObject;
  	  		logType = LOG_TYPE.LOG_JOB; 
  		} else {
  			Class<?> clazz = aObject.getClass();
  			String 	 className = clazz.getCanonicalName();
  			loggerV1 = Logger.getLogger(clazz);
  		}
  	}

  	private static HashMap<Integer, String> initInt2Str() {
  		HashMap<Integer, String>	result = new HashMap<Integer, String>();
  		
  		result.put(LOG_TRACE, "TRACE");
  		result.put(LOG_DEBUG, "DEBUG");
  		result.put(LOG_INFO, "INFO");
  		result.put(LOG_WARN, "WARN");
  		result.put(LOG_ERROR, "ERROR");
  		result.put(LOG_FATAL, "FATAL");
  		result.put(LOG_OFF, "OFF");
  		
  		return result;
  	}
  	
  	private static HashMap<String, Integer> initStr2Int() {
  		HashMap<String, Integer>	result = new HashMap<String, Integer>();
  		
  		result.put("TRACE", LOG_TRACE);
  		result.put("DEBUG", LOG_DEBUG);
  		result.put("INFO", LOG_INFO);
  		result.put("WARN", LOG_WARN);
  		result.put("ERROR", LOG_ERROR);
  		result.put("FATAL", LOG_FATAL);
  		result.put("OFF", LOG_OFF);
  		
  		return result;
  	}
	
  	/**
  	 * Set Lcag Log level for Java function.
  	 * Note: This function must be be named setLevel, as this has side effects with Job ContextBean injection 
  	 * @param aLevel - Minimum log level to set.
  	 */
  	private static void setLogLevel(LOG_TYPE aType, String aTypeName, Object aLevel) {
  		Integer intLevel = asLevel(aLevel);
  		
  		if (intLevel == null) {
  			myLogger.error("setLogLevel for %s with aLevel=null called => ignored", aTypeName);
  		} else {
	  		if ( aType == LOG_TYPE.LOG_JAVA || aType == LOG_TYPE.LOG_ANY) 
	  			minJavaLevel = intLevel;
	  		if ( aType == LOG_TYPE.LOG_JOB || aType == LOG_TYPE.LOG_ANY) 
	  			minJobLevel = intLevel;
	
			if ( intLevel <= LOG_OFF ) {
	  			System.err.println(String.format("WARNING: LcagLogger %s LOG LEVEL DISABLED (!)", aTypeName ));
	  		} else { 
	  			System.out.println(String.format("LcagLogger: Info: %s LOG LEVEL set to '%s'", aTypeName, asString(intLevel)));
	  		}
  		}
  	}

  	public static int getJavaLogLevel() {
  		return minJavaLevel;
	} 

  	/**
  	 * Set Lcag Log level for Java function.
  	 * Note: This function must be be named setLevel, as this has side effects with Job ContextBean injection 
  	 * @param aLevel - Minimum log level to set.
  	 */
  	public static void setJavaLogLevel(Object aLevel) {
  		setLogLevel(LOG_TYPE.LOG_JAVA, "JAVA", aLevel);
  	}

  	public static int getJobLogLevel() {
  		return minJobLevel;
	} 

  	/**
  	 * Set Lcag Log level for jobs.
  	 * Note: This function must be be named setLevel, as this has side effects with Job ContextBean injection 
  	 * @param aLevel - Minimum log level to set.
  	 */
  	public static void setJobLogLevel(Object aLevel) {
  		setLogLevel(LOG_TYPE.LOG_JOB, "JOB", aLevel);
	} 

  	/**
  	 * Set Lcag Log level for jobs or Java function or both.
  	 * Note: This function must be be named setLevel, as this has side effects with Job ContextBean injection 
  	 * @param aLevel - Minimum log level to set.
  	 */
  	public static void setAllLogLevel(Object aLevel) {
  		setJavaLogLevel(aLevel);
  		setJobLogLevel(aLevel);
  	}

  	/**
  	 * Set Lcag Log level for jobs or Java function or both.
  	 * Note: This function must be be named setLevel, as this has side effects with Job ContextBean injection 
  	 * @param aLogType - Either LOG_TYPE.LOG_JAVA or LOG_TYPE.LOG_JOB or LOG_TYPE.LOG_ANY for both  
  	 * @param aLevel - Minimum log level to set.
  	 * @deprecated	Use setJobLogLevel, setJavaLogLevel or setAllLogLevel instead
  	 */
  	public static void setLogLevel(LOG_TYPE aLogType, Object aLevel) {

  		if ( aLogType == LOG_TYPE.LOG_JAVA) {
  			setJavaLogLevel(aLevel);
  		} else if ( aLogType == LOG_TYPE.LOG_JOB ) {
  			setJobLogLevel(aLevel);
  		} else if ( aLogType == LOG_TYPE.LOG_ANY ) {
  			setAllLogLevel(aLevel);
  		}
  	}

  	/**
  	 * Restore loglevel to previous values and remember current values.
  	 */
  	public static void restoreLogLevels() {
  		int	l = lastJavaLevel;
  		
  		System.out.println(String.format("LcagLogger: Levels restored for Jobs set to %s, Java Code set to %s",
  				int2Str.getOrDefault(lastJobLevel, "OTHER"), int2Str.getOrDefault(lastJavaLevel, "OTHER"))
  		);
  		lastJavaLevel = minJavaLevel;
  		minJavaLevel  = l;
  		
  		l				= lastJobLevel;
  		lastJobLevel	= minJobLevel;
  		minJobLevel		= l;
  	}

  	public static String asString(int aLogLevel) {
  		return int2Str.getOrDefault(aLogLevel, "OTHER");
  	}
  	
  	public static Integer asLevel(Object aLogLevel) {
  		Integer result = null;
  		String errMsg = null;
  		
  		if (aLogLevel != null) {
  			if (aLogLevel instanceof Integer) {
  				result = (Integer)aLogLevel;
  			} else if ( aLogLevel instanceof String) {
  				String logLevelStr = ((String)aLogLevel).trim().toUpperCase();
  				
  				if ( "".equals(logLevelStr) ) {
  					result = null;
  				} else if (logLevelStr.startsWith("LOG_")) {
	  				logLevelStr = logLevelStr.substring(4);
	  			}
	  			
	  			result = str2Int.get(logLevelStr);
  			}
  		} 
  		
  		return result;
  	}

  	public String format(String fmt, Object ...valueList) {
  		String result = String.format(fmt,  valueList);
  		
  		return result;
  	}
  	
  	private String doLog(int logLevel, String fmt, Object ...valueList) {
  		String logText = format(fmt, valueList);
  		String loggerName = loggerV2 == null ? loggerV1.getName() : loggerV2.getName();
  		String logPrefix = "";
  		int suffixPosition = loggerName.lastIndexOf(".");
  		String jobName = loggerName.substring(suffixPosition + 1);
  		
  		boolean doIt = ( logType == LOG_TYPE.LOG_JOB && logLevel <= minJobLevel ) || ( logType == LOG_TYPE.LOG_JAVA && logLevel <= minJavaLevel );
  		
  		if (! doIt)
  			return "";
  		
  		logPrefix = "#" + asString(logLevel) + "#";

  		switch (logLevel) {
  			case LOG_INFO:
  			case LOG_DEBUG:
			case LOG_TRACE:
  				if (loggerV1 != null) loggerV1.info(logPrefix + logText);
  				if (loggerV2 != null) 
  					loggerV2.info(logPrefix + logText);
  				break;
  			case LOG_WARN:
  				if (loggerV1 != null) loggerV1.warn(logPrefix + logText);
  				if (loggerV2 != null) 
  					loggerV2.warn(logPrefix + logText);
  				break;
  			case LOG_ERROR:
  				if (loggerV1 != null) loggerV1.error(logPrefix + logText);
  				if (loggerV2 != null) 
  					loggerV2.error(logPrefix + logText);
  				break;
  			case LOG_FATAL:
  				if (loggerV1 != null) loggerV1.fatal(logPrefix + logText);
  				if (loggerV2 != null) 
  					loggerV2.fatal(logPrefix + logText);
  				break;
  			default:
  				break;
		}
		
		//FIXME: logger.* does not produce output! drop next line after this is fixed.
		if (logLevel >  LOG_ERROR) {
			System.out.println(logPrefix  + jobName + " - " + logText);
		} else {
			System.err.println(logPrefix  + jobName + " - " + logText);
		}
		
		return logText;
  	}
  	
  	public String trace(String fmt, Object ...valueList) {
  		String logText = doLog(LOG_TRACE, fmt, valueList);
  		
  		return logText;
  	}
  	
  	public String debug(String fmt, Object ...valueList) {
  		String logText = doLog(LOG_DEBUG, fmt, valueList);

  		return logText;
  	}
  	
  	public String info(String fmt, Object ...valueList) {
  		String logText = doLog(LOG_INFO, fmt, valueList);

  		return logText;
  	}
  	
  	public String warn(String fmt, Object ...valueList) {
  		String logText = doLog(LOG_WARN, fmt, valueList);

  		return logText;
  	}
  	
  	public String error(String fmt, Object ...valueList) {
  		String logText = doLog(LOG_ERROR, fmt, valueList);

  		return logText;
  	}
  	
  	public String fatal(String fmt, Object ...valueList) {
  		String logText = doLog(LOG_FATAL, fmt, valueList);

  		return logText;
  	}
  	
  	public static LcagLogger getLogger(Logger aLogger) {
  		return new LcagLogger(aLogger);
  	}

  	public static LcagLogger getLogger(Class aClass) {
  		return new LcagLogger(aClass);
  	}

  	public static LcagLogger getLogger(Object aObject) {
  		return new LcagLogger(aObject);
  	} 	
}
