package routines;

import java.util.HashMap;

/**
 * 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 org.apache.logging.log4j.Logger loggerV2 = null; // to use log4j2

	private LOG_TYPE logType = LOG_TYPE.LOG_JAVA;
	private String classToLogFor = "unknown";
	String loggerName = loggerV2 == null ? "doLog: Error: loggerV2 not initialized" : loggerV2.getName();
	String jobName = "";

	/* 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_DEBUG; // 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(Object aObject) {
		int suffixPosition = loggerName.lastIndexOf(".");
		
		if (aObject instanceof org.apache.logging.log4j.Logger) {
			loggerV2 = (org.apache.logging.log4j.Logger) aObject;
			logType = LOG_TYPE.LOG_JOB;
			classToLogFor = "";
			jobName = loggerName.substring(suffixPosition + 1);
		} else if (aObject instanceof Class<?>) {
			Class<?> clazz = (Class<?>)aObject;

			classToLogFor = clazz.getCanonicalName();
			loggerV2 = org.apache.logging.log4j.LogManager.getLogger(clazz);
		} else {
			Class<?> clazz = aObject.getClass();

			classToLogFor = clazz.getCanonicalName();
			loggerV2 = org.apache.logging.log4j.LogManager.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 logPrefix = "";

		boolean doIt = (logType == LOG_TYPE.LOG_JOB && logLevel <= minJobLevel)
				|| (logType == LOG_TYPE.LOG_JAVA && logLevel <= minJavaLevel);

		if (!doIt)
			return "";

		logPrefix = String.format("%s: #%s#", classToLogFor, asString(logLevel));

		switch (logLevel) {
		case LOG_INFO:
		case LOG_DEBUG:
		case LOG_TRACE:
			if (loggerV2 != null)
				loggerV2.info(logPrefix + logText);
			break;
		case LOG_WARN:
			if (loggerV2 != null)
				loggerV2.warn(logPrefix + logText);
			break;
		case LOG_ERROR:
			if (loggerV2 != null)
				loggerV2.error(logPrefix + logText);
			break;
		case LOG_FATAL:
			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(Class aClass) {
		return new LcagLogger(aClass);
	}

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