package eu.dnetlib.validator2.engine.builtins

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

class XMLCardinalityRuleSpecification extends Specification {

    static final String XPATH_CARDINALITY_3 = "//*[name()='ns:attr' and @id='cardinality-3']"

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

    def "Builder works"() {
        given:
        def id = "1"
        def xpath = "anything"
        def greaterThan = "1"
        def lessThan = "5"

        when: "setting each property explicitly and calling build()"
        def rule1 = ruleBuilder1.
                setId(id).
                setXPathExpression(xpath).
                setRange(greaterThan, lessThan).
                build()

        then:
        ruleContextMatchesProps(rule1.getContext(), id, xpath, greaterThan, lessThan)

        when: "building from map containing properties"
        def rule2 = ruleBuilder2.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)                 : id,
                (XPathExpressionContext.PROPERTY_NAME)         : xpath,
                (CardinalityContext.GREATER_THAN_PROPERTY_NAME): greaterThan,
                (CardinalityContext.LESS_THAN_PROPERTY_NAME)   : lessThan
        ])

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

    void ruleContextMatchesProps(context, id, xpath, greaterThan, lessThan) {
        assert context.getIdProperty().getValue() == id
        assert context.getXPathExpressionProperty().getValue() == xpath
        assert context.getLowerBoundProperty().getValue() == greaterThan
        assert context.getUpperBoundProperty().getValue() == lessThan
    }

    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, greaterThan: #greaterThan, lessThan: #lessThan"() {
        when: "setting each property explicitly before calling build()"
        ruleBuilder1.
                setId(id).
                setXPathExpression(xpathExpression).
                setRange(greaterThan, lessThan)

        then:
        thrown(expectedException)

        when: "reading properties from map"
        ruleBuilder2.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)                 : id,
                (XPathExpressionContext.PROPERTY_NAME)         : xpathExpression,
                (CardinalityContext.GREATER_THAN_PROPERTY_NAME): greaterThan,
                (CardinalityContext.LESS_THAN_PROPERTY_NAME)   : lessThan
        ])

        then:
        thrown(expectedException)

        where:
        id   | xpathExpression | greaterThan | lessThan || expectedException
        ""   | "valid"         | "1"         | "10"     || IllegalArgumentException
        null | "valid"         | "1"         | "10"     || IllegalArgumentException
        "1"  | "valid"         | ""          | "10"     || IllegalArgumentException
        "1"  | "valid"         | null        | "10"     || IllegalArgumentException
        "1"  | "valid"         | "NaN"       | "10"     || IllegalArgumentException
        "1"  | "valid"         | "1"         | ""       || IllegalArgumentException
        "1"  | "valid"         | "1"         | null     || IllegalArgumentException
        "1"  | "valid"         | "1"         | "NaN"    || IllegalArgumentException

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

    @Unroll
    def "Inputs id: #fields, xpath: #xpathExpression, greaterThan: #greaterThan, lessThan: #lessThan, result in #result"() {
        given:
        Document doc = XMLHelper.parse("/eu/dnetlib/validator2/engine/xml-cardinality.xml")
        assert doc != null

        when: "setting each property explicitly before calling build()"
        def rule1 = ruleBuilder1.
                setId(id).
                setXPathExpression(xpathExpression).
                setRange(greaterThan, lessThan).
                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,
                (CardinalityContext.GREATER_THAN_PROPERTY_NAME): greaterThan,
                (CardinalityContext.LESS_THAN_PROPERTY_NAME)   : lessThan
        ])

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

        where:
        id  | xpathExpression     | greaterThan | lessThan || result
        "1" | XPATH_CARDINALITY_3 | "2"         | "4"      || true
        "1" | XPATH_CARDINALITY_3 | "3"         | "9"      || false
        "1" | XPATH_CARDINALITY_3 | "0"         | "3"      || false
        "1" | XPATH_CARDINALITY_3 | "9"         | "-10"    || false
        "2" | "//non-existent"    | "-1"        | "1"      || true
        "2" | "//non-existent"    | "0"         | "10"     || false
        "2" | "//non-existent"    | "-10"       | "0"      || false
        "2" | "//non-existent"    | "9"         | "-10"    || false

    }

}
