import java.io.*;
import java.util.*;

import org.jdom2.*;



/**
 * Takes a featuremodel.xml file generated using FeatureModelConfigurator and a given ROS/ Gazebo .gazebo.xacro file.
 * The .gazebo.xacro file is modified to support the features specified in the featuremodel.xml file.
 */
public class Adapter extends XMLHelper {
	
	/**
	 * Obtains an attribute value of the input/ output entries specified in adapter.config.
	 * 
	 * @param doc		The JDOM Document (adapter.config)
	 * @param entry		String formed as "{input or output}/{entity}"
	 * @param val		Attribute name as string
	 * @return path as specified in adapter.config
	 */
	private static String getConfigVal(Document doc, String str, String val) {
		List<Attribute> valList = xQueryAttributes(doc, "//"+str+"/@"+val);
		if(valList.size()!=1) {
			System.err.println("Error while reading input/ output "+val+" value from adapter.config.");
			System.exit(-1);
		}

		return valList.get(0).getValue();
	}
	
	public Adapter() {
		System.out.println("Adapter started.");
		
		try {
			//######### Input ###############################			
			Document adapterConfig = readXml("./adapter.config");
			
			String in_fmPath = getConfigVal(adapterConfig, "input/featuremodel", "path");
			String in_scPath = getConfigVal(adapterConfig, "input/sensorconfig", "path");
			String in_gmPath = getConfigVal(adapterConfig, "input/gazebomodel", "path");
			String out_gmPath = getConfigVal(adapterConfig, "output/gazebomodel", "path");
			String out_bnPath = getConfigVal(adapterConfig, "output/botname", "path");
			String in_fmName = getConfigVal(adapterConfig, "input/featuremodel", "name");
			String in_gmName = "";
			String out_gmName = getConfigVal(adapterConfig, "output/gazebomodel", "name");
			String out_bnName = getConfigVal(adapterConfig, "output/botname", "name");
			
			Document featureModel = readXml(in_fmPath+in_fmName);
			List<Document> sensorConfigList = new ArrayList<Document>();
			Document gazeboModel = null;
			
			//Get the selected mode (either NO_CHANGE or Custom) as String
			String mode = "";
			List<Attribute> modeList = xQueryAttributes(featureModel, "//mode/@param");
			if(modeList.size()==1) {
				mode = modeList.get(0).getValue();
			}
			else {
				System.err.println("Invalid input in \'"+in_fmName+"\': NO_CHANGE/ Custom parameter could not be parsed.");
				System.exit(-1);
			}
			
			//Get all selected sensorconfig files as List<Document>
			switch(mode) {
				case "NO_CHANGE":
					break;
				case "Custom":
					List<Attribute> sensorList = xQueryAttributes(featureModel, "//sensor/@name");
					
					List<String> scList = getFileNames(in_scPath);
					for(Attribute sensor : sensorList) {
						boolean inDirectory = false;
						for(String fileName : scList) {
							if(fileName.equals(sensor.getValue()+".xml")) inDirectory = true;
						}
						if(!inDirectory) {
							System.err.println("Cannot handle \'"+sensor.getValue()+"\': No input file known.");
							System.exit(-1);
						}
					}
					
					for(Attribute sensor : sensorList) {
						sensorConfigList.add(readXml(in_scPath+sensor.getValue()+".xml")); 
					}
					break;
				default:
					System.err.println("Invalid input in \'"+in_fmName+"\': NO_CHANGE/ Custom parameter could not be parsed.");
					System.exit(-1);
			}
			
			//Get the selected .gazebo.xacro file as Document
			String botName = "";
			List<Attribute> botList = xQueryAttributes(featureModel, "//templates/@name");
			if(botList.size()==1) {
				botName = botList.get(0).getValue();
			}
			else {
				System.err.println("Invalid input in \'"+in_fmName+"\': No or multiple bots selected.");
				System.exit(-1);
			}

			in_gmName = botName+".gazebo.xacro";
			
			List<String> gmList = getFileNames(in_gmPath);
			boolean inDirectory = false;
			for(String fileName : gmList) {
				if(fileName.equals(in_gmName)) inDirectory = true;
			}
			if(!inDirectory) {
				System.err.println("Cannot handle \'"+botName+"\': No input file known.");
				System.exit(-1);
			}
			
			gazeboModel = readXml(in_gmPath+in_gmName);
			//###############################################
			
			
			//######### Operate on .gazebo.xacro ############
			switch(mode) {
				case "NO_CHANGE":
					break;
				case "Custom":
					//Remove all existing sensors in template
					List<Element> removableSensorsList = xQueryElements(gazeboModel, "//sensor");
					
					for(Element sensor : removableSensorsList) {	
						Element entry = sensor.getParentElement();
						if(entry.getName().equals("gazebo")) {
							entry.detach();
						}
						else {
							System.out.println("Found a <sensor> element in \'"+in_gmName+
								"\' that does not have a <gazebo> element as immediate parent. It was not removed, as this input is unexpected.");	
						}
					}
					
					//Insert selected sensors into template
					for(Document sensorConfig : sensorConfigList) {	
						List<Element> sensorList = xQueryElements(sensorConfig, "//gazebo");
						if(sensorList.size()==1) {
							Element sensor = sensorList.get(0);
							
							//Warn if a sensor still contains $(arg ...) values
							List<Element> tagList = xQueryElements(sensor, "//*");
							for(Element tag : tagList) {	
								String text = tag.getText();
								if(text.contains("$")) {
									System.out.println("Warning: a sensorconfig file still contains an abstract value: \'"+text+
										"\'. It should be replaced with a set value, or the output .gazebo.xacro will likely be broken.");	
								}
							}
							
							sensor.detach();
							gazeboModel.getRootElement().addContent(sensor);
						}
						else {
							System.err.println("Invalid input. At least one sensorconfig file contains no or multiple <gazebo> entries.");
							System.exit(-1);
						}
					}
					break;
				default:
					System.err.println("Invalid input in \'"+in_fmName+"\': NO_CHANGE/ Custom parameter could not be parsed.");
					System.exit(-1);
			}
			//###############################################
			
			
			//######### Output result #######################
			saveAsXml(gazeboModel, out_gmPath+out_gmName);
			
			BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(out_bnPath+out_bnName));
			bufferedWriter.write(botName);
			bufferedWriter.close();
			//###############################################
		}
		catch (Exception e) {
			e.printStackTrace();
		}

		System.out.println("Adapter finished.");
	}
}