package eu.dnetlib.validator2.engine.builtins

import eu.dnetlib.validator2.engine.XMLHelper
import eu.dnetlib.validator2.engine.contexts.XPathExpressionContext
import eu.dnetlib.validator2.engine.contexts.XPathExpressionProperty
import org.w3c.dom.Document
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll

class StandardXPathExpressionPropertySpecification extends Specification {

    static final String XPATH_MATCHES_THREE = "//node/attr[@id='title']/text()"
    static final String XPATH_MATCHES_ONE   = "/node/attr[@id='title']/text()"
    static final String XPATH_MATCHES_NONE  = "//nonexistent_field"
    static final String XPATH_1_OF_2        = "//attr[@id='1of2_exists']/text()"
    static final String XPATH_0_OF_1        = "//node/attr[@id='0of1_exists']/text()"

    @Shared Document doc = XMLHelper.parse("/eu/dnetlib/validator2/engine/xml-field-exists.xml")

    @Shared XPathExpressionProperty xpathExpProp1 = new StandardXPathExpressionProperty(XPathExpressionContext.PROPERTY_NAME)

    def "XPath expressions get compiled properly"() {
        given: "compiledExpressions is initially empty"
        def compiledExpressions = XPathExpressionHelper.COMPILED_EXPRESSIONS
        assert compiledExpressions.isEmpty()

        and: "set the same xpath expression in 2 different instances"
        def xpathExpProp2 = new StandardXPathExpressionProperty(XPathExpressionContext.PROPERTY_NAME)
        xpathExpProp1.setValue(XPATH_MATCHES_THREE)
        xpathExpProp2.setValue(XPATH_MATCHES_THREE)

        and: "additional mock documents"
        def doc1 = Mock(Document)
        def doc2 = Mock(Document)

        when: "evaluate (compile) expression against mock documents"
        xpathExpProp1.evaluate(doc)
        xpathExpProp1.evaluate(doc1)
        xpathExpProp2.evaluate(doc2)

        then: "the expression is compiled only once"
        compiledExpressions.size() == 1
    }

    @Unroll
    def "evaluation with expression #xpathExpression returns #numberOfNodes nodes"() {
        when:
        xpathExpProp1.setValue(xpathExpression)

        then:
        xpathExpProp1.evaluate(doc).getLength() == numberOfNodes

        where:
        xpathExpression     || numberOfNodes
        XPATH_MATCHES_THREE || 3
        XPATH_MATCHES_ONE   || 1
        XPATH_MATCHES_NONE  || 0
        XPATH_1_OF_2        || 2
        XPATH_0_OF_1        || 1
    }

    def "XPath compiler handles invalid expression: #xpathExpression"() {
        when:
        xpathExpProp1.setValue(xpathExpression)

        then:
        thrown(RuntimeException)

        where:
        xpathExpression                       | _
        ""                                    | _
        " "                                   | _
        null                                  | _
        "invalid xpath"                       | _
        "//elem[starts-with(text(), 'valid']" | _
    }

}
