package de.kompf.javaxml;

import java.io.*;

import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.*;
import org.xml.sax.SAXException;

/**
 * The program parses a GPX file into a DOM.
 * The DOM is modified to create a route from all waypoints contained in the file.
 * The result is written into a second file.
 * 
 * @author Kompf
 *
 */
public class GpxDOMEditor {

  /**
   * Parse an XML file into a DOM.
   * @param file The file.
   * @return The parsed document.
   * @throws ParserConfigurationException
   * @throws SAXException
   * @throws IOException
   */
  private Document parseFile(File file)
      throws ParserConfigurationException, SAXException, IOException {
    DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
    return docBuilder.parse(file);
  }

  /**
   * Write a DOM into a file.
   * @param doc The document.
   * @param file The file.
   * @throws TransformerException
   */
  private void writeFile(Document doc, File file) throws TransformerException {
    TransformerFactory transFactory = TransformerFactory.newInstance();
    Transformer trans = transFactory.newTransformer();
    trans.setOutputProperty(OutputKeys.INDENT, "yes");
    trans.setOutputProperty(OutputKeys.METHOD, "xml");
    trans.transform(new DOMSource(doc), new StreamResult(file));
  }
  
  /**
   * Modify an existing GPX document to create a route from all waypoints.
   * @param gpxDoc The GOX document.
   * @throws SAXException
   */
  private void createRouteFromWaypoints(Document gpxDoc) throws SAXException {
    // get the GPX element
    Element gpxElement = gpxDoc.getDocumentElement();
    if (! "gpx".equals(gpxElement.getNodeName())) {
      throw new SAXException("This is not a GPX file: " + gpxElement.getNodeName());
    }
    
    // get the waypoints
    NodeList wptList = gpxDoc.getElementsByTagName("wpt");
    if (wptList.getLength() > 0) {
      // create the route with a name
      Element rteElement = gpxDoc.createElement("rte");
      gpxElement.appendChild(rteElement);
      rteElement.appendChild(createElementWithText(gpxDoc, "name", "Created from waypoints"));
    
      // for each waypoint: create a routepoint with the same lat/lon attributes
      for (int i = 0; i < wptList.getLength(); ++i) {
        Element wpt = (Element) wptList.item(i);
        Element rtept = gpxDoc.createElement("rtept");
        copyAttributes(wpt, rtept);
        rteElement.appendChild(rtept);
      }
    }
  }
  
  /**
   * Copy all attributes of the source element to the destination element.
   * @param src The source element.
   * @param dst The destination element.
   */
  private void copyAttributes(Element src, Element dst) {
    NamedNodeMap attrs = src.getAttributes();
    for (int i = 0; i < attrs.getLength(); ++i) {
      dst.setAttributeNode((Attr) attrs.item(i).cloneNode(false));
    }
  }

  /**
   * Create an element that contains a text node.
   * @param doc The document.
   * @param tagName The name of the element.
   * @param text The text.
   * @return The newly created element.
   */
  private Element createElementWithText(Document doc, String tagName, String text) {
    Element el = doc.createElement(tagName);
    el.appendChild(doc.createTextNode(text));
    return el;
  }

  /**
   * MAIN.
   * 
   * @param args ignored
   * @throws IOException 
   * @throws SAXException 
   * @throws ParserConfigurationException 
   * @throws TransformerException 
   */
  public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, TransformerException {
    File inFile = new File("Rue-Mz.gpx");
    File outFile = new File("Rue-Mz-Rte.gpx");
    
    GpxDOMEditor gpxDOMEditor = new GpxDOMEditor();
    Document gpxDoc = gpxDOMEditor.parseFile(inFile);
    gpxDOMEditor.createRouteFromWaypoints(gpxDoc);
    gpxDOMEditor.writeFile(gpxDoc, outFile);
    
    System.out.println("GPX file created: " + outFile.getAbsolutePath());
  }

}

