package de.renew.gui.pnml.creator;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;

import de.renew.gui.CPNDrawing;
import de.renew.gui.PlaceFigure;


public class PNMLCreator {
    /**
     * The logger for this class.
     */
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(PNMLCreator.class);
    private static final String xmlNamespace = "http://www.pnml.org/version-2009/grammar/pnml";
    private final String _netType;
    private final boolean _toolInsc;
    private Document _doc;

    /**
     * Creates PNMLCreator with a certain net type and a boolean for
     * the inscription.
     * @param netType the net type
     * @param toolInsc whether tool inscription exists or not
     */
    public PNMLCreator(String netType, boolean toolInsc) {
        _netType = netType;
        _toolInsc = toolInsc;
    }

    private static Transformer createTransformer() {
        try {
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
            return transformer;
        } catch (TransformerConfigurationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Initialize the document element with a single drawing.
     *
     * @param drawing the CPN drawing the document is going to be
     *                initialised with
     * @return place figures in the order they occur in the PNML document
     * @throws ParserConfigurationException when something goes wrong with
     * the document creation
     */
    public List<PlaceFigure> setDrawings(CPNDrawing drawing) throws ParserConfigurationException {
        makeDoc();
        return addNet(drawing);
    }

    private void setDrawings(CPNDrawing[] drawings) throws ParserConfigurationException {
        makeDoc();
        for (CPNDrawing drawing : drawings) {
            addNet(drawing);
        }
    }

    /**
     * Takes the OutputStream and writes it into the _doc field
     * of this class.
     * Also takes a CPNDrawing and initialises the document element
     * with one drawing.
     * @param stream the OutputStream
     * @param drawing the drawing that will be set in the document element
     * @throws Exception
     */
    public void write(OutputStream stream, CPNDrawing drawing) throws Exception {
        setDrawings(drawing);
        writeXML(stream);
    }

    /**
     * Takes the OutputStream and writes it into the _doc field
     * of this class. Also takes an array of CPNDrawings and sets
     * them for the document element.
     * @param stream the OutputStream
     * @param drawings the drawings that will be set in the document element
     * @throws Exception
     */
    public void write(OutputStream stream, CPNDrawing[] drawings) throws Exception {
        setDrawings(drawings);
        writeXML(stream);
    }

    private void makeDoc() throws ParserConfigurationException {
        DOMImplementation di =
            DocumentBuilderFactory.newInstance().newDocumentBuilder().getDOMImplementation();

        DocumentType dt = di.createDocumentType("PNMLFile", null, null);
        _doc = di.createDocument(xmlNamespace, "pnml", dt);
    }

    private List<PlaceFigure> addNet(CPNDrawing drawing) {
        Element eRoot = _doc.getDocumentElement();
        NetCreator netCreator = new NetCreator(_netType, _toolInsc);
        eRoot.appendChild(netCreator.createElement(this, drawing));
        return netCreator.getPlaceList();
    }

    private void writeXML(OutputStream stream) {
        Transformer transformer = createTransformer();
        DOMSource source = new DOMSource(_doc);
        StreamResult result = new StreamResult(stream);
        try {
            transformer.transform(source, result);
        } catch (TransformerException e) {
            logger.error(PNMLCreator.class.getSimpleName() + ": " + e.getLocalizedMessage(), e);
        }

        try {
            stream.flush();
        } catch (IOException e) {
            // ignore
        }
    }

    /**
     * Create a tag with the given name within the document.
     * @param tagName contains the tag name
     * @return {@link Document#createElement(String)}.
     */
    public Element createElement(String tagName) {
        return _doc.createElement(tagName);
    }

    /**
     * Creates a Toolspec. element and sets it attributes.
     * @return the toolSpec element
     */
    public Element createToolspecific() {
        Element toolSpec = createElement("toolspecific");
        toolSpec.setAttribute("tool", "renew");
        toolSpec.setAttribute("version", "2.0");
        return toolSpec;
    }

    /**
     * Creates a TextElement by calling the createElement() method
     * with "text" as the textname.
     * @param text the tag
     * @return the TextElement
     */
    public Element createTextElement(String text) {
        Element element = createElement("text");
        element.appendChild(_doc.createTextNode(text));
        return element;
    }

    public Element getDocumentElement() {
        return _doc.getDocumentElement();
    }
}