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

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.reporting.model.ClassificationModel;
import org.jboss.windup.reporting.service.ClassificationService;
import org.jboss.windup.rules.apps.xml.model.XMLDocumentCache;
import org.jboss.windup.rules.apps.xml.model.XmlFileModel;
import org.jboss.windup.util.exception.WindupException;
import org.jboss.windup.util.xml.LocationAwareXmlReader;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * Manages creating, querying, and deleting XmlFileModels.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 * @author Ondrej Zizka
 */
public class XmlFileService extends GraphService<XmlFileModel>
{
    private static final Logger LOG = Logger.getLogger(XmlFileService.class.getSimpleName());

    public final static String UNPARSEABLE_XML_CLASSIFICATION = "Unparsable XML File";
    public final static String UNPARSEABLE_XML_DESCRIPTION = "This file could not be parsed";


    public XmlFileService(GraphContext ctx)
    {
        super(ctx, XmlFileModel.class);
    }

    /**
     * Loads and parses the provided XML file. This will quietly fail (not throwing an {@link Exception}) and return
     * null if it is unable to parse the provided {@link XmlFileModel}. A {@link ClassificationModel} will be created to
     * indicate that this file failed to parse.
     *
     * @return Returns either the parsed {@link Document} or null if the {@link Document} could not be parsed
     */
    public Document loadDocumentQuiet(EvaluationContext context, XmlFileModel model)
    {
        try
        {
            return loadDocument(context, model);
        }
        catch(Exception ex)
        {
            return null;
        }
    }

    /**
     * Loads and parses the provided XML file. This will quietly fail (not throwing an {@link Exception}) and return
     * null if it is unable to parse the provided {@link XmlFileModel}. A {@link ClassificationModel} will be created to
     * indicate that this file failed to parse.
     *
     * @return Returns either the parsed {@link Document} or null if the {@link Document} could not be parsed
     */
    public Document loadDocument(EvaluationContext context, XmlFileModel model) throws WindupException
    {
        if (model.asFile().length() == 0)
        {
            final String msg = "Failed to parse, XML file is empty: " + model.getFilePath();
            LOG.log(Level.WARNING, msg);
            model.setParseError(msg);
            throw new WindupException(msg);
        }

        ClassificationService classificationService = new ClassificationService(getGraphContext());

        XMLDocumentCache.Result cacheResult = XMLDocumentCache.get(model);
        if (cacheResult.isParseFailure())
        {
            final String msg = "Not loading XML file '" + model.getFilePath() + "' due to previous parse failure: " + model.getParseError();
            LOG.log(Level.FINE, msg);
            //model.setParseError(msg);
            throw new WindupException(msg);
        }

        Document document = cacheResult.getDocument();
        if (document != null)
            return document;

        // Not yet cached - load, store in cache and return.
        try (InputStream is = model.asInputStream())
        {
            document = LocationAwareXmlReader.readXML(is);
            XMLDocumentCache.cache(model, document);
        }
        catch (SAXException e)
        {
            XMLDocumentCache.cacheParseFailure(model);
            document = null;
            final String message = "Failed to parse XML file: " + model.getFilePath() + ", due to: " + e.getMessage();
            LOG.log(Level.WARNING, message);
            classificationService.attachClassification(context, model, UNPARSEABLE_XML_CLASSIFICATION, UNPARSEABLE_XML_DESCRIPTION);
            model.setParseError(message);
            throw new WindupException(message, e);
        }
        catch (IOException e)
        {
            XMLDocumentCache.cacheParseFailure(model);
            document = null;
            final String message = "Failed to parse XML file: " + model.getFilePath() + ", due to: " + e.getMessage();
            LOG.log(Level.WARNING, message);
            classificationService.attachClassification(context, model, UNPARSEABLE_XML_CLASSIFICATION, UNPARSEABLE_XML_DESCRIPTION);
            model.setParseError(message);
            throw new WindupException(message, e);
        }

        return document;
    }
}
