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

import static org.joox.JOOX.$;

import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.metadata.MetadataBuilder;
import org.jboss.windup.config.operation.iteration.AbstractIterationOperation;
import org.jboss.windup.config.phase.ClassifyFileTypesPhase;
import org.jboss.windup.config.query.Query;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.reporting.service.ClassificationService;
import org.jboss.windup.rules.apps.xml.model.DoctypeMetaModel;
import org.jboss.windup.rules.apps.xml.model.NamespaceMetaModel;
import org.jboss.windup.rules.apps.xml.model.XmlFileModel;
import org.jboss.windup.rules.apps.xml.service.DoctypeMetaService;
import org.jboss.windup.rules.apps.xml.service.NamespaceService;
import org.jboss.windup.rules.apps.xml.service.XmlFileService;
import org.jboss.windup.rules.files.FileMapping;
import org.jboss.windup.util.xml.LocationAwareContentHandler;
import org.jboss.windup.util.xml.LocationAwareContentHandler.Doctype;
import org.jboss.windup.util.xml.XmlUtil;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.w3c.dom.Document;

/**
 * Extract some basic metadata from all {@link XmlFileModel}s found in the graph.
 */
public class DiscoverXmlFilesRuleProvider extends AbstractRuleProvider
{
    private static final Logger LOG = Logger.getLogger(DiscoverXmlFilesRuleProvider.class.getSimpleName());

    public DiscoverXmlFilesRuleProvider()
    {
        super(MetadataBuilder.forProvider(DiscoverXmlFilesRuleProvider.class)
                    .setPhase(ClassifyFileTypesPhase.class));
    }

    @Override
    public Configuration getConfiguration(GraphContext context)
    {
        return ConfigurationBuilder.begin()

            .addRule(FileMapping.from(".*\\.xml$").to(XmlFileModel.class))
            .addRule(FileMapping.from(".*\\.xmi$").to(XmlFileModel.class))
            .addRule(FileMapping.from(".*\\.jsf$").to(XmlFileModel.class))
            .addRule(FileMapping.from(".*\\.xhtml$").to(XmlFileModel.class))
            .addRule()
            .when(Query.fromType(XmlFileModel.class))
            .perform(new AbstractIterationOperation<XmlFileModel>()
            {
                @Override
                public void perform(GraphRewrite event, EvaluationContext context, XmlFileModel payload)
                {
                    addXmlMetaInformation(event, context, payload);
                }

                @Override
                public String toString()
                {
                    return "IndexXmlFilesMetadata";
                }
            });
    }

    private void addXmlMetaInformation(GraphRewrite event, EvaluationContext context, XmlFileModel file)
    {
        DoctypeMetaService docTypeService = new DoctypeMetaService(event.getGraphContext());
        NamespaceService namespaceService = new NamespaceService(event.getGraphContext());

        try
        {
            Document parsedDocument = file.asDocument();

            // pull out doctype data.
            Doctype docType = (Doctype) parsedDocument.getUserData(LocationAwareContentHandler.DOCTYPE_KEY_NAME);

            // if this is successful, then we know it is a proper XML file.
            // set it to the graph as an XML file.
            XmlFileModel xmlResourceModel = GraphService.addTypeToModel(event.getGraphContext(), file, XmlFileModel.class);

            // get and index by the root tag.
            String tagName = $(parsedDocument).tag();
            xmlResourceModel.setRootTagName(tagName);

            if (docType != null)
            {
                // create the doctype from
                Iterator<DoctypeMetaModel> metas = docTypeService.findByPublicIdAndSystemId(docType.getPublicId(),
                            docType.getSystemId());
                if (metas.hasNext())
                {
                    DoctypeMetaModel meta = metas.next();
                    meta.addXmlResource(xmlResourceModel);
                    xmlResourceModel.setDoctype(meta);
                }
                else
                {
                    DoctypeMetaModel meta = event.getGraphContext().getFramed().addVertex(null, DoctypeMetaModel.class);
                    meta.addXmlResource(xmlResourceModel);
                    meta.setBaseURI(docType.getBaseURI());
                    meta.setName(docType.getName());
                    meta.setPublicId(docType.getPublicId());
                    meta.setSystemId(docType.getSystemId());
                }
            }

            Map<String, String> namespaceSchemaLocations = XmlUtil.getSchemaLocations(parsedDocument);
            if (namespaceSchemaLocations != null && namespaceSchemaLocations.size() > 0)
            {
                for (String namespace : namespaceSchemaLocations.keySet())
                {
                    NamespaceMetaModel meta = namespaceService.createNamespaceSchemaLocation(namespace,
                                namespaceSchemaLocations.get(namespace));
                    meta.addXmlResource(xmlResourceModel);
                }
            }
        }
        catch (Exception e)
        {
            if (file.asFile().length() == 0)
            {
                final String message = "Failed to parse XML entity: " + file.getFilePath() + ": the file is empty.";
                LOG.log(Level.FINE, message);
                file.setParseError(message);
            }
            else
            {
                final String message = "Failed to parse XML entity: " + file.getFilePath() + ", due to: " + e.getMessage();
                LOG.log(Level.INFO, message);
                LOG.log(Level.FINE, message, e);
                file.setParseError(message);
            }
            new ClassificationService(event.getGraphContext())
                .attachClassification(context, file, XmlFileService.UNPARSEABLE_XML_CLASSIFICATION, XmlFileService.UNPARSEABLE_XML_DESCRIPTION);
        }
    }
}
