/*******************************************************************************
 * Copyright (c) 2007, 2008 IBM Corporation
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/
package org.eclipse.cosmos.me.internal.deployment.sdd.tooling.btg;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.cosmos.me.internal.deployment.sdd.resources.tooling.btg.Messages;
import org.eclipse.cosmos.me.internal.deployment.sdd.tooling.btg.util.PropertiesReader;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.validation.SDD_DAS;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.validation.ValidationFactory;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.validation.exception.XMLValidationException;
import org.eclipse.cosmos.me.provisional.deployment.sdd.tooling.btg.BTGProperty;
import org.eclipse.cosmos.me.provisional.deployment.sdd.tooling.btg.IBTGDataAggregator;
import org.eclipse.cosmos.me.provisional.deployment.sdd.tooling.btg.IBTGDataCollector;
import org.eclipse.cosmos.me.provisional.deployment.sdd.tooling.btg.SDDFragment;
import org.eclipse.cosmos.me.provisional.deployment.sdd.tooling.btg.exception.InvalidCLInputException;
import org.eclipse.cosmos.me.provisional.deployment.sdd.tooling.btg.util.BTGLogUtil;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.w3c.dom.Document;

/**
 * This class controls all aspects of the application's execution
 */
public class BTGApplication implements IApplication {
	private static final String OUTPUT_FILEBASE_PROPERTY = "OutputFileNameBase"; //$NON-NLS-1$
	private static final String PD_NODENAME = "PackageDescriptor";
	private static final String DD_NODENAME = "DeploymentDescriptor";
	
	// Default values
	private String aggregatorChoice = null;
	private String propertiesFile = System.getProperty("user.dir") + File.separator + "BTG.ini"; //$NON-NLS-1$ //$NON-NLS-2$
	
	private boolean printToConsole = true;
	private static Logger logger = Logger.getLogger("org.eclipse.cosmos.me.deployment.sdd.tooling.btg");//$NON-NLS-1$
    private static Logger consoleLogger = Logger.getLogger("org.eclipse.cosmos.me.deployment.sdd.tooling.btg.stdout");//$NON-NLS-1$
    
	private Properties btgProperties = null;
	private Properties aggregatorProperties = null;
	private int aggregatorCount = 0;
	private IBTGDataAggregator aggregator = null;
	
	
	public void stop() {
		// Called to force Application to stop
	}

	public Object start(IApplicationContext arg0) throws Exception {
		String logFile = System.getProperty("user.dir") + File.separator + "logger.properties";
		BTGLogUtil.initialize(logFile);
	    
	    consoleLogger.info(Messages.getString("BTGApplication.5")); //$NON-NLS-1$
	    
	    // Get parameters
	    String[] params = new String[0];
	    if (arg0.getArguments().containsKey(IApplicationContext.APPLICATION_ARGS)) {
	    	params = (String[])arg0.getArguments().get(IApplicationContext.APPLICATION_ARGS); 
	    }
	    Iterator<String> paramsIter = Arrays.asList(params).iterator();
	    
	    // Parse parameters
	    while (paramsIter.hasNext()) {
	    	String param = paramsIter.next();
	    	
        	if (param.equalsIgnoreCase("-help") || param.equals("-?") || param.equalsIgnoreCase("-h")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        		showHelp();
        		return IApplication.EXIT_OK;
        	}
        	
        	else if (param.equalsIgnoreCase("-usage")) { //$NON-NLS-1$
        		showUsage();
        		return IApplication.EXIT_OK;
        	}
        	
        	else if (param.equalsIgnoreCase("-propertiesFile")) { //$NON-NLS-1$
        		String nextParam = paramsIter.next();

        		if (!nextParam.startsWith("-")) { //$NON-NLS-1$
        			propertiesFile = nextParam;
        		}
        		else {
        			Exception e = new InvalidCLInputException(Messages.getString("BTGApplication.14"));
        			consoleLogger.severe("InvalidCLInputException");
                    consoleLogger.log(Level.INFO, Messages.getString("BTGApplication.30"), BTGLogUtil.getMsgLogName());
                    logger.log(Level.SEVERE, "ExceptionStack", e);
        			throw e; //$NON-NLS-1$
        		}
        	}
        	
        	else if (param.equalsIgnoreCase("-printToConsole")) { //$NON-NLS-1$
        		printToConsole = true;
        	}
        	
        	else {
        		Exception e = new InvalidCLInputException(Messages.getString("BTGApplication.16") + param);
    			consoleLogger.severe("InvalidCLInputException");
                consoleLogger.log(Level.INFO, Messages.getString("BTGApplication.30"), BTGLogUtil.getMsgLogName());
                logger.log(Level.SEVERE, "ExceptionStack", e);
    			throw e;
        	}
	    }

	    // Get Data Collector and Data Aggregator plug-ins
		Collection<IBTGDataCollector> dataCollectorInstances = BTGPlugin.getDataCollectorInstances();
		Collection<IBTGDataAggregator> dataAggregatorInstances = BTGPlugin.getDataAggregatorInstances();
		
		// Handle case where no Collectors were loaded
		if (dataCollectorInstances == null || dataCollectorInstances.size() == 0) {
			// No Collectors defined, issue a message and exit
			consoleLogger.config(Messages.getString("BTGApplication.48")); //$NON-NLS-1$
			
			return IApplication.EXIT_OK;
		}
		else {
			// Issue message about how many Collectors were loaded
			consoleLogger.config(Messages.getString("BTGApplication.46",
					Integer.toString(dataCollectorInstances.size()))); //$NON-NLS-1$
		}
		
		// Handle case where no Aggregators were loaded
		if (dataAggregatorInstances == null || dataAggregatorInstances.size() == 0) {
			// No Aggregators defined, issue a message and exit
			consoleLogger.config(Messages.getString("BTGApplication.49")); //$NON-NLS-1$
			
			return IApplication.EXIT_OK;			
		}
		else {
			// Issue message about how many Aggregators were loaded
			consoleLogger.config(Messages.getString("BTGApplication.47",
					Integer.toString(dataAggregatorInstances.size()))); //$NON-NLS-1$
		}
		
		// Read in the list of properties from properties file
		PropertiesReader propsReader = new PropertiesReader(propertiesFile);
		Collection<Properties> props = propsReader.getPropertiesList();
		
		consoleLogger.config(Messages.getString("BTGApplication.17", propertiesFile)); //$NON-NLS-1$

		// Validate Collector and Aggregator properties
		validatePluginProps(props, dataCollectorInstances, dataAggregatorInstances, propsReader);
		
		// Make sure at least one of the loaded aggregators is enabled
		if (aggregatorCount == 0) {
			consoleLogger.config(Messages.getString("BTGApplication.50"));
			
			return IApplication.EXIT_OK;
		}
		
		// Make sure no more than one of the loaded aggregators is enabled
		if (aggregatorCount > 1) {
			consoleLogger.config(Messages.getString("BTGApplication.51"));
			
			return IApplication.EXIT_OK;
		}

		// SDDFragments found by collector plug-ins
		Collection<SDDFragment> sddFragments = findSDDFragments(props, dataCollectorInstances);
				
		// Descriptors returned from the Aggregator
		Collection<Document> descriptorsList = findDescriptorsList(dataAggregatorInstances, sddFragments);
		
		// Write the aggregated SDDs to output files
		if (descriptorsList == null || descriptorsList.isEmpty()) {
			consoleLogger.config(Messages.getString("BTGApplication.28")); //$NON-NLS-1$
		}
		else if (descriptorsList.size() != 2) {
			consoleLogger.config(Messages.getString("BTGApplication.10", String.valueOf(descriptorsList.size()))); //$NON-NLS-1$
		}
		else {
			consoleLogger.info(Messages.getString("BTGApplication.27")); //$NON-NLS-1$
			writeDescriptorsToFiles(descriptorsList, btgProperties.getProperty(OUTPUT_FILEBASE_PROPERTY));			
		}
		
		consoleLogger.config(Messages.getString("BTGApplication.29")); //$NON-NLS-1$
		
		return IApplication.EXIT_OK;
	}
	
	// Validate BTG, Collector and Aggregator properties
	private void validatePluginProps(Collection<Properties> props, Collection<IBTGDataCollector> dataCollectorInstances,
			Collection<IBTGDataAggregator> dataAggregatorInstances, PropertiesReader propsReader) throws ParseException {

		for (Properties p : props) {
			if (PropertiesReader.BTG_PLUGINID.equals(p.get(PropertiesReader.PLUGINID_PROPERTY))) {
				// Validate the BTG properties
				propsReader.checkSupportedProperties(p, getSupportedProperties());
				btgProperties = p;
			}
			else {
				// Find a Data Collector plug-in with a PluginID to match these properties
				for (IBTGDataCollector dataCollectorInstance : dataCollectorInstances) {
					String pluginID = BTGPlugin.getDataCollectorPluginID(dataCollectorInstance);
				
					// Validate the properties for this plug-in
					if (p.get(PropertiesReader.PLUGINID_PROPERTY).equals(pluginID)) {
						propsReader.checkSupportedProperties(p, dataCollectorInstance.getSupportedProperties());
					}
				}
				
				// Find a Data Aggregator plug-in with a PluginID to match these properties
				for (IBTGDataAggregator dataAggregatorInstance : dataAggregatorInstances) {
					String pluginID = BTGPlugin.getDataAggregatorPluginID(dataAggregatorInstance);
				
					// Validate the properties for this plug-in
					if (p.get(PropertiesReader.PLUGINID_PROPERTY).equals(pluginID)) {
						propsReader.checkSupportedProperties(p, dataAggregatorInstance.getSupportedProperties());
						aggregatorCount++;
						
						// Since this aggregator is enabled, store it as the aggregator choice
						aggregatorChoice = pluginID;
						// Store the properties for this aggregator
						aggregatorProperties = p;
					}
				}				
			}
		}
	}
	
	// SDDFragments found by collector plug-ins 
	private Collection<SDDFragment> findSDDFragments(Collection<Properties> props,
			Collection<IBTGDataCollector> dataCollectorInstances) throws Exception {
		Collection<SDDFragment> sddFragments = new ArrayList<SDDFragment>();
		
		// Loop through properties for all enabled plug-ins
		for (Properties p : props) {
			// Find the plug-in with pluginID that matches these properties
			for (IBTGDataCollector dataCollectorInstance : dataCollectorInstances) {
				String pluginID = BTGPlugin.getDataCollectorPluginID(dataCollectorInstance);
					
				// Pass the properties to the plug-in with the matching ID
				if (p.get(PropertiesReader.PLUGINID_PROPERTY).equals(pluginID)) {
					// Add the fragments returned by this plug-in
					Collection<SDDFragment> descriptorList = dataCollectorInstance.collectSDDData(p);
					if (descriptorList != null) {
						sddFragments.addAll(descriptorList);
					}
				}
			}
		}
		
		return sddFragments;
	}
	
	// Documents returned from the Aggregator
	private Collection<Document> findDescriptorsList(Collection<IBTGDataAggregator> dataAggregatorInstances,
			Collection<SDDFragment> sddFragments) {
		Collection<Document> descriptorsList = null;
		
		// Find an aggregator with a PluginID that matches the aggregatorChoice
		for (IBTGDataAggregator dataAggregatorInstance : dataAggregatorInstances) {
			String pluginID = BTGPlugin.getDataAggregatorPluginID(dataAggregatorInstance);
			
			if (aggregatorChoice.equals(pluginID)) {
				consoleLogger.config(Messages.getString("BTGApplication.24") + pluginID); //$NON-NLS-1$
				aggregator = dataAggregatorInstance;
			}
		}
		// Use the chosen aggregator to combine the SDD Fragments from the collector plug-ins
		if (aggregator != null) {
			descriptorsList = aggregator.aggregateSDDData(sddFragments, aggregatorProperties, false);
		}
		else {
			consoleLogger.config(Messages.getString("BTGApplication.25")); //$NON-NLS-1$
		}
		
		return descriptorsList;
	}
	
	public Collection<BTGProperty> getSupportedProperties() {
		Collection<BTGProperty> properties = new ArrayList<BTGProperty>();
		
		// OutputFileNameBase
		properties.add(new BTGProperty(OUTPUT_FILEBASE_PROPERTY, true));
		
		return properties;
	}
	
	/*
	 * This method takes a Collection of Documents which together make up a single SDD.
	 * An SDD is defined as one Package Descriptor and one Deployment Descriptor
	 */
	public void writeDescriptorsToFiles(Collection<Document> descriptors, String fileNameBase)
			throws XMLValidationException, IOException {
		SDD_DAS sddDas = ValidationFactory.DEFAULT_INSTANCE.createSDD_DAS();
		
		// If more than two descriptors were passed in, issue warning message
		if (descriptors != null && descriptors.size() > 2){
			consoleLogger.log(Level.INFO, Messages.getString("BTGApplication.18"));
		}
		
		if (descriptors != null) {
			boolean foundPd = false;
			
			// Look for the Package Descriptor
			for (Document descriptor : descriptors) {
				// Make sure descriptor is not null
				if (descriptor != null) {
					String nodeName = descriptor.getDocumentElement().getNodeName();
					
					// Removed the "sdd-pd" if present
					if (nodeName.indexOf(':') != -1) {
						nodeName = nodeName.split(":")[1];
					}
					
					if (PD_NODENAME.equals(nodeName)) {
						foundPd = true;

						// Write the descriptor to a file using the passed in file name base
						OutputStream pdOutStream = new FileOutputStream(fileNameBase + ".pd.xml");
						sddDas.saveDocument(descriptor, pdOutStream, null, false);
						pdOutStream.close();

						// Also write the descriptor to the console if enabled
						if (printToConsole) {
							consoleLogger.info("\n\n**************************************"); //$NON-NLS-1$
							consoleLogger.info(Messages.getString("BTGApplication.45")); //$NON-NLS-1$
							consoleLogger.info("**************************************\n"); //$NON-NLS-1$
							sddDas.saveDocument(descriptor, System.out, null, false);
						}
					}
				}
			}

			if (!foundPd) {
				// Issue a message if there wasn't a Package Descriptor
				consoleLogger.log(Level.INFO, Messages.getString("BTGApplication.11"));
			}

			boolean foundDd = false;
			// Look for the Deployment Descriptor
			for (Document descriptor : descriptors) {
				// Make sure descriptor is not null
				if (descriptor != null) {
					String nodeName = descriptor.getDocumentElement().getNodeName();
					
					// Remove the "sdd-dd" if present
					if (nodeName.indexOf(':') != -1) {
						nodeName = nodeName.split(":")[1];
					}
					
					if(DD_NODENAME.equals(nodeName)) {
						foundDd = true;

						// Write the descriptor to a file using the passed in file name base
						OutputStream ddOutStream = new FileOutputStream(fileNameBase + ".dd.xml");
						sddDas.saveDocument(descriptor, ddOutStream, null, false);
						ddOutStream.close();

						// Also write the descriptor to the console if enabled
						if (printToConsole) {
							consoleLogger.info("\n**************************************"); //$NON-NLS-1$
							consoleLogger.info(Messages.getString("BTGApplication.44")); //$NON-NLS-1$
							consoleLogger.info("**************************************\n"); //$NON-NLS-1$
							sddDas.saveDocument(descriptor, System.out, null, false);
						}
					}
				}
			}

			if (!foundDd) {
				// Issue a message if there wasn't a Deployment Descriptor
				consoleLogger.log(Level.INFO, Messages.getString("BTGApplication.13"));
			}
		} 
		else {
			// Issue a message if no Package Descriptor or Deployment Descriptor passed in
			consoleLogger.log(Level.INFO, Messages.getString("BTGApplication.11"));
			consoleLogger.log(Level.INFO, Messages.getString("BTGApplication.13"));
		}
	}
		
	private void showHelp() {
		consoleLogger.info(Messages.getString("BTGApplication.38")); //$NON-NLS-1$
		consoleLogger.info(Messages.getString("BTGApplication.39")); //$NON-NLS-1$
		consoleLogger.info(Messages.getString("BTGApplication.40")); //$NON-NLS-1$
	}
	
	private void showUsage() {
		consoleLogger.info(Messages.getString("BTGApplication.41")); //$NON-NLS-1$
		consoleLogger.info(Messages.getString("BTGApplication.42")); //$NON-NLS-1$
	}
}