content: /**
   * ﻿Copyright (C) 2009 - 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.ags;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;
  
  import net.opengis.wps.x100.InputDescriptionType;
  import net.opengis.wps.x100.OutputDescriptionType;
  import net.opengis.wps.x100.ProcessDescriptionType;
  
  import org.apache.commons.io.FileUtils;
  import org.n52.wps.ags.workspace.AGSWorkspace;
  import org.n52.wps.io.data.GenericFileDataWithGT;
  import org.n52.wps.io.data.GenericFileDataConstants;
  import org.n52.wps.io.data.IData;
  import org.n52.wps.io.data.binding.complex.GenericFileDataWithGTBinding;
  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;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * @author Matthias Mueller, TU Dresden
   *
   */
  public class GenericAGSProcessDelegator implements IAlgorithm{
  	
  	private static Logger LOGGER = LoggerFactory.getLogger(GenericAGSProcessDelegator.class);
  	private final String processID;
  	private List<String> errors;
  	private AGSWorkspace workspace;
  	protected final ToolParameter[] parameterDescriptions;
  	private String[] toolParameters;
  	private final int parameterCount;
  	private final ProcessDescriptionType processDescription;
  	private final File instanceWorkspace;
  	
  	
  	
  	public GenericAGSProcessDelegator(File workspace, String processID, ToolParameter[] legacyParameters, ProcessDescriptionType processDescription) {
  		errors = new ArrayList<String>();
  		this.processID = processID;
  		this.parameterDescriptions = legacyParameters;
  		this.parameterCount = legacyParameters.length;
  		this.processDescription = processDescription;
  		this.toolParameters = new String[this.parameterCount];
  		this.instanceWorkspace = workspace;
  		
  	}
  	
  	
  	public ProcessDescriptionType getDescription() {
  		return processDescription;
  	}
  
  	public List<String> getErrors() {
  		return errors;
  	}
  	
  	
  	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;
  				}
  				else if(datatype.equalsIgnoreCase("boolean")){
  					return LiteralBooleanBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("float")){
  					return LiteralFloatBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("double")){
  					return LiteralDoubleBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("int")){
  					return LiteralIntBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("integer")){
  					return LiteralIntBinding.class;
  				}
  			}
  			
  			//Complex Output
  			else if(input.isSetComplexData()){
  				return GenericFileDataWithGTBinding.class;
  			}
  		}
  		
  		return null;
  	}
  
  	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;
  				}
  				else if(datatype.equalsIgnoreCase("boolean")){
  					return LiteralBooleanBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("float")){
  					return LiteralFloatBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("double")){
  					return LiteralDoubleBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("int")){
  					return LiteralIntBinding.class;
  				}
  				else if(datatype.equalsIgnoreCase("integer")){
  					return LiteralIntBinding.class;
  				}
  			}
  			
  			//Complex Output
  			else if(output.isSetComplexOutput()){
  				return GenericFileDataWithGTBinding.class;
  			}
  		}
  		return null;
  	}
  		
  	
  	public String getWellKnownName() {
  		return processID;
  	}
  
  	
  	public boolean processDescriptionIsValid() {
  		return this.getDescription().validate();
  	}
  
  	
  	public Map<String, IData> run(Map<String, List<IData>> inputData) {
  		
  		//initialize arcObjects
  		AGSProperties.getInstance().bootstrapArcobjectsJar();
  		
  		//create the workspace
  		this.workspace = new AGSWorkspace(instanceWorkspace);
  		
  		//Assign the parameters
  		for (int i=0; i<this.parameterCount; i++){
  			
  			ToolParameter currentParam = this.parameterDescriptions[i];
  			
  			// input parameters
  			if(currentParam.isInput){
  				if(inputData.containsKey(currentParam.wpsInputID)){
  					//open the IData list and iterate through it
  					List<IData> dataItemList = inputData.get(currentParam.wpsInputID);
  					Iterator<IData> it = dataItemList.iterator();
  					ArrayList<String> valueList = new ArrayList<String>();
  					while (it.hasNext()){
  						IData currentItem = it.next();
  						valueList.add(this.loadSingleDataItem(currentItem));
  					}
  					
  					String[] valueArray = valueList.toArray(new String[0]);
  					
  					//TODO verify: isnt the input separator acutally an " ; "?
  					this.toolParameters[i] = this.calcToolParameterString(" ", valueArray);
  				}
  				else{
  					if(currentParam.isOptional){
  						this.toolParameters[i] = null;
  					}else{
  						errors.add("Error while allocating input parameter " + currentParam.wpsInputID);
  						throw new RuntimeException("Error while allocating input parameter " + currentParam.wpsInputID);
  						
  					}
  				}
  			}
  			
  			//output only parameters
  			else if(currentParam.isOutput && !currentParam.isInput){
  				if(currentParam.isComplex){
  					String extension = "";
  					if(currentParam.schema != null && currentParam.schema.length()>0){
  						//we have vector data. So use a shp file.
  						extension = "shp";
  					}else{
  						extension = GenericFileDataConstants.mimeTypeFileTypeLUT().get(currentParam.mimeType);
  					}
  					String fileName = UUID.randomUUID().toString().substring(0,7) + "." + extension; // geoprocessor can't handle points, dashes etc in output file name
  					
  					fileName = this.addOutputFile(fileName);
  					this.toolParameters[i] = fileName;
  				}
  			}
  		}
  		
  		
  		//execute
  		String toolName = this.processDescription.getTitle().getStringValue();
  		LOGGER.info("Executing ArcGIS tool " + toolName + " . Parameter array contains " + this.parameterCount + " parameters.");
  		try {
  			workspace.executeGPTool(toolName, null, this.toolParameters);
  		} catch (IOException e1) {
  			LOGGER.error(e1.getMessage());
  			errors.add(e1.getMessage());
  			throw new RuntimeException(e1.getMessage()); // otherwise WPS tries to zip and return non-existing files => null pointer
  		}
  		
  		
  		//create the output
  		HashMap<String, IData> result = new HashMap<String, IData>();
  		
  		for (int i=0; i<this.parameterCount; i++){
  			ToolParameter currentParam = this.parameterDescriptions[i];
  			
  			if(currentParam != null // Otherwise, nullpointer exception when trying to process outputs!
  					&& currentParam.isOutput){ 
  				
  				if(currentParam.isComplex){
  					String fileName = this.toolParameters[i];
  					//GenericFileData outputFileData = new GenericFileData(this.workspace.getFileAsStream(fileName), currentParam.mimeType);
  					
  					File currentFile = new File (fileName);
  					GenericFileDataWithGT outputFileData;
  					try {
  						if(currentParam.schema != null && currentParam.schema.length()>0){
  							//we have vector data. So use a shp file.
  							
  							outputFileData = new GenericFileDataWithGT(currentFile, GenericFileDataConstants.MIME_TYPE_ZIPPED_SHP);
  						}else{
  							outputFileData = new GenericFileDataWithGT(currentFile, currentParam.mimeType);
  						}
  						result.put(currentParam.wpsOutputID, new GenericFileDataWithGTBinding(outputFileData));
  					} catch (FileNotFoundException e) {
  						LOGGER.error("Could not read output file: " + fileName);
  						errors.add("Could not read output file: " + fileName);
  						throw new RuntimeException("No files found. Probably the process did not create any output files.");
  					} catch (IOException e) {
  						LOGGER.error("Could not create output file from: " + fileName);
  						errors.add("Could not create output file from: " + fileName);
  						e.printStackTrace();
  					}
  				}
  				
  				if(currentParam.isLiteral){
  					// not implemented
  				}
  				if(currentParam.isCRS){
  					// not implemented
  				}
  			}
  		}
  
  		//Handle if no result was created
  		if (result.isEmpty()){
  			String message = "";
  			for (String error : errors){
  				message = message.concat(error + " - ");
  			}
  			message = message.concat("ArcGIS Backend Process " + this.processID + " did not return any output"
  					+ " data or the output data could not be processed.");
  			throw new RuntimeException(message);
  		}
  		return result;
  	}
  	
  	private String loadSingleDataItem(IData dataItem){
  		
  		Object payload = dataItem.getPayload();
  		String value = null;
  		
  		//File
  		if (payload instanceof GenericFileDataWithGT){
  			GenericFileDataWithGT gfd = (GenericFileDataWithGT)payload;
  			value = gfd.writeData(this.workspace.getWorkspace());	
  		}
  		
  		//String
  		else if (payload instanceof String)
  			value = (String) payload;
  		
  		//Float
  		else if (payload instanceof Float)
  			value = ((Float)payload).toString();
  
  		//Integer
  		else if (payload instanceof Integer)
  			value = ((Integer)payload).toString();
  		
  		//Double
  		else if (payload instanceof Double)
  			value = ((Double)payload).toString();
  		
  		return value;
  	}
  	
  	private String calcToolParameterString(String valueSeparator, String[] valueArray){
  		
  		String returnValue = null;
  		boolean firstrun = true;
  		
  		for(String currentValue : valueArray){
  			if (firstrun){
  				firstrun = false;
  				returnValue = currentValue;
  			}
  			else {
  				returnValue = returnValue + valueSeparator + currentValue;
  			}
  		}
  		
  		return returnValue;
  	}
  	
  	private final String addOutputFile (String fileName){
  		String newFileName = this.workspace.getWorkspace().getAbsolutePath() + "\\" + fileName;
  		return newFileName;
  	}
  	
  	//delete the current workspace
  	protected void finalize(){
  		try {
  			FileUtils.deleteDirectory(instanceWorkspace);
  		} catch (IOException e) {
  			LOGGER.error("Could not delete dead workspace:\n" + instanceWorkspace.getAbsolutePath());
  			e.printStackTrace();
  		}
  	}
  }
  