package kg.apc.jmeter.jmxmon;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

/**
 * This class store jmx connections
 * It is not a complete pool, it will not manage lease on connection
 * It only provide the same connection for the same jmx url
 *
 */
public class JMXMonConnectionPool {
	
	/**
	 * the logger
	 */
	private static final Logger log = LoggerFactory.getLogger(JMXMonConnectionPool.class);

	/**
	 * Store {@link JMXMonConnection}
	 */
	private Map<String, JMXMonConnection> pool;
	
	/**
	 * The constructor
	 */
	public JMXMonConnectionPool() {
		super();
		pool = new HashMap<String, JMXMonConnection>();
	}
	
	/**
	 * Try to get a connection to the specified jmx url
	 * @param jmxUrl the jmx url
	 * @param attributes jmx connection attributes
	 * @return a jmx connection
	 */
	public MBeanServerConnection getConnection(String jmxUrl, Hashtable attributes)
	{
		return getConnection(jmxUrl, attributes, false);
	}
	
	/**
	 * Try to get a connection to the specified jmx url
	 * @param jmxUrl the jmx url
	 * @param attributes jmx connection attributes
	 * @param wait if true wait the current thread until the end of the connection attempt
	 * @return a jmx connection
	 */
	public MBeanServerConnection getConnection(String jmxUrl, Hashtable attributes, boolean wait)
	{
		JMXMonConnection connection = (JMXMonConnection)pool.get(jmxUrl);
		if (connection == null) {
			connection = new JMXMonConnection(jmxUrl);
			pool.put(jmxUrl, connection);
		}
		return connection.connect(attributes, wait);

	}
	

	/**
	 * Close all active connections by closing linked {@link JMXConnector}
	 */
	public void closeAll() {
		for (Object connection : pool.values()) {
			JMXMonConnection jmxcon = (JMXMonConnection)connection;
            if (jmxcon.connector != null) {
                try {
                	jmxcon.connector.close();
                    log.debug("jmx connector is closed");
                } catch (Exception ex) {
                    log.debug("Can't close jmx connector, but continue");
                }
            } else {
                log.debug("jmxConnector == null, don't try to close connection");
            }
            
            jmxcon.connector = null;
            jmxcon = null;
            
        }
		/* erase pool informations for this run but will be recreate for the next run */
		pool.clear();
	}

	/**
	 * This class store jmx connection data
	 * and ensure that only one connection attempt
	 * is made simultaneously
	 *
	 */
	private class JMXMonConnection {
		
		/**
		 * jmx url
		 */
		private String jmxUrl;
		
		/**
		 * jmx connector or null if not connected
		 */
		private JMXConnector connector;
		/**
		 * jmx connection or null if not connected
		 */
		private MBeanServerConnection connection;
		/**
		 * thread used to handle connection attempt
		 */
		private Thread connectionAttemptThread;
		/**
		 * semaphore : true if a connection attempt is running
		 */
		private boolean connectionAttemptFlag;
		
		/**Construtor
		 * 
		 * @param jmxUrl jmx url
		 */
		private JMXMonConnection(String jmxUrl){
			this.jmxUrl = jmxUrl;
		}
		
		/**
		 * Empty URL denotes local connection
		 */
		private boolean isLocalConnection() {
			return jmxUrl.isEmpty();
		}
		
		/**
		 * Start a connection thread if none are running
		 * @param attributes jmx connection attributes
		 * @param wait if true wait the current thread until the end of the connection attempt
		 */
		protected synchronized void tryConnect(final Hashtable attributes, boolean wait)
		{
			if (isLocalConnection()) {
				log.debug("Using local PlatformMBeanServer connection");
				connection = ManagementFactory.getPlatformMBeanServer();
				return;
			}
			connectionAttemptFlag = true;
			
			connectionAttemptThread = new Thread(new Runnable() {
				
				@Override
				public void run() {
					try {
						JMXServiceURL u = new JMXServiceURL(jmxUrl);
						log.debug("Create new connection url = " + jmxUrl);
			            connector = JMXConnectorFactory.connect(u, attributes);
			            connection = connector.getMBeanServerConnection();
			            
		            } catch (MalformedURLException ex) {
		                //throw new RuntimeException(ex);
		                log.error("Malformed JMX url", ex);
		            } catch (IOException ex) {
		                log.error("IOException reading JMX", ex);
		            }
					finally {
						connectionAttemptFlag = false;
					}
				}
			});
			
			connectionAttemptThread.start();
			
			if (wait)
				try {
					connectionAttemptThread.join();
				} catch (InterruptedException e) {
					log.warn("Connection thread has been interrupted", e);
				}
			
		}
		
		/**
		 * Check if a connection already exists if true it will return the
		 * existing connection else it will attempt a new connection
		 * @param attributes jmx connection attributes
		 * @param wait if true wait the current thread until the end of the connection attempt
		 * @return a jmx connection or null
		 */
		protected MBeanServerConnection connect(Hashtable attributes, boolean wait) {

			if (connection != null){
				log.debug("Reused the same connection for url = " + (isLocalConnection()?"(local JVM)":jmxUrl));
				return connection;
			}
			
			if (!connectionAttemptFlag)
				tryConnect(attributes, wait);
			
			return connection;
		}
        
		/**
		 * Store a new jmx connection
		 * @param connector the jmx connector
		 * @param connection the jmx connection
		 */
		protected void setNewActiveConnection(JMXConnector connector, MBeanServerConnection connection)
		{
			this.connector = connector;
			this.connection = connection;
		}

	}

	/**
	 * Allow external class to notify the pool that the connection identified by the provided
	 * jmx url is closed/in error 
	 * @param url the jmx url
	 */
	public void notifyConnectionDirty(String url) {
		JMXMonConnection connection = (JMXMonConnection)pool.get(url);
		
		if (connection != null){
			connection.setNewActiveConnection(null, null);
		}
	}

}
