/**
 * File:    ExtractPropertiesService.java
 * Created: 26.02.2009
 *
 *
/*******************************************************************************
 * Copyright (c) 2009-2010  Open Wide (www.openwide.fr)
 *
 * 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:
 *    Mickael Istria, Open Wide, Lyon, France
 *      - creation and implementation
 *    Guillaume Decarnin, Open Wide, Lyon, France
 *      - Refactoring to process Action inputs and outputs
 *    Marc Dutoo, Open Wide, Lyon, France
 *      - #327474 - also exports aspects of elements not in an activity
 *******************************************************************************/
package org.eclipse.jwt.transformations.properties;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.jwt.transformations.api.TransformationException;
import org.eclipse.jwt.transformations.api.TransformationService;
import org.eclipse.jwt.we.conf.model.AspectInstance;
import org.eclipse.jwt.we.conf.model.aspects.AspectManager;
import org.eclipse.jwt.we.conf.property.model.property.Property;
import org.eclipse.jwt.meta.model.application.Application;
import org.eclipse.jwt.meta.model.core.Model;
import org.eclipse.jwt.meta.model.core.Package;
import org.eclipse.jwt.meta.model.core.PackageableElement;
import org.eclipse.jwt.meta.model.data.Data;
import org.eclipse.jwt.meta.model.data.DataMapping;
import org.eclipse.jwt.meta.model.processes.Action;
import org.eclipse.jwt.meta.model.processes.Activity;

/**
 * This class implements a {@link TransformationService} and has tools
 * to extract from a {@link Model} file a .properties file that contains
 * the key/value map for nodes properties.
 * 
 * Keys are [{@link Activity}Name]/[{@link Action}Name]/[propertyName]
 * 
 * This transformation can be customized/extended by extension point
 * 'org.eclipse.jwt.transformations.properties.customProperties'
 * @see PropertyProcessorsExtensionPoint
 * 
 * @author mistria
 * @since 0.7
 * @noextend This class is not intended to be subclassed by clients.
 */
public class ExtractPropertiesService extends TransformationService {

	private Map<String, String> resources;
	
	/**
	 * Creates the properties file from {@link Model} file
	 */
	@Override
	public void transform(String inFilePath, String propertiesFile) throws IOException, TransformationException {
		Model jwtModel = getJWTModel(inFilePath);

		try {
			resources = processProperties(jwtModel,	propertiesFile);
		} catch (IOException ex) {
			throw new TransformationException("Cannot write properties file aside of XPDL file"); //$NON-NLS-1$
		}
	}

	/**
	 * During transformation, it is possible that extensions assign some
	 * resources files to be attached.
	 * In such case, it is possible to get these files just after
	 * {@link ExtractPropertiesService}{@link #transform(String, String)} is called
	 * @return the FilePath/symbolicName set of attached resources
	 */
	public Map<String, String> getAttachedResources() {
		return this.resources;
	}
	
	
	/**
	 * Loads the given JWT model
	 * @param inFilePath
	 * @return the corresponding JWT EMF model
	 */
	private Model getJWTModel(String inFilePath) {
		// Create a resource set.
		ResourceSet resourceSet = new ResourceSetImpl();

		// Register the default resource factory -- only needed for
		// stand-alone!
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());

		// Get the URI of the model file.
		URI fileURI = URI.createFileURI(new File(inFilePath).getAbsolutePath());

		// Demand load the resource for this file.
		Resource resource = resourceSet.getResource(fileURI, true);
		Model jwtModel = (Model) resource.getContents().get(0);
		return jwtModel;
	}

	/**
	 * Exports all fields of aspects of elements of the given model
	 * as properties in the given property file. Returns a map of
	 * additional resources (files) according to the other processors.
	 * @param jwtModel
	 * @param outFileName
	 * @return a filesystemFilePath/zipArchiveFileName map of resources.
	 * May be used to embed additional files with the transformed
	 * model in a zip archive for instance.
	 * @throws IOException
	 */
	public Map<String, String> processProperties(Model jwtModel, String outFileName)
			throws IOException {
		PrintStream out = new PrintStream(outFileName);
		resources = new HashMap<String, String>();
		processProperties(jwtModel, out);
		return resources;
	}

	private void processProperties(Package jwtPackage, PrintStream out) {
		out.println("## Begin package " + jwtPackage.getName()); //$NON-NLS-1$
		for (Object subpackage : jwtPackage.getSubpackages()) {
			processProperties((Package) subpackage, out);
		}
		for (Object element : jwtPackage.getElements()) {
			if (element instanceof Activity) {
				Activity activity = (Activity) element;
				processActivity(activity, out);
			} else {
				processProperties((PackageableElement) element, out);
			}
		}
		out.println("## End package " + jwtPackage.getName()); //$NON-NLS-1$
	}

	private void processActivity(Activity activity, PrintStream out) {
		out.println("## Begin activity " + activity.getName()); //$NON-NLS-1$
		for (Object node : activity.getNodes()) {
			if (node instanceof Action) {
				Action action = (Action) node;
				processActionIO(action, activity, out);
				
				Application app = action.getExecutedBy();
				processApplication(app, action, activity, out);
			}
			// else another kind of ActivityNode : mainly references
		}
		out.println("## End activity " + activity.getName()); //$NON-NLS-1$
	}

	private void processApplication(Application app, Action action,	Activity activity, PrintStream out) {
		if (app == null) {
			return;
		}
		// Data mapping
		out.println("# Data mapping of action " + action.getName() + ":"); //$NON-NLS-1$ //$NON-NLS-2$
		int i = 0;
		for (Object inObj : app.getInput()) {
			for (Object mapObj : action.getMappings()) {
				DataMapping mapping = (DataMapping) mapObj;
				if (inObj.equals(mapping.getBoundParameter())) {
					out.println(buildPropertySetterString(activity, action, "argVariable" + i, mapping.getParameter().getName())); //$NON-NLS-1$
					i++;
				}
			}
		}
		int j = 0;
		for (Object outObj : app.getOutput()) {
			for (Object mapObj : action.getMappings()) {
				DataMapping mapping = (DataMapping) mapObj;
				if (outObj.equals(mapping.getBoundParameter())) {
					out.println(buildPropertySetterString(activity, action, "resVariable" + ((j != 0) ? j : ""), mapping.getParameter().getName())); //$NON-NLS-1$  //$NON-NLS-2$
					j++;
				}
			}
		}
		
		// Generic features of application
		out.println("# Other properties of application of action " + action.getName() + ":"); //$NON-NLS-1$ //$NON-NLS-2$
		for (EObject objFeature : app.eClass().getEAllAttributes()) {
			if (objFeature instanceof EStructuralFeature) {
				EStructuralFeature feature = (EStructuralFeature)objFeature;
				PropertyProcessor propProcessor = getPropertyProcessor(app, feature);
				for (Entry<String, Object> propEntry : propProcessor.getEntries().entrySet()) {
					out.println(buildPropertySetterString(activity, action, propEntry.getKey(), propEntry.getValue()));
				}
				Map<String, String> propResources = propProcessor.getResources();
				if (propResources != null)
					this.resources.putAll(propResources);
			}
		}
		
		// Aspects of application
		out.println("# Aspect properties of application of action " + action.getName() + ":"); //$NON-NLS-1$ //$NON-NLS-2$
		for (AspectInstance aspect : AspectManager.INSTANCE.getAspectInstances(app)) {
			if (aspect instanceof Property) {
				Property property = (Property)aspect;
				out.println(buildPropertySetterString(activity, action, property.getId(), property.getValue()));
			} else {
				for (EObject objFeature : aspect.eClass().getEAllAttributes()) {
					if (objFeature instanceof EStructuralFeature) {
						EStructuralFeature feature = (EStructuralFeature)objFeature;
						PropertyProcessor propProcessor = getPropertyProcessor(aspect, feature);
						for (Entry<String, Object> propEntry : propProcessor.getEntries().entrySet()) {
							out.println(buildPropertySetterString(activity, action, propEntry.getKey(), propEntry.getValue()));
						}
						Map<String, String> propResources = propProcessor.getResources();
						if (propResources != null)
							this.resources.putAll(propResources);
					}
				}
			}
		}
	}

	/**
	 * Extract input outputs from actions and add them to properties
	 * @param action
	 * @param activity
	 * @param out
	 */
	private void processActionIO(Action action, Activity activity, PrintStream out) {
		// Data mapping
		out.println("# IO Properties of action " + action.getName() + ":"); //$NON-NLS-1$ //$NON-NLS-2$
		int i = 0;
		for (Object inObj : action.getInputs()) {
			Data data = (Data)inObj;
			out.println(buildPropertySetterString(activity, action, "input" + i, data.getName())); //$NON-NLS-1$
			i++;
		}
		int j = 0;
		for (Object outObj : action.getOutputs()) {
			Data data = (Data)outObj;
			out.println(buildPropertySetterString(activity, action, "output" + j, data.getName())); //$NON-NLS-1$
			j++;
		}
		
		// Generic features
		out.println("# Other properties of action " + action.getName() + ":"); //$NON-NLS-1$ //$NON-NLS-2$
		for (EObject objFeature : action.eClass().getEAllAttributes()) {
			if (objFeature instanceof EStructuralFeature) {
				EStructuralFeature feature = (EStructuralFeature)objFeature;
				PropertyProcessor propProcessor = getPropertyProcessor(action, feature);
				for (Entry<String, Object> propEntry : propProcessor.getEntries().entrySet()) {
					out.println(buildPropertySetterString(activity, action, propEntry.getKey(), propEntry.getValue()));
				}
				Map<String, String> propResources = propProcessor.getResources();
				if (propResources != null)
					this.resources.putAll(propResources);
			}
		}
		
		// Aspects
		out.println("# Aspect properties of action " + action.getName() + ":"); //$NON-NLS-1$ //$NON-NLS-2$
		for (AspectInstance aspect : AspectManager.INSTANCE.getAspectInstances(action)) {
			if (aspect instanceof Property) {
				Property property = (Property)aspect;
				out.println(buildPropertySetterString(activity, action, property.getId(), property.getValue()));
			} else {
				for (EObject objFeature : aspect.eClass().getEAllAttributes()) {
					if (objFeature instanceof EStructuralFeature) {
						EStructuralFeature feature = (EStructuralFeature)objFeature;
						PropertyProcessor propProcessor = getPropertyProcessor(aspect, feature);
						for (Entry<String, Object> propEntry : propProcessor.getEntries().entrySet()) {
							out.println(buildPropertySetterString(activity, action, propEntry.getKey(), propEntry.getValue()));
						}
						Map<String, String> propResources = propProcessor.getResources();
						if (propResources != null)
							this.resources.putAll(propResources);
					}
				}
			}
		}
		
	}

	/**
	 * Exports (aspect) properties of a mere packageable element, not in an activity
	 * @param element
	 * @param out
	 */
	private void processProperties(PackageableElement element, PrintStream out) {
		out.println("# Properties of package element " + element.getName() + ":"); //$NON-NLS-1$ //$NON-NLS-2$
		// Generic features
		for (EObject objFeature : element.eClass().getEAllAttributes()) {
			if (objFeature instanceof EStructuralFeature) {
				EStructuralFeature feature = (EStructuralFeature)objFeature;
				PropertyProcessor propProcessor = getPropertyProcessor(element, feature);
				for (Entry<String, Object> propEntry : propProcessor.getEntries().entrySet()) {
					out.println(buildPropertySetterString(element, propEntry.getKey(), propEntry.getValue()));
				}
				Map<String, String> propResources = propProcessor.getResources();
				if (propResources != null)
					this.resources.putAll(propResources);
			}
		}
		// Aspects
		for (AspectInstance aspect : AspectManager.INSTANCE.getAspectInstances(element)) {
			if (aspect instanceof Property) {
				Property property = (Property)aspect;
				out.println(buildPropertySetterString(element, property.getId(), property.getValue()));
			} else {
				for (EObject objFeature : aspect.eClass().getEAllAttributes()) {
					if (objFeature instanceof EStructuralFeature) {
						EStructuralFeature feature = (EStructuralFeature)objFeature;
						PropertyProcessor propProcessor = getPropertyProcessor(aspect, feature);
						for (Entry<String, Object> propEntry : propProcessor.getEntries().entrySet()) {
							out.println(buildPropertySetterString(element, propEntry.getKey(), propEntry.getValue()));
						}
						Map<String, String> propResources = propProcessor.getResources();
						if (propResources != null)
							this.resources.putAll(propResources);
					}
				}
			}
		}
	}

	private PropertyProcessor getPropertyProcessor(EObject app, EStructuralFeature feature) {
		return new ChainedPropertyProcessor(app, feature, PropertyProcessorsExtensionPoint.getPropertyProcessors(app, feature));
	}

	/**
	 * Writes a property line for an action (or its application or IO) within an activity
	 * @param activity
	 * @param action
	 * @param propertyName
	 * @param value
	 * @return the property line
	 */
	private String buildPropertySetterString(Activity activity, Action action, String propertyName, Object value) {
		if (value == null || value.toString().length() == 0)
			return ""; //$NON-NLS-1$
		String stringValue = value.toString().replace("\n", "\\n\\\n"); // finish lines with \n\ //$NON-NLS-1$ //$NON-NLS-2$
		return activity.getName() + "/" + action.getName() //$NON-NLS-1$
			+ "/" + propertyName + "=" + stringValue; //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Writes a property line for a mere packageable element, not in an activity
	 * @param element
	 * @param propertyName
	 * @param value
	 * @return the property line
	 */
	private String buildPropertySetterString(PackageableElement element, String propertyName, Object value) {
		if (value == null || value.toString().length() == 0)
			return ""; //$NON-NLS-1$
		return element.getName() + "/" + propertyName + "=" + value; //$NON-NLS-1$ //$NON-NLS-2$
	}

}
