package eu.dnetlib.validator2.engine.builtins

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

class XMLValidURLRuleSpecification extends Specification {

    static final String XPATH_VALID_URL        = "//attr[@id='valid_url']/text()"
    static final String XPATH_INVALID_URL      = "//attr[@id='invalid_url']/text()"
    static final String XPATH_MIXED_VALID_URL  = "//attr[@id='mixed_valid_url']/text()"
    static final String XPATH_SINGLE_VALID_URL = "//attr[@id='single_valid_url']/text()"
    static final String XPATH_NON_EXISTENT     = "//attr[@id='no_xpath']/text()"

    def ruleBuilder1 = XMLValidURLRule.builder()
    def ruleBuilder2 = XMLValidURLRule.builder()

    def "Builder works"() {
        given:
        def id = "1"
        def xpath = "//sampleXpath"
        def traversal = "all"

        when: "setting each property explicitly and calling build()"
        def rule1 = ruleBuilder1.
                setId(id).
                setXPathExpression(xpath).
                setNodeListAction(traversal).
                build()

        then:
        ruleContextMatchesProps(rule1.getContext(), id, xpath, traversal)

        when: "building from map containing properties"
        def rule2 = ruleBuilder2.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)        : id,
                (XPathExpressionContext.PROPERTY_NAME): xpath,
                (NodeListContext.PROPERTY_NAME)       : traversal
        ])

        then:
        ruleContextMatchesProps(rule2.getContext(), id, xpath, traversal)
    }

    void ruleContextMatchesProps(context, id, xpath, traversal) {
        assert context.getIdProperty().getValue() == id
        assert context.getXPathExpressionProperty().getValue() == xpath
        assert context.getNodeListActionProperty().getValue() == traversal
    }

    def "Builder handles premature build() call"() {
        when:
        ruleBuilder1.build()

        then:
        thrown(IllegalStateException)

        when:
        ruleBuilder2.
                setId("0").
                setXPathExpression("//sampleXpath").
                build()

        then:
        thrown(IllegalStateException)
    }

    @Unroll
    def "Builder validates invalid input properties => id: #id, xpath: #xpathExpression, node traversal: #traversal"() {
        when: "setting each property explicitly before calling build()"
        ruleBuilder1.
                setId(id).
                setXPathExpression(xpathExpression).
                setNodeListAction(traversal)

        then:
        thrown(expectedException)

        when: "reading properties from map"
        ruleBuilder2.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)        : id,
                (XPathExpressionContext.PROPERTY_NAME): xpathExpression,
                (NodeListContext.PROPERTY_NAME)       : traversal
        ])

        then:
        thrown(expectedException)

        where:
        id   | xpathExpression | traversal || expectedException
        ""   | "valid"         | ">0"      || IllegalArgumentException
        null | "valid"         | ">0"      || IllegalArgumentException
        "1"  | "valid"         | ""        || IllegalArgumentException
        "1"  | "valid"         | null      || IllegalArgumentException
        "1"  | "valid"         | "invalid" || IllegalArgumentException

        //TODO: These are validated when set, by XPathExpressionProperty and throw RuntimeException.
        // We could/should ommit them?
//        "1"  | ""              | ">0"      || IllegalArgumentException
//        "1"  | null            | ">0"      || IllegalArgumentException
        // Redundant and perhaps incorrect?
//        ""   | ""              | ""        || IllegalArgumentException
    }

    @Unroll
    def "Inputs id: #id, xpath: #xpath, regex: #regex, traversal: #traversal, result in #result"() {
        given:
        Document doc = XMLHelper.parse("/eu/dnetlib/validator2/engine/xml-valid-url.xml")
        assert doc != null

        when: "setting each property explicitly before calling build()"
        def rule1 = ruleBuilder1.
                setId(id).
                setXPathExpression(xpathExpression).
                setNodeListAction(traversal).
                build()

        then:
        rule1.test(doc) == result
        noExceptionThrown()

        when: "building from map containing properties"
        def rule2 = ruleBuilder2.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)          : id,
                (XPathExpressionContext.PROPERTY_NAME)  : xpathExpression,
                (NodeListContext.PROPERTY_NAME)         : traversal
        ])

        then:
        rule2.test(doc) == result
        noExceptionThrown()

        where:
        id   | xpathExpression        | traversal || result
        "1"  | XPATH_VALID_URL        | "all"     || true     // 2 of 2
        "1"  | XPATH_VALID_URL        | ">0"      || true
        "1"  | XPATH_VALID_URL        | "1"       || false
        "1"  | XPATH_VALID_URL        | "0"       || false
        "2"  | XPATH_MIXED_VALID_URL  | "all"     || false    // 1 of 2
        "2"  | XPATH_MIXED_VALID_URL  | ">0"      || true
        "2"  | XPATH_MIXED_VALID_URL  | "1"       || false
        "2"  | XPATH_MIXED_VALID_URL  | "0"       || false
        "3"  | XPATH_SINGLE_VALID_URL | "all"     || true     // 1 of 1
        "3"  | XPATH_SINGLE_VALID_URL | ">0"      || true
        "3"  | XPATH_SINGLE_VALID_URL | "1"       || true
        "3"  | XPATH_SINGLE_VALID_URL | "0"       || false
        "4"  | XPATH_INVALID_URL      | "all"     || false    // 0 of 2
        "4"  | XPATH_INVALID_URL      | ">0"      || false
        "4"  | XPATH_INVALID_URL      | "1"       || false
        "4"  | XPATH_INVALID_URL      | "0"       || true
        "5"  | XPATH_NON_EXISTENT     | "all"     || false    // xpath returns 0 results
        "5"  | XPATH_NON_EXISTENT     | ">0"      || false
        "5"  | XPATH_NON_EXISTENT     | "1"       || false
        "5"  | XPATH_NON_EXISTENT     | "0"       || true
    }

}
