package org.jboss.windup.rules.apps.xml.xml;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;

import org.apache.commons.lang3.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DefaultHandler2;

/**
 * This implements a SAX handler that maintains a list of validation errors for use by {@link ValidateXmlFilesRuleProvider}.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 */
class ValidateXmlHandler extends DefaultHandler2 {
    private static final String XMLSCHEMA_ATTRIBUTE_NAME = "schemaLocation";
    private static final String NO_NAMESPACE_XMLSCHEMA_ATTRIBUTE_NAME = "noNamespaceSchemaLocation";

    private final List<SAXParseException> parseExceptions = new ArrayList<>();
    private final List<String> xsdURLs = new ArrayList<>();
    private final EnhancedEntityResolver2 entityResolver;
    private boolean firstElementFound = false;
    private Locator locator;
    private boolean onlineMode;

    public ValidateXmlHandler(boolean onlineMode) {
        this.onlineMode = onlineMode;
        this.entityResolver = new EnhancedEntityResolver2(onlineMode);
    }

    /**
     * Contains a list of XSD urls found in this document.
     */
    public List<String> getXsdURLs() {
        return xsdURLs;
    }

    /**
     * Contains a list of parse failures in the document.
     */
    public List<SAXParseException> getParseExceptions() {
        return parseExceptions;
    }

    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException {
        return entityResolver.resolveEntity(publicId, systemId);
    }

    @Override
    public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId) throws SAXException, IOException {
        return entityResolver.resolveEntity(name, publicId, baseURI, systemId);
    }

    @Override
    public void setDocumentLocator(Locator locator) {
        this.locator = locator;
    }

    static boolean isNetworkUrl(String url) {
        return url != null && !url.startsWith("file");
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if (!firstElementFound) {
            firstElementFound = true;
            try {
                xsdURLs.addAll(getXSDLocations(attributes));

                // validate the xsd urls
                for (String xsdUrl : xsdURLs) {
                    try {
                        if (!onlineMode && isNetworkUrl(xsdUrl)) {
                            continue;
                        }

                        URLConnection urlConnection = new URL(xsdUrl).openConnection();
                        urlConnection.setConnectTimeout(10000);
                        urlConnection.setReadTimeout(10000);
                        // this will throw if invalid
                        InputStream inputStream = urlConnection.getInputStream();
                    } catch (IOException e) {
                        parseExceptions.add(new SAXParseException(e.getMessage(), locator, e));
                    }
                }
            } catch (InvalidXSDURLException e) {
                parseExceptions.add(new SAXParseException(e.getMessage(), locator, e));
            }
        }
        super.startElement(uri, localName, qName, attributes);
    }

    @Override
    public void warning(SAXParseException e) throws SAXException {
        super.warning(e);
    }

    @Override
    public void error(SAXParseException e) throws SAXException {
        parseExceptions.add(e);
    }

    @Override
    public void fatalError(SAXParseException e) throws SAXException {
        parseExceptions.add(e);
    }

    private List<String> getXSDLocations(Attributes attributes) throws InvalidXSDURLException {
        List<String> xsdUrls = new ArrayList<>();
        if (attributes == null) {
            return xsdUrls;
        }

        String xsdSchemaAttr = attributes.getValue(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
                XMLSCHEMA_ATTRIBUTE_NAME);
        if (StringUtils.isNotBlank(xsdSchemaAttr)) {
            String[] splittedXslSchemaAttr = xsdSchemaAttr.split("\\s");
            for (int i = 1; i < splittedXslSchemaAttr.length; i += 2) {
                String urlStr = splittedXslSchemaAttr[i];
                xsdUrls.add(urlStr);
            }
        }

        String noNamespaceSchema = attributes.getValue(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
                NO_NAMESPACE_XMLSCHEMA_ATTRIBUTE_NAME);
        if (StringUtils.isNotBlank(noNamespaceSchema)) {
            xsdUrls.add(noNamespaceSchema);
        }
        return xsdUrls;
    }
}
