package cz.cuni.amis.pogamut.base.agent.jmx;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import com.google.inject.Inject;

import cz.cuni.amis.pogamut.base.agent.IAgent;
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.Pogamut;
import cz.cuni.amis.pogamut.base.utils.PogamutJMX;
import cz.cuni.amis.pogamut.base.utils.logging.AgentLogger;
import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
import cz.cuni.amis.utils.ExceptionToString;

/**
 * Wraps a few methods into one place so it won't plague the public method
 * space of the agent. (Make things a bit clear...).
 * <p><p>
 * Contains list of IJMXEnabled components that should be enabled when the
 * whole JMX feature of the agent is fired up.
 * <p><p>
 * Note that jmx domain is taken from the java property "pogamut.jmx.domain".
 * 
 * @author Jimmy
 */
@AgentScoped
public class AgentJMX {

	/**
	 * MBeanServer the JMX is currently using. (If null the jmx is not
	 * enabled.)
	 */
	private MBeanServer mBeanServer = null;

	/**
	 * Current domain name of the objects that are registred by this agent
	 * and its components.
	 * <p>
	 * <p>
	 * Note that every agent MUST HAVE its own unique domain.
	 */
	private String jmxDomain = null;

	/**
	 * List of IJMXEnabled components that are enabled when enableJMX() is
	 * called.
	 */
	private List<IJMXEnabled> jmxComponents = new ArrayList<IJMXEnabled>();

	/**
	 * ObjectName of the agent owning this class.
	 */
	private ObjectName agentJMXName = null;

	private AgentLogger agentLogger;

	private LogCategory log;

	/**
	 * Agent that owns the JMX (equally the agent this object supports).
	 */
	private IAgent agent;
	
	@Inject
	public void AgentJMX(IAgent agent) {
		this.agent = agent;
		this.agentLogger = agent.getLogger();
		this.log = agentLogger.platform();
	}

	/**
	 * Adding new IJMXEnabled component to the list - registering it so it
	 * will be notified when the enableJMX() is called.
	 * 
	 * @param component
	 */
	public void addComponent(IJMXEnabled component) {
		synchronized (jmxComponents) {
			jmxComponents.add(component);
		}
	}

	/**
	 * MBeanServer, if null the jmx is not enabled.
	 * 
	 * @return
	 */
	public MBeanServer getMBeanServer() {
		return mBeanServer;
	}

	/**
	 * JMX domain of the whole agent - used to construct ObjectName
	 * instances. If null the jmx is not enabled.
	 * 
	 * @return
	 */
	public String getJMXDomain() {
		return jmxDomain;
	}

	/**
	 * Whether the JMX is enabled or not.
	 * 
	 * @return
	 */
	public boolean isJMXEnabled() {
		return jmxDomain != null;
	}

	/**
	 * Returns ObjectName of the agent.
	 * 
	 * @return
	 */
	public ObjectName getAgentJMXName() {
		return agentJMXName;
	}

	/**
	 * Used to test the jmxDomain name whether it is OK. It tries to
	 * instantiate an ObjectName, if it failes it tries to assigned random
	 * unique domain, if it fails we're doomed and the JMX can't be started
	 * (returns null).
	 * 
	 * @param jmxDomain
	 *            tested domain name
	 * @return jmx domain name that is valid, if null - JMX can't be started
	 */
	private String testJMXDomainName(String jmxDomain) {
		try {
			new ObjectName(PogamutJMX.getJMXName(jmxDomain, "testType",
					"testName"));
		} catch (MalformedObjectNameException e) {
			// jmxDomain is probably malformed
			String newJmxDomain = "jmxDomainMalformed_"
					+ UUID.randomUUID().toString();
			log.warning(
					"Domain name '" + jmxDomain
							+ "' probably malformed, trying to change to '"
							+ newJmxDomain + "'.");
			try {
				new ObjectName(PogamutJMX.getJMXName(newJmxDomain,
						"testType", "testName"));
			} catch (MalformedObjectNameException e1) {
				log
						
						.severe(
								"Domain name '"
										+ newJmxDomain
										+ "' seems also malformed, can't start JMX.");
				return null;
			} catch (NullPointerException e1) {
				log.severe(
						"Domain name '" + newJmxDomain
								+ "' seems also malformed.");
				return null;
			}
			return newJmxDomain;
		} catch (NullPointerException e) {
			log.log(Level.SEVERE,
					"Can't create object name with domain '" + jmxDomain
							+ "'.");
			return null;
		}
		return jmxDomain;
	}

	/**
	 * This enables the JMX feature on the whole agent notifying all
	 * IJMXEnabled components to register itself to provided mBeanServer.
	 * <p><p>
	 * Note that jmxDomain must be well-formed in JMX Object Name sense.
	 * 
	 * @param mBeanServer
	 * @param jmxDomain
	 * @return
	 * @throws JMXAlreadyEnabledException
	 * @throws CantStartJMXException
	 */
	public boolean enableJMX(MBeanServer mBeanServer) throws JMXAlreadyEnabledException,
			CantStartJMXException {
		boolean exception = false;
		synchronized (jmxComponents) {
			if (isJMXEnabled())
				throw new JMXAlreadyEnabledException(
						"JMX is already enabled on the agent.", log);

			/*/ start - jmx domain name test
			String testedJmxDomain = testJMXDomainName(jmxDomain);
			if (testedJmxDomain == null)
				throw new CantStartJMXException(
						"Can't start JMX on the agent, JMX Domain name '"
								+ jmxDomain + "' probably malformed.",
						log);
			jmxDomain = testedJmxDomain;
			// end - jmx domain name test

			this.jmxDomain = jmxDomain;
                             */ 
                     
                            jmxDomain = Pogamut.getPlatform().getProperty("pogamut.jmx.domain");
                            // create ObjectName for the agent
                            try {
			// TODO remove this
                                String id = UUID.randomUUID().toString();
                                agentJMXName = ObjectName
                                                .getInstance(jmxDomain+":name="+agent.getName().replace('.', '_')+"_"+id+",type=agent");
                            } catch (MalformedObjectNameException ex) {
                                    throw new CantStartJMXException("Malformed agent ObjectName.", ex);
                            }

			
                            
              		// export the agent itself                         
                            try {
                                // create the MBean for agent
                                AgentMBeanAdapter agentMBean = new AgentMBeanAdapter(agent);	
                                mBeanServer.registerMBean(agentMBean, agentJMXName);
			} catch (Exception ex) {
				throw new CantStartJMXException(
						"Agent MBeans cannot be registered.", ex, log);
			}
			// type for all sub MBeans
			String extType = agentJMXName.getKeyProperty("type") + "."
					+ agentJMXName.getKeyProperty("name");
			for (IJMXEnabled jmxComponent : jmxComponents) {
				try {
					jmxComponent.enableJMX(mBeanServer, jmxDomain, extType);
				} catch (JMXAlreadyEnabledException e) {
					exception = true;
					log.log(
							Level.SEVERE,
							ExceptionToString
									.process(
											"JMXComponent(class="
													+ jmxComponent
															.getClass()
															.getName()
													+ ",name="
													+ jmxComponent
															.toString()
													+ ") states that it's already enabled.",
											e));
				} catch (CantStartJMXException e) {
					exception = true;
					log.severe(ExceptionToString.process(
							"JMXComponent(class="
									+ jmxComponent.getClass().getName()
									+ ",name=" + jmxComponent.toString()
									+ ") can't start it's JMX.", e));
				}
			}
		}
		return !exception;
	}
}
