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 XMLFieldExistsRuleSpecification extends Specification {

    static final String XPATH_ELEMENT_TEXT_MATCHES_THREE = "//node/attr[@id='title']/text()"
    static final String XPATH_ELEMENT_TEXT_1_OF_2        = "//attr[@id='1of2_exists']/text()"
    static final String XPATH_ELEMENT_TEXT_MATCHES_ONE   = "/node/attr[@id='title']/text()"
    static final String XPATH_ELEMENT_TEXT_0_OF_1        = "//node/attr[@id='0of1_exists']/text()"
    static final String XPATH_ATTR_1_OF_1                = "//node/attr/@name"
    static final String XPATH_ATTR_0_OF_1                = "//node/attr/@noName"
    static final String XPATH_MATCHES_NONE               = "//nonexistent_field"

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

    def "Builder works"() {
        given:
        def id = "1"
        def xpath = XPATH_ELEMENT_TEXT_MATCHES_THREE
        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(XPATH_ELEMENT_TEXT_MATCHES_ONE).
                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
//        ""   | ""              | ""        || IllegalArgumentException
    }

    @Unroll
    def "Input xpathExpression: #xpathExpression, with traversal: #traversal, results in #result"() {
        given:
        Document doc = XMLHelper.parse("/eu/dnetlib/validator2/engine/xml-field-exists.xml")
        assert doc != null

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

        then:
        notThrown(IllegalStateException)
        rule1.test(doc) == result

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

        then:
        notThrown(IllegalStateException)
        rule2.test(doc) == result

        where: "nodes returned are either text value of element or attribute"
        id  | xpathExpression                  | traversal || result
        "1" | XPATH_ELEMENT_TEXT_MATCHES_THREE | "all"     || true
        "1" | XPATH_ELEMENT_TEXT_MATCHES_THREE | ">0"      || true
        "1" | XPATH_ELEMENT_TEXT_MATCHES_THREE | "1"       || false
        "1" | XPATH_ELEMENT_TEXT_MATCHES_THREE | "0"       || false
        "2" | XPATH_ELEMENT_TEXT_1_OF_2        | "all"     || false
        "2" | XPATH_ELEMENT_TEXT_1_OF_2        | ">0"      || true
        "2" | XPATH_ELEMENT_TEXT_1_OF_2        | "1"       || false
        "2" | XPATH_ELEMENT_TEXT_1_OF_2        | "0"       || false
        "3" | XPATH_ELEMENT_TEXT_MATCHES_ONE   | "all"     || true
        "3" | XPATH_ELEMENT_TEXT_MATCHES_ONE   | ">0"      || true
        "3" | XPATH_ELEMENT_TEXT_MATCHES_ONE   | "1"       || true
        "3" | XPATH_ELEMENT_TEXT_MATCHES_ONE   | "0"       || false
        "4" | XPATH_ELEMENT_TEXT_0_OF_1        | "all"     || false
        "4" | XPATH_ELEMENT_TEXT_0_OF_1        | ">0"      || false
        "4" | XPATH_ELEMENT_TEXT_0_OF_1        | "1"       || false
        "4" | XPATH_ELEMENT_TEXT_0_OF_1        | "0"       || true
        "5" | XPATH_ATTR_1_OF_1                | "all"     || true
        "5" | XPATH_ATTR_1_OF_1                | ">0"      || true
        "5" | XPATH_ATTR_1_OF_1                | "1"       || true
        "5" | XPATH_ATTR_1_OF_1                | "0"       || false
        "6" | XPATH_ATTR_0_OF_1                | "all"     || false
        "6" | XPATH_ATTR_0_OF_1                | ">0"      || false
        "6" | XPATH_ATTR_0_OF_1                | "1"       || false
        "6" | XPATH_ATTR_0_OF_1                | "0"       || true
        "7" | XPATH_MATCHES_NONE               | "all"     || false
        "7" | XPATH_MATCHES_NONE               | ">0"      || false
        "7" | XPATH_MATCHES_NONE               | "1"       || false
        "7" | XPATH_MATCHES_NONE               | "0"       || true    // Altered implementation to cover case
    }

}
