content: /**
   * ﻿Copyright (C) 2012 - 2019 52°North Initiative for Geospatial Open Source
   * Software GmbH
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 as published
   * by the Free Software Foundation.
   *
   * If the program is linked with libraries which are licensed under one of
   * the following licenses, the combination of the program with the linked
   * library is not considered a "derivative work" of the program:
   *
   *       • Apache License, version 2.0
   *       • Apache Software License, version 1.0
   *       • GNU Lesser General Public License, version 3
   *       • Mozilla Public License, versions 1.0, 1.1 and 2.0
   *       • Common Development and Distribution License (CDDL), version 1.0
   *
   * Therefore the distribution of the program linked with libraries licensed
   * under the aforementioned licenses, is permitted by the copyright holders
   * if the distribution is compliant with both the GNU General Public
   * License version 2 and the aforementioned licenses.
   *
   * This program is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
   * Public License for more details.
   */
  package org.n52.wps.mc;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import net.opengis.wps.x100.InputDescriptionType;
  import net.opengis.wps.x100.OutputDescriptionType;
  import net.opengis.wps.x100.ProcessDescriptionType;
  
  import org.n52.movingcode.runtime.GlobalRepositoryManager;
  import org.n52.movingcode.runtime.codepackage.MovingCodePackage;
  import org.n52.movingcode.runtime.iodata.IIOParameter;
  import org.n52.movingcode.runtime.iodata.IODataType;
  import org.n52.movingcode.runtime.iodata.MediaData;
  import org.n52.movingcode.runtime.processors.AbstractProcessor;
  import org.n52.movingcode.runtime.processors.ProcessorFactory;
  import org.n52.wps.io.data.GenericFileData;
  import org.n52.wps.io.data.IData;
  import org.n52.wps.io.data.binding.complex.GenericFileDataBinding;
  import org.n52.wps.io.data.binding.literal.LiteralBooleanBinding;
  import org.n52.wps.io.data.binding.literal.LiteralDoubleBinding;
  import org.n52.wps.io.data.binding.literal.LiteralFloatBinding;
  import org.n52.wps.io.data.binding.literal.LiteralIntBinding;
  import org.n52.wps.io.data.binding.literal.LiteralStringBinding;
  import org.n52.wps.server.IAlgorithm;
  
  public class MCProcessDelegator implements IAlgorithm {
  
  	private final String identifier;
  	private List<String> errors;
  	private ProcessDescriptionType description = null;
  
  	public MCProcessDelegator(final String identifier) {
  		this.identifier = identifier;
  		this.errors = new ArrayList<String>();
  	}
  
  	@Override
  	public Map<String, IData> run(Map<String, List<IData>> inputData) {
  		MovingCodePackage[] allMCs = GlobalRepositoryManager.getInstance().getPackageByFunction(identifier);
  
  		AbstractProcessor processor = null;
  		//  try to isolate a package that is supported by a registered processor
  		for (MovingCodePackage currentMC : allMCs){
  			if (ProcessorFactory.getInstance().supportsPackage(currentMC)){
  				processor = ProcessorFactory.getInstance().newProcessor(currentMC);
  				break;
  			}
  		}
  		
  		// TODO: exit here if processor == null;
  
  
  		// assign inputs
  		for (String inputID : inputData.keySet()) {
  			List<IData> iDataList = inputData.get(inputID);
  			if (iDataList == null || iDataList.isEmpty() || iDataList.size() == 0) {
  				// do nothing
  			}
  			else {
  				// here we can be sure that iDataList contains at least one element
  				for (IData iData : iDataList) {
  
  					switch (probeMCDataType(iData)) {
  						// simple cases
  						case BOOLEAN:
  							processor.addData(inputID, iData.getPayload());
  							break;
  						case DOUBLE:
  							processor.addData(inputID, iData.getPayload());
  							break;
  						case INTEGER:
  							processor.addData(inputID, iData.getPayload());
  							break;
  						case STRING:
  							processor.addData(inputID, iData.getPayload());
  							break;
  
  							// media data
  						case MEDIA:
  							GenericFileData gfd = (GenericFileData) iData.getPayload();
  							processor.addData(inputID, new MediaData(gfd.getDataStream(), gfd.getMimeType()));
  							break;
  						default:
  							errors.add("The supplied data is not compatible with the MC library.\n"
  									+ "Allowed types are BOOLEAN, DOUBLE, INTEGER, STRING, MEDIA.\n"
  									+ "Offending InputID: " + inputID);
  							throw new IllegalArgumentException("Invalid data passed to process. Offending InputID: "
  									+ inputID);
  					}
  				}
  
  			}
  
  		}
  
  		// How do we declare MimeTypes for Outputs?
  		// Do we have enough information to do this?
  		//
  		// We could use the ExecuteRequest to do this but the WPS might
  		// do additional transformation to support more inputs types
  		// (e.g. gml for shapefiles)
  		//
  		// so we shall stick to the defaults until there's a better way
  		// to do output probing
  		// MC packages for WPS should therefore have only *one* supported
  		// type per input or output
  
  		OutputDescriptionType[] wpsOutputs = this.getDescription().getProcessOutputs().getOutputArray();
  
  		for (IIOParameter param : processor.values()) {
  			// for all output-only parameters:
  			if (param.getDirection() == IIOParameter.Direction.OUT) {
  				// fetch the "default" mimeType
  				for (OutputDescriptionType wpsOut : wpsOutputs) {
  					String outputID = param.getMessageOutputIdentifier();
  					if (wpsOut.getIdentifier().getStringValue().equalsIgnoreCase(outputID)) {
  						switch (param.getType()) {
  							// simple cases
  							case BOOLEAN:
  								processor.addData(outputID, null);
  								break;
  							case DOUBLE:
  								processor.addData(outputID, null);
  								break;
  							case INTEGER:
  								processor.addData(outputID, null);
  								break;
  							case STRING:
  								processor.addData(outputID, null);
  								break;
  
  								// media data
  							case MEDIA:
  								String mimeType = wpsOut.getComplexOutput().getDefault().getFormat().getMimeType();
  								processor.addData(outputID, new MediaData(null, mimeType));
  								break;
  							default:
  								errors.add("Soemthing went wrong assigning the output data types.\n"
  										+ "Probably a messed-up process description.\n" + "Offending OutputID: " + outputID);
  								throw new IllegalArgumentException("Invalid data passed to process. Offending InputID: "
  										+ outputID);
  						}
  					}
  				}
  			}
  		}
  
  		// check feasibility
  		if ( !processor.isFeasible()) {
  			errors.add("Feasibility == FALSE for process: " + identifier);
  			throw new IllegalArgumentException("For some reason the parameterisation was wrong for process: "
  					+ identifier);
  		}
  
  		try {
  			processor.execute(0); // execute without any timeout
  		}
  		catch (IllegalArgumentException e) {
  			errors.add(e.getMessage());
  			throw new RuntimeException("Execution terminated with an error.");
  		}
  		catch (RuntimeException e) {
  			errors.add(e.getMessage());
  			throw new RuntimeException("Execution terminated with an error.");
  		}
  		catch (IOException e) {
  			errors.add(e.getMessage());
  			throw new RuntimeException("Execution terminated with an error.");
  		}
  
  		// create the output
  		HashMap<String, IData> result = new HashMap<String, IData>();
  
  		// iterate through all values
  		for (IIOParameter param : processor.values()) {
  			// select the outputs only
  			if (param.isMessageOut()) {
  				// we will only add the first element due to wps output multiplicity restrictions
  				// actually, there should be exactly one value in the list, but who knows ...
  				// check: is there more than one value?
  				if (param.size() <= 0) {
  					errors.add("Could not retrieve process output for identifier: "
  							+ param.getMessageOutputIdentifier() + ". Value list was found empty.");
  				}
  				switch (param.getType()) {
  					case BOOLEAN:
  						result.put(param.getMessageOutputIdentifier(), new LiteralBooleanBinding((Boolean) param.get(0)));
  						break;
  					case DOUBLE:
  						result.put(param.getMessageOutputIdentifier(), new LiteralDoubleBinding((Double) param.get(0)));
  						break;
  					case INTEGER:
  						result.put(param.getMessageOutputIdentifier(), new LiteralIntBinding((Integer) param.get(0)));
  						break;
  					case STRING:
  						result.put(param.getMessageOutputIdentifier(), new LiteralStringBinding((String) param.get(0)));
  						break;
  					case MEDIA:
  
  						MediaData md = (MediaData) param.get(0);
  						GenericFileData gfd = new GenericFileData(md.getMediaStream(), md.getMimeType());
  						result.put(param.getMessageOutputIdentifier(), new GenericFileDataBinding(gfd));
  						break;
  				}
  			}
  		}
  		if (errors.size() > 0) {
  			throw new RuntimeException("Abnormal termination of execution");
  		}
  
  		// TODO: implement return values
  		return result;
  	}
  
  	@Override
  	public List<String> getErrors() {
  		// TODO Auto-generated method stub
  		return null;
  	}
  
  	@Override
  	public ProcessDescriptionType getDescription() {
  		if (description == null) {
  			ProcessDescriptionType originalDescription = GlobalRepositoryManager.getInstance().getProcessDescription(identifier);
  			MCProcessRepository.filterProcessDescription(originalDescription);
  			description = originalDescription;
  		}
  		
  		return description;
  	}
  
  	@Override
  	public String getWellKnownName() {
  		return getDescription().getIdentifier().getStringValue();
  	}
  
  	@Override
  	public boolean processDescriptionIsValid() {
  		return getDescription().validate();
  	}
  
  	@Override
  	public Class< ? > getInputDataType(String id) {
  		InputDescriptionType[] inputs = this.getDescription().getDataInputs().getInputArray();
  
  		for (InputDescriptionType input : inputs) {
  
  			// Literal Input
  			if (input.isSetLiteralData()) {
  				String datatype = input.getLiteralData().getDataType().getStringValue();
  				if (datatype.equalsIgnoreCase("string")) {
  					return LiteralStringBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("boolean")) {
  					return LiteralBooleanBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("float")) {
  					return LiteralFloatBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("double")) {
  					return LiteralDoubleBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("int")) {
  					return LiteralIntBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("integer")) {
  					return LiteralIntBinding.class;
  				}
  			}
  
  			// Complex Output
  			if (input.isSetComplexData()) {
  				return GenericFileDataBinding.class;
  			}
  		}
  
  		return null;
  	}
  
  	@Override
  	public Class< ? > getOutputDataType(String id) {
  		OutputDescriptionType[] outputs = this.getDescription().getProcessOutputs().getOutputArray();
  
  		for (OutputDescriptionType output : outputs) {
  
  			// Literal Output
  			if (output.isSetLiteralOutput()) {
  				String datatype = output.getLiteralOutput().getDataType().getStringValue();
  				if (datatype.equalsIgnoreCase("string")) {
  					return LiteralStringBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("boolean")) {
  					return LiteralBooleanBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("float")) {
  					return LiteralFloatBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("double")) {
  					return LiteralDoubleBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("int")) {
  					return LiteralIntBinding.class;
  				}
  				if (datatype.equalsIgnoreCase("integer")) {
  					return LiteralIntBinding.class;
  				}
  			}
  
  			// Complex Output
  			if (output.isSetComplexOutput()) {
  				return GenericFileDataBinding.class;
  			}
  		}
  		return null;
  	}
  
  	/*
  	 * This class performs a lookup to mediate between the data types used in the 52n WPS framework and the
  	 * types defined in the Moving Code lib.
  	 */
  	private static final IODataType probeMCDataType(IData iData) {
  		Class< ? > clazz = iData.getSupportedClass();
  
  		if (clazz == IODataType.BOOLEAN.getSupportedClass()) {
  			return IODataType.BOOLEAN;
  		}
  
  		if (clazz == IODataType.DOUBLE.getSupportedClass()) {
  			return IODataType.DOUBLE;
  		}
  
  		if (clazz == IODataType.INTEGER.getSupportedClass()) {
  			return IODataType.INTEGER;
  		}
  
  		if (clazz == IODataType.STRING.getSupportedClass()) {
  			return IODataType.STRING;
  		}
  
  		if (clazz == GenericFileData.class) {
  			return IODataType.MEDIA;
  		}
  
  		// in case we do not find a matching type return null
  		return null;
  	}
  
  }
  