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.RegularExpressionContext
import eu.dnetlib.validator2.engine.contexts.XPathExpressionContext
import org.w3c.dom.Document
import spock.lang.Specification
import spock.lang.Unroll

class XMLRegularExpressionRuleSpecification extends Specification {

    static final String REGEX_3_DIGITS = "\\d{3}"
    static final String REGEX_2_DIGITS = "\\d{2}"
    static final String REGEX_HTTP = "^https?://.*\$"

    static final String XPATH_FOR_3DIGITS_REGEX = "//attr[@id='3digits_regex']/text()"
    static final String XPATH_FOR_2DIGITS_REGEX = "//attr[@id='2digits_regex']/text()"
    static final String XPATH_FOR_HTTP_REGEX    = "//attr[@id='http_regex']/text()"
    static final String XPATH_NON_EXISTENT      = "//attr[@id='no_xpath']/text()"

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

    def "Builder works"() {
        given:
        def id = "0"
        def xpath = XPATH_FOR_3DIGITS_REGEX
        def regex = REGEX_3_DIGITS
        def traversal = "all"

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

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

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

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

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

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

        then:
        thrown(IllegalStateException)

        when:
        ruleBuilder2.
                setId("1").
                setRegularExpression(REGEX_HTTP).
                build()

        then:
        thrown(IllegalStateException)
    }

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

        then:
        thrown(expectedException)

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

        then:
        thrown(expectedException)

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

        //TODO: These are validated when set, by XPathExpressionProperty and throw RuntimeException.
        // We could/should ommit them?
//        "1"  | ""              | "valid" | ">0"      || IllegalArgumentException
//        "1"  | null            | "valid" | ">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-regex.xml")
        assert doc != null

        when: "setting each property explicitly before calling build()"
        def rule1 = ruleBuilder1.
                setId(id).
                setXPathExpression(xpathExpression).
                setRegularExpression(regex).
                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,
                (RegularExpressionContext.PROPERTY_NAME): regex,
                (NodeListContext.PROPERTY_NAME)         : traversal
        ])

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

        where:
        id  | xpathExpression         | regex          | traversal || result
        "1" | XPATH_FOR_3DIGITS_REGEX | REGEX_3_DIGITS | "all"     || true    // 1 of 1
        "1" | XPATH_FOR_3DIGITS_REGEX | REGEX_3_DIGITS | ">0"      || true
        "1" | XPATH_FOR_3DIGITS_REGEX | REGEX_3_DIGITS | "1"       || true
        "1" | XPATH_FOR_3DIGITS_REGEX | REGEX_3_DIGITS | "0"       || false
        "2" | XPATH_FOR_HTTP_REGEX    | REGEX_HTTP     | "all"     || true    // 2 of 2
        "2" | XPATH_FOR_HTTP_REGEX    | REGEX_HTTP     | ">0"      || true
        "2" | XPATH_FOR_HTTP_REGEX    | REGEX_HTTP     | "1"       || false
        "2" | XPATH_FOR_HTTP_REGEX    | REGEX_HTTP     | "0"       || false
        "3" | XPATH_FOR_2DIGITS_REGEX | REGEX_2_DIGITS | "all"     || false   // 1 of 2
        "3" | XPATH_FOR_2DIGITS_REGEX | REGEX_2_DIGITS | ">0"      || true
        "3" | XPATH_FOR_2DIGITS_REGEX | REGEX_2_DIGITS | "1"       || false
        "3" | XPATH_FOR_2DIGITS_REGEX | REGEX_2_DIGITS | "0"       || false
        "4" | XPATH_FOR_2DIGITS_REGEX | REGEX_HTTP     | "all"     || false   // 0 of 2
        "4" | XPATH_FOR_2DIGITS_REGEX | REGEX_HTTP     | ">0"      || false
        "4" | XPATH_FOR_2DIGITS_REGEX | REGEX_HTTP     | "1"       || false
        "4" | XPATH_FOR_2DIGITS_REGEX | REGEX_HTTP     | "0"       || true
        "5" | XPATH_NON_EXISTENT      | REGEX_HTTP     | "all"     || false   // xpath returns 0 results
        "5" | XPATH_NON_EXISTENT      | REGEX_HTTP     | ">0"      || false
        "5" | XPATH_NON_EXISTENT      | REGEX_HTTP     | "1"       || false
        "5" | XPATH_NON_EXISTENT      | REGEX_HTTP     | "0"       || true
    }

}
