package cz.cuni.amis.pogamut.base.utils.logging;

import java.lang.management.ManagementFactory;
import java.util.Random;
import java.util.logging.Handler;
import java.util.logging.Level;

import javax.management.MBeanServer;

import cz.cuni.amis.pogamut.base.agent.exceptions.CantStartJMXException;
import cz.cuni.amis.pogamut.base.agent.exceptions.JMXAlreadyEnabledException;
import cz.cuni.amis.pogamut.base.factory.guice.AgentScoped;
import cz.cuni.amis.pogamut.base.utils.IJMXEnabled;
import cz.cuni.amis.pogamut.base.utils.logging.jmx.JMXLogCategories;
import cz.cuni.amis.utils.ExceptionToString;

/**
 * All logging apis are fine ... but we don't want to have 
 * loggers for classes but for instances - therefore we've created
 * our wrapper allowing you to do two things quickly:
 * <ol>
 * <li>log things</li>
 * <li>create new logger categories</li>
 * </ol>
 * 1) that's obvious it should be easy
 * <p>
 * 2) this may prove crucial for your debugging to have own logger
 * for planner another for emotions of your agents, etc.
 * <p><p>
 * Simply - every Agent instance (starting with the first abstract class
 * AbstractAgent) has instance of this class (which is java.logging.Logger(s) wrapper).
 * <p>
 * Every Agent instance (via Agent logger) contains 4 types of logs:
 * <ol>
 * <li>platform log - used to log things from PogamutBase library</li>
 * <li>in log - serves for logging messages that comes from the world to the agent</li>
 * <li>out log - serves for logging commands that are sent by the agent to the world</li>
 * <li>user log - reserved solely for agent developers as default log</li>
 * </ol>
 * <p><p>
 * Additionally you may create more logger categories (which wraps another Logger instances).
 * <p><p>
 * LogCategory serves as a gateway for your log messages, it contains methods as you
 * know them from java.logging API (things like fine(), info(), severe(), log(Level, msg), etc.).
 * Plus it allows you to obtain new LogHandler instances for that category (if you need to 
 * publish log messages from that category somewhere else).
 * <p>
 * As was told before - four default categories exists and their instances may be
 * obtained through methods:
 * <ul>
 * <li>platform() - platform category - reserved by PogamutBase library</li> 
 * <li>in() - category for incoming messages - reserved by PogamutBase library</li>
 * <li>out() - category for outgoing commands - reserved by PogamutBase library</li>
 * <li>user() - category for your messages - PogamutBase library won't ever touch this</li>
 * </ul> 
 * <p>
 * Every LogHandler serves for filtering messages for one category and publishing them
 * into one end (console, file, memory, whatever...).
 * <p><p>
 * <b>WARNING:</b><p>
 * As default the categories don't have associated any handler therefore
 * it won't log anything anywhere. You have to specify them for yourself!
 * <p>
 * The quickest way is to call method addConsoleHandlersToAllCategories() after instantiating this class.
 * <p><p>
 * Example of usage:
 * agentLoggerInstance().addConsoleHandlersToAllCategories()</p>
 * agentLoggerInstance().in().info("Hello message came");<p>
 * agentLoggerInstance().user().warning("My agent is panicking!");<p>
 * LogCategory emotionLog = agentLoggerInstace.getCategory("Emotions");<p>
 * emotionLog.severe("Emotions not implemented yet...");
 * 
 * @author Jimmy
 */
@AgentScoped
public class AgentLogger implements IJMXEnabled {
		
	/**
	 * Log categories of this logger, serves to manage categories inside the log.
	 */
	protected ILogCategories categories;

	/**
	 * Enum with names of default categories.
	 * @author Jimmy
	 */
	public enum CategoryNames {
		IN("In"),
		OUT("Out"),
		USER("User"),
		PLATFORM("Platform");
		
		private String name;
		
		private CategoryNames(String name) {
			this.name = name;
		}
		
		public String getName() {
			return name;
		}
	}
	
	/**
	 * Log category for incoming messages from the world.
	 * <p><p>
	 * <b>Reserved by PogamutBase library.</b>
	 */
	protected LogCategory in;
	
	/**
	 * Log category for outgoing commands from the agent to the world.
	 * <p><p>
	 * <b>Reserved by PogamutBase library.</b>
	 */
	protected LogCategory out;
	/**
	 * Log category for the PogamutBase library messages.
	 * <p><p>
	 * <b>Reserved by PogamutBase library.</b>
	 */
	protected LogCategory platform;
	/**
	 * Log category for the agent developer - PogamutBase library won't ever touch this.
	 */
	protected LogCategory user;
	
	/**
	 * During construction the four default categories are initialized.
	 * <p><p>
	 * The MBeanServer is not set therefore won't be used!
	 * 
	 * @param logger
	 */
	public AgentLogger() {
		categories = new LogCategories();		
		initDefaultLoggingCategories();
	}
	
	public AgentLogger(LogCategories logCategories) {
		categories = logCategories;
		initDefaultLoggingCategories();
	}
	
	/**
	 * Used to initialized four default logging categories, called only from constructors.
	 * <p>
	 * LogCategories (categories field) must be initialized prior to this.
	 * <p><p>
	 * <B>DO NOT CALL FROM ANYWHERE ELSE!</b>	 * 
	 */
	private void initDefaultLoggingCategories() {		
		in = categories.getCategory(CategoryNames.IN.getName());
		out = categories.getCategory(CategoryNames.OUT.getName());
		user = categories.getCategory(CategoryNames.USER.getName());
		platform = categories.getCategory(CategoryNames.PLATFORM.getName());
	}
	
	/**
	 * Returns LogCategory for specified name. If category with
	 * this name doesn't exist new is created.
	 * @param name
	 * @return
	 */
	public LogCategory getCategory(String name) {
		return categories.getCategory(name);
	}
	
	/**
	 * Return categories wrapper - useful when you need to know
	 * all the names of agent logger categories.
	 * @return
	 */
	public ILogCategories getCategories() {
		return categories;
	}
	
	/**
	 * Returns category for incoming messages (world to agent).
	 * <p><p>
	 * <b>Reserved by PogamutBase library.</b>
	 * 
	 * @return
	 */
	public LogCategory in() {
		return this.in;
	}
	
	/**
	 * Returns category for outgoing message (agent to world).
	 * <p><p>
	 * <b>Reserved by PogamutBase library.</b>
	 * 
	 * @return
	 */
	public LogCategory out() {
		return this.out;
	}
	
	/**
	 * Returns category for the agent developer.
	 * @return
	 */
	public LogCategory user() {
		return this.user;
	}
	
	/**
	 * Returns category for the PogamutBase library.
	 * <p><p>
	 * <b>Reserved by PogamutBase library.</b>
	 * 
	 * @return
	 */
	public LogCategory platform() {
		return this.platform;
	}
	
	/**
	 * Adds new handler to every existing category with the console publisher (LogPublisher.ConsolePubslisher class).
	 */
	public void addConsoleHandlersToAllCategories() {
		addPublisherToAllCategories(new LogPublisher.ConsolePublisher());
	}

	/**
	 * Adds new publisher to all categories.
	 * @param logPublisher
	 */
	public void addPublisherToAllCategories(ILogPublisher logPublisher) {
		addHandlerToAllCategories(new LogHandler(logPublisher));		
	}
	
	
	/**
	 * Adds new handler to all categories.
	 */
	public void addHandlerToAllCategories(Handler handler) {
		for (LogCategory category : categories.getCategories().values()) {
			category.addHandler(handler);
		}
	}
	
	/**
	 * Set level for all handlers of all categories.
	 * @param newLevel
	 */
	public void setLevel(Level newLevel) {
		categories.setLevel(newLevel);
	}
	
	@Override
	public synchronized void enableJMX(MBeanServer mBeanServer, String jmxDomain, String type) throws JMXAlreadyEnabledException, CantStartJMXException {
	//TODO use type	
            if (categories instanceof JMXLogCategories) throw new JMXAlreadyEnabledException("AgentLogger has already JMX turned on.", platform(), this);
		try {
			ILogCategories newCategories = new JMXLogCategories(categories, mBeanServer, jmxDomain, type);
			categories = newCategories;
		} catch (Exception e) {
			throw new CantStartJMXException(ExceptionToString.process("Can't start JMX for agent logger", e), platform(), this);
		}
	}
	
	//
	// TESTING JMX AGENT LOGGER
	//
	
	public static void main(String[] args) throws Exception {
		MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
		
		AgentLogger logger = new AgentLogger();
		logger.enableJMX(mbs, "JMXAgentLoggerTest", "testType");
		
		Random rnd = new Random(System.currentTimeMillis());
		
		while(true) {
			long sleep = rnd.nextInt(3000) + 1000;
			Thread.sleep(sleep);
			logger.in().info("--IN-- slept for " + sleep);
			logger.out().info("--OUT-- slept for " + sleep);
			logger.platform().info("--PLATFORM-- slept for " + sleep);
			logger.user().info("--USER-- slept for " + sleep);
		}
				
	}

}
