/**
 * Platform.java
 * Created Jul 9, 2008
 * Copyright (c) TANDBERG Television 2007-2008
 */
package com.tandbergtv.watchpoint.pmm.boot;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.tandbergtv.watchpoint.pmm.util.XMLDocumentUtility;

/**
 * Central class of the PMM runtime. Performs startup and shutdown of bundles in the system.
 * This class cannot be subclassed.
 * 
 * @author Sahil Verma
 */
public final class Platform {
	
	private static final Platform platform = new Platform();
	
	private static final Logger logger = Logger.getLogger(Platform.class);
	
	private Platform() {
	}
	
	/**
	 * Returns the platform
	 * 
	 * @return
	 */
	public static Platform getPlatform() {
		return platform;
	}

	/**
	 * Starts the platform. All available bundle activators are started.
	 */
	public void start() {
		Document document = readDefaultConfiguration();
		List<Activator> activators = getActivators(document);

		start(activators);
	}
	
	/**
	 * Start the platform. Bundle activators are read from the specified path.
	 * 
	 * @param configuration
	 */
	public void start(String configuration) {
		Document document = readConfiguration(new File(configuration));
		List<Activator> activators = getActivators(document);

		start(activators);
	}
	
	/**
	 * Stops the platform. All available bundle activators are stopped. The behavior is slightly
	 * inefficient because we don't keep track of which bundles were started - we go back and look 
	 * at the configured set of bundles and stop them all.
	 */
	public void stop() {
		Document document = readDefaultConfiguration();
		List<Activator> activators = getActivators(document);

		Collections.reverse(activators);
		stop(activators);
	}
	
	/**
	 * Stops the platform. Bundle activators are read from the specified path.
	 * @param configuration
	 */
	public void stop(String configuration) {
		Document document = readConfiguration(new File(configuration));
		List<Activator> activators = getActivators(document);

		Collections.reverse(activators);
		stop(activators);
	}
	
	/**
	 * Starts the runtime using the specified bundle activators. Specific services used for
	 * bootstrapping the runtime are always started.
	 * 
	 * @param activators
	 */
	private void start(Collection<Activator> activators) {
		logger.info("Starting PMM...");
		
		/* Now for the real work */
		for (Activator activator : activators) {
			logger.debug("Class name " + activator.className);
			
			try {
				start(activator);
			} catch (Exception e) {
				/*
				 * Propagate the error, the container must destroy us. Log the error as well, or
				 * else we might not be able to see it in the application's log file
				 */
				logger.warn("Activator for " + activator.bundle + " had problems, aborting...", e);
				throw new RuntimeException("Activator for " + activator.bundle + " had problems, aborting...", e);
			}
		}
		
		logger.info("Started PMM.");
	}
	
	/**
	 * Stops the specified bundle activators
	 * 
	 * @param activators
	 */
	private void stop(Collection<Activator> activators) {
		for (Activator activator : activators) {
			logger.debug("Class name " + activator.className);
			
			try {
				stop(activator);
			} catch (Exception e) {
				logger.warn("Activator for " + activator.bundle + " had problems", e);
				/* And keep going, we really want to shutdown */
			}
		}
		
		logger.info("Stopped PMM.");
	}
	
	private void start(Activator activator) throws Exception {
		Object instance = getClass().getClassLoader().loadClass(activator.className).newInstance();
		Method method = instance.getClass().getMethod("start");

		method.invoke(instance);
	}

	private void stop(Activator activator) throws Exception {
		Object instance = getClass().getClassLoader().loadClass(activator.className).newInstance();
		Method method = instance.getClass().getMethod("stop");

		method.invoke(instance);
	}

	/**
	 * Gets the default list of configured activators
	 * 
	 * @param document
	 * @return
	 */
	private List<Activator> getActivators(Document document) {
		/* FIXME This should lookup the extension registry */
		List<Activator> activators = new ArrayList<Activator>();

		try {
			XPath xpath = XPathFactory.newInstance().newXPath();
			NodeList nodes = (NodeList) xpath.evaluate("//activator", document, XPathConstants.NODESET);

			for (int i = 0; i < nodes.getLength(); i++) {
				Node node = nodes.item(i);

				String className = node.getAttributes().getNamedItem("class").getTextContent();
				String bundle = node.getAttributes().getNamedItem("bundle").getTextContent();
				
				Activator activator = new Activator(bundle, className);
				activators.add(activator);
			}
		} catch (Exception e) {
			throw new RuntimeException("Failed to read activator configuration", e);
		}
		
		return activators;
	}
	
	private Document readConfiguration(File file) {
		Document document = null;
		
		try {
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
		} catch (Exception e) {
			throw new RuntimeException("Failed to read activator configuration: " + file.getAbsolutePath(), e);
		}
		
		return document;
	}
	
	private Document readDefaultConfiguration() {
		Document document = null;
		
		try {
			document = XMLDocumentUtility.loadFile("com/tandbergtv/watchpoint/pmm/boot/activator.xml");
		} catch (Exception e) {
			throw new RuntimeException("Failed to read default activator configuration", e);
		}
		
		return document;
	}
	
	/**
	 * This class represents an activator for a bundle
	 * 
	 * @author Sahil Verma
	 */
	private class Activator {
		
		String bundle;
		
		String className;

		/**
		 * Creates an Activator
		 * @param bundle
		 * @param className
		 */
		public Activator(String bundle, String className) {
			this.bundle = bundle;
			this.className = className;
		}
	}	
}
