/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */

package net.ranides.assira.xml.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.nio.file.Path;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.functional.checked.CheckedSupplier;
import net.ranides.assira.trace.ExceptionUtils;
import net.ranides.assira.xml.XMLElement;
import net.ranides.assira.xml.XMLElements;
import net.ranides.assira.xml.XMLException;

import lombok.experimental.UtilityClass;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * This class is factory loading XML documents
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@UtilityClass
public final class W3ElementFactory {

    /**
     * Internal method used by {@link XMLElement#fromFile(File)}
     *
     * @param doc doc
     * @return XMLElement
     */
    public static XMLElement fromFile(File doc) {
        return fromSupplier(() -> W3Element.wrap(builder().parse(doc)));
    }

    /**
     * Internal method used by {@link XMLElement#fromPath(Path)}
     *
     * @param doc doc
     * @return XMLElement
     */
    public static XMLElement fromPath(Path doc) {
        return fromFile(doc.toFile());
    }

    /**
     * Internal method used by {@link XMLElement#fromURI(URI)}
     *
     * @param doc doc
     * @return XMLElement
     */
    public static XMLElement fromURI(URI doc) {
        return fromSupplier(() -> W3Element.wrap(builder().parse(doc.toString())));
    }

    /**
     * Internal method used by {@link XMLElement#fromString(String)}
     *
     * @param doc doc
     * @return XMLElement
     */
    public static XMLElement fromString(Reader doc) {
        return fromSupplier(() -> W3Element.wrap(builder().parse(new InputSource(doc))));
    }

    /**
     * Internal method used by {@link XMLElement#fromStream(InputStream)}
     *
     * @param doc doc
     * @return XMLElement
     */
    public static XMLElement fromStream(InputStream doc) {
        return fromSupplier(() -> W3Element.wrap(builder().parse(new InputSource(doc))));
    }
    
    private static XMLElement fromSupplier(CheckedSupplier<XMLElement,Exception> supplier) {
        try {
            return supplier.get();
        } catch (SAXException | ParserConfigurationException | IOException cause) {
            throw new XMLException(cause);
        } catch(Exception cause) {
            throw ExceptionUtils.rethrow(cause);
        }
    }

    static XMLElement append(XMLElement target, String xml) {
        Node content = parse(target, xml);
        Node out = target.node();
        while (content.hasChildNodes()) {
            Node first = content.removeChild(content.getFirstChild());
            out.appendChild(first);
        }
        return target;
    }
    
    static XMLElement prepend(XMLElement target, String xml) {
        Node content = parse(target, xml);
        Node out = target.node();
        while (content.hasChildNodes()) {
            Node last = content.removeChild(content.getLastChild());
            out.insertBefore(last, out.getFirstChild());
        }
        return target;
    }

    private static Node parse(XMLElement target, String xml) {
        try {
            DocumentType tt = target.document().node().getDoctype();
            String doctype = tt == null ? "" : "<!DOCTYPE __fragment__ [" + tt.getInternalSubset() + "] >";

            String wxml = doctype + "<__fragment__>" + xml + "</__fragment__>";
            Document cdoc = builder().parse(new InputSource(new StringReader(wxml)));
            return target.document().node().importNode(cdoc.getDocumentElement(),true);
        } catch(SAXException | DOMException | ParserConfigurationException | IOException cause) {
            throw new XMLException(cause);
        }
    }
    
    private static DocumentBuilder builder() throws ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        return factory.newDocumentBuilder();
    }
    
}
