/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.csu.sdmx.validation.xml;

import cz.csu.sdmx.validation.model.ValidationError;
import cz.csu.sdmx.validation.model.ValidationResult;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DefaultHandler2;

/**
 *
 * @author themi
 */
public class XmlHandler extends DefaultHandler2{
    public static final String LINE_NUMBER_KEY_NAME = "lineNumber";
    public static final String URN_KEY_NAME = "urn";
    public static final String FILENAME_KEY_NAME = "filename";
    
    private Locator locator;
    private Document doc;
    private Stack<Element> elementStack;
    private StringBuilder textBuffer;
    private Map<String,String> namespaces;
    private ValidationResult result;
    
    public XmlHandler(Document doc, ValidationResult result) {
        this.doc = doc;
        this.result = result;
        
        elementStack = new Stack<>();
        textBuffer = new StringBuilder();
        namespaces = new HashMap<>();
    }
    
    @Override
    public void warning(SAXParseException e) throws SAXException {
            result.addError(createError(ValidationError.WARNING, e));
    }

    @Override
    public void error(SAXParseException e) throws SAXException {
            result.addError(createError(ValidationError.ERROR, e));
    }

    @Override
    public void fatalError(SAXParseException e) throws SAXException {
            result.addError(createError(ValidationError.FATAL, e));
    }
    
    private ValidationError createError(String type, SAXParseException e){
        ValidationError error = new ValidationError();
        error.setFilename((String)doc.getUserData(XmlHandler.FILENAME_KEY_NAME));
        error.setText(e.getMessage());
        error.setLineNumber(String.valueOf(e.getLineNumber()));
        error.setType(type);
        
        return error;
    }

    @Override
    public void setDocumentLocator(final Locator locator) {
        this.locator = locator; // Save the locator, so that it can be used later for line tracking when traversing nodes.
    }

    @Override
    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        //add all namespaces to list
        if(!prefix.isEmpty())
            prefix = ":" + prefix;
        namespaces.put("xmlns"+prefix, uri);
    }

    @Override
    public void endDocument() throws SAXException {
        //add all namespaces in list as attributes
        Element root = doc.getDocumentElement();
        for(Map.Entry<String,String> e : namespaces.entrySet()){
            root.setAttribute(e.getKey(), e.getValue());
            if(e.getValue().contains("urn"))
                doc.setUserData(URN_KEY_NAME, e.getValue(), null);
        }
    }

    @Override
    public void startElement(final String uri, final String localName, String qName, final Attributes attributes)
            throws SAXException {
        addTextIfNeeded();
        //create element by uri and name
        final Element el = doc.createElementNS(uri,qName);
        //add all attributes to element
        for (int i = 0; i < attributes.getLength(); i++) 
            el.setAttribute(attributes.getQName(i), attributes.getValue(i));
        
        //add line number as user data
        el.setUserData(LINE_NUMBER_KEY_NAME, String.valueOf(this.locator.getLineNumber()), null);
        elementStack.push(el);
    }

    @Override
    public void endElement(final String uri, final String localName, final String qName) {
        addTextIfNeeded();
        final Element closedEl = elementStack.pop();
        //if is root element, append to document
        if (elementStack.isEmpty()) { 
            doc.appendChild(closedEl);
        } else {
            //else get element above and append
            final Element parentEl = elementStack.peek();
            parentEl.appendChild(closedEl);
        }
    }

    @Override
    public void characters(final char ch[], final int start, final int length) throws SAXException {
        textBuffer.append(ch, start, length);
    }

    //outputs text accumulated under the current node
    private void addTextIfNeeded() {
        if (textBuffer.length() > 0) {
            final Element el = elementStack.peek();
            final Node textNode = doc.createTextNode(textBuffer.toString());
            el.appendChild(textNode);
            textBuffer.delete(0, textBuffer.length());
        }
    }

}
