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

import static org.joox.JOOX.$;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.phase.InitialAnalysisPhase;
import org.jboss.windup.config.query.Query;
import org.jboss.windup.config.query.QueryGremlinCriterion;
import org.jboss.windup.config.ruleprovider.IteratingRuleProvider;
import org.jboss.windup.config.projecttraversal.ProjectTraversalCache;
import org.jboss.windup.reporting.model.TechnologyTagLevel;
import org.jboss.windup.reporting.service.TechnologyTagService;
import org.jboss.windup.rules.apps.java.model.JavaClassModel;
import org.jboss.windup.rules.apps.java.service.JavaClassService;
import org.jboss.windup.rules.apps.javaee.model.HibernateEntityModel;
import org.jboss.windup.rules.apps.javaee.model.HibernateMappingFileModel;
import org.jboss.windup.rules.apps.javaee.service.HibernateEntityService;
import org.jboss.windup.rules.apps.javaee.service.HibernateMappingFileService;
import org.jboss.windup.rules.apps.xml.model.DoctypeMetaModel;
import org.jboss.windup.rules.apps.xml.model.XmlFileModel;
import org.jboss.windup.rules.apps.xml.service.XmlFileService;
import org.jboss.windup.util.xml.XmlUtil;
import org.ocpsoft.rewrite.config.ConditionBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.w3c.dom.Document;

import com.thinkaurelius.titan.core.attribute.Text;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.frames.FramedGraphQuery;
import com.tinkerpop.gremlin.java.GremlinPipeline;
import org.jboss.windup.config.metadata.RuleMetadata;


/**
 * Discovers the hibernate.hbm.xml files.
 */
@RuleMetadata(phase = InitialAnalysisPhase.class, perform = "Discover hibernate.hbm.xml files")
public class DiscoverHibernateMappingRuleProvider extends IteratingRuleProvider<DoctypeMetaModel>
{
    private static final Logger LOG = Logger.getLogger(DiscoverHibernateMappingRuleProvider.class.getSimpleName());

    private static final String TECH_TAG = "Hibernate Mapping";
    private static final TechnologyTagLevel TECH_TAG_LEVEL = TechnologyTagLevel.IMPORTANT;

    private static final String REGEX_HIBERNATE = "(?i).*hibernate.mapping.*";


    @Override
    public ConditionBuilder when()
    {

        QueryGremlinCriterion doctypeSearchCriterion = new QueryGremlinCriterion()
        {
            @Override
            public void query(GraphRewrite event, GremlinPipeline<Vertex, Vertex> pipeline)
            {
                pipeline.has(DoctypeMetaModel.PROPERTY_PUBLIC_ID, Text.REGEX, REGEX_HIBERNATE);

                FramedGraphQuery systemIDQuery = event.getGraphContext().getQuery().type(DoctypeMetaModel.class)
                            .has(DoctypeMetaModel.PROPERTY_SYSTEM_ID, Text.REGEX, REGEX_HIBERNATE);
                GremlinPipeline<Vertex, Vertex> systemIdPipeline = new GremlinPipeline<>(systemIDQuery.vertices());

                pipeline.add(systemIdPipeline);

                pipeline.dedup();
            }
        };

        return Query.fromType(DoctypeMetaModel.class).piped(doctypeSearchCriterion);
    }

    @Override
    public void perform(GraphRewrite event, EvaluationContext context, DoctypeMetaModel payload)
    {
        JavaClassService javaClassService = new JavaClassService(event.getGraphContext());
        HibernateMappingFileService hibernateMappingFileService = new HibernateMappingFileService(
                    event.getGraphContext());
        HibernateEntityService hibernateEntityService = new HibernateEntityService(event.getGraphContext());
        XmlFileService xmlFileService = new XmlFileService(event.getGraphContext());
        TechnologyTagService technologyTagService = new TechnologyTagService(event.getGraphContext());

        String publicId = payload.getPublicId();
        String systemId = payload.getSystemId();

        // extract the version information from the public / system ID.
        String versionInformation = extractVersion(publicId, systemId);

        for (XmlFileModel xml : payload.getXmlResources())
        {
            // create a facet, and then identify the XML.
            HibernateMappingFileModel hibernateMapping = hibernateMappingFileService.addTypeToModel(xml);

            Document doc = xmlFileService.loadDocumentQuiet(context, hibernateMapping);

            if (!XmlUtil.xpathExists(doc, "/hibernate-mapping", null))
            {
                LOG.log(Level.INFO, "Docment does not contain Hibernate Mapping.");
                continue;
            }

            String clzPkg = $(doc).xpath("/hibernate-mapping").attr("package");
            String clzName = $(doc).xpath("/hibernate-mapping/class").attr("name");
            String tableName = $(doc).xpath("/hibernate-mapping/class").attr("table");
            String schemaName = $(doc).xpath("/hibernate-mapping/class").attr("schema");
            String catalogName = $(doc).xpath("/hibernate-mapping/class").attr("catalog");

            if (StringUtils.isBlank(clzName))
            {
                LOG.log(Level.FINE, "Docment does not contain class name. Skipping.");
                continue;
            }

            technologyTagService.addTagToFileModel(xml, TECH_TAG, TECH_TAG_LEVEL);

            // prepend with package name.
            if (StringUtils.isNotBlank(clzPkg) && !StringUtils.startsWith(clzName, clzPkg))
            {
                clzName = clzPkg + "." + clzName;
            }

            // get a reference to the Java class.
            JavaClassModel clz = javaClassService.getOrCreatePhantom(clzName);

            // create the hibernate facet.
            HibernateEntityModel hibernateEntity = hibernateEntityService.create();
            hibernateEntity.setSpecificationVersion(versionInformation);
            hibernateEntity.setApplications(ProjectTraversalCache.getApplicationsForProject(event.getGraphContext(), xml.getProjectModel()));
            hibernateEntity.setJavaClass(clz);
            hibernateEntity.setTableName(tableName);
            hibernateEntity.setSchemaName(schemaName);
            hibernateEntity.setCatalogName(catalogName);

            // map the entity back to the XML mapping.
            hibernateMapping.addHibernateEntity(hibernateEntity);

            if (StringUtils.isNotBlank(versionInformation))
            {
                hibernateEntity.setSpecificationVersion(versionInformation);
                hibernateMapping.setSpecificationVersion(versionInformation);
            }
        }
    }

    private String extractVersion(String publicId, String systemId)
    {
        Pattern pattern = Pattern.compile("[0-9][0-9a-zA-Z.-]+");

        if (StringUtils.isNotBlank(publicId))
        {
            Matcher matcher = pattern.matcher(publicId);
            if (matcher.find())
            {
                return matcher.group();
            }
        }

        if (StringUtils.isNotBlank(systemId))
        {
            Matcher matcher = pattern.matcher(systemId);
            if (matcher.find())
            {
                String match = matcher.group();

                // for system ID, make sure to remove the ".dtd" that could come in.
                return StringUtils.removeEnd(match, ".dtd");
            }
        }
        return null;
    }
}
