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

class XMLVocabularyRuleSpecification extends Specification {

    static final String XPATH1 = "//node/attr[@id='xpath1']/text()"
    static final String XPATH2 = "//node/attr[@id='xpath2']/text()"

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

    def "Builder works"() {
        given:
        def ruleBuilder3 = XMLVocabularyRule.builder()
        def ruleBuilder4 = XMLVocabularyRule.builder()
        def id = "1"
        def xpath = "irrelevant"
        def traversal = "all"
        def terms = "term1,term2,term3"
        def termsType = "whitelist"

        when: "setting each property explicitly and calling build() without optional terms_type prop"
        def rule1 = ruleBuilder1.
                setId(id).
                setXPathExpression(xpath).
                setNodeListAction(traversal).
                setVocabularyTerms(terms).
                build()

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

        when: "building from map containing properties without optional terms_type prop"
        def rule2 = ruleBuilder2.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)         : id,
                (XPathExpressionContext.PROPERTY_NAME) : xpath,
                (NodeListContext.PROPERTY_NAME)        : traversal,
                (VocabularyContext.TERMS_PROPERTY_NAME): terms
        ])

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

        when: "setting each property explicitly and calling build() with optional terms_type prop"
        def rule3 = ruleBuilder3.
                setId(id).
                setXPathExpression(xpath).
                setNodeListAction(traversal).
                setVocabularyTermsAndTermsType(terms, termsType).
                build()

        then:
        ruleContextMatchesProps(rule3.getContext(), id, xpath, traversal, terms, termsType)

        when: "building from map containing properties with optional terms_type prop"
        def rule4 = ruleBuilder4.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)              : id,
                (XPathExpressionContext.PROPERTY_NAME)      : xpath,
                (NodeListContext.PROPERTY_NAME)             : traversal,
                (VocabularyContext.TERMS_PROPERTY_NAME)     : terms,
                (VocabularyContext.TERMS_TYPE_PROPERTY_NAME): termsType
        ])

        then:
        ruleContextMatchesProps(rule4.getContext(), id, xpath, traversal, terms, termsType)
    }

    void ruleContextMatchesProps(context, id, xpath, traversal, terms, termsType) {
        assert context.getIdProperty().getValue() == id
        assert context.getXPathExpressionProperty().getValue() == xpath
        assert context.getNodeListActionProperty().getValue() == traversal
        assert context.getTermsProperty().getValue() == terms
        if (termsType != null) {
            assert context.getTermsTypeProperty().getValue() == termsType
        } else {
            assert context.getTermsTypeProperty().getValue() == "blacklist"
        }
    }

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

        then:
        thrown(IllegalStateException)

        when: "missing NodeListAction property"
        ruleBuilder2.
                setId("1").
                setXPathExpression("irrelevant").
                setVocabularyTermsAndTermsType("term1,term2", "irrelevant").
                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).
                setVocabularyTermsAndTermsType(terms, termsType)

        then:
        thrown(expectedException)

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

        then:
        thrown(expectedException)

        where:
        id   | xpathExpression | traversal | terms | termsType || expectedException
        ""   | "valid"         | ">0"      | "1,2" | "any"     || IllegalArgumentException
        null | "valid"         | ">0"      | "1,2" | "any"     || IllegalArgumentException
        "1"  | "valid"         | ""        | "1,2" | "any"     || IllegalArgumentException
        "1"  | "valid"         | null      | "1,2" | "any"     || IllegalArgumentException
        "1"  | "valid"         | "invalid" | "1,2" | "any"     || IllegalArgumentException
        "1"  | "valid"         | ">0"      | ""    | "any"     || IllegalArgumentException
        "1"  | "valid"         | ">0"      | " , " | "any"     || IllegalArgumentException
        "1"  | "valid"         | ">0"      | ",,, "| "any"     || IllegalArgumentException
        "1"  | "valid"         | ">0"      | null  | "any"     || IllegalArgumentException
        "1"  | "valid"         | ">0"      | "1,2" | ""        || IllegalArgumentException
        "1"  | "valid"         | ">0"      | "1,2" | null      || IllegalArgumentException

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

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

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

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

        when: "reading properties from map"
        def rule2 = ruleBuilder2.buildFrom([
                (RuleContext.ID_PROPERTY_NAME)              : id,
                (XPathExpressionContext.PROPERTY_NAME)      : xpathExpression,
                (NodeListContext.PROPERTY_NAME)             : traversal,
                (VocabularyContext.TERMS_PROPERTY_NAME)     : terms,
                (VocabularyContext.TERMS_TYPE_PROPERTY_NAME): termsType
        ])

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

        where:
        id  | xpathExpression | traversal | terms                 | termsType   || result
        "1" | XPATH1          | "all"     | "tErm1, term2, term3" | "whitelist" || true     // 3 of 3
        "1" | XPATH1          | ">0"      | "term1, teRm2, term3" | "whitelist" || true
        "1" | XPATH1          | "1"       | "term1, term2, terM3" | "whitelist" || false
        "1" | XPATH1          | "0"       | "term1,term2,term3"   | "whitelist" || false
        "2" | XPATH1          | "all"     | "Term1, term2, term3" | "blacklist" || false    // 3 of 3 reverse results
        "2" | XPATH1          | ">0"      | "term1, tErm2, term3" | "blacklist" || false
        "2" | XPATH1          | "1"       | "term1, term2, teRm3" | "blacklist" || true
        "2" | XPATH1          | "0"       | "term1, term2, terM3" | "blacklist" || true
        "3" | XPATH1          | "all"     | "Term1, term2, term4" | "whitelist" || false    // 2 of 3
        "3" | XPATH1          | ">0"      | "Term1,tErm2, term4"  | "whitelist" || true
        "3" | XPATH1          | "1"       | "term1, terM2 ,term4" | "whitelist" || false
        "3" | XPATH1          | "0"       | "Term1, terM2, term4" | "whitelist" || false
        "4" | XPATH1          | "all"     | "Term1, term2, term4" | "blacklist" || true     // 2 of 3 reverse results
        "4" | XPATH1          | ">0"      | "Term1,tErm2, term4"  | "blacklist" || false
        "4" | XPATH1          | "1"       | "term1, terM2 ,term4" | "blacklist" || true
        "4" | XPATH1          | "0"       | "Term1, terM2, term4" | "blacklist" || true
        "5" | XPATH1          | "all"     | "Term1, "             | "whitelist" || false    // 1 of 3
        "5" | XPATH1          | ">0"      | "tErm1, "             | "whitelist" || true
        "5" | XPATH1          | "1"       | "teRm1, ,"            | "whitelist" || false
        "5" | XPATH1          | "0"       | "terM1,, "            | "whitelist" || false
        "6" | XPATH1          | "all"     | "Term1, "             | "blacklist" || true     // 1 of 3 reverse results
        "6" | XPATH1          | ">0"      | "tErm1, "             | "blacklist" || false
        "6" | XPATH1          | "1"       | "teRm1, ,"            | "blacklist" || true
        "6" | XPATH1          | "0"       | "terM1,, "            | "blacklist" || true
        "7" | XPATH2          | "all"     | ",Term2, term6"       | "whitelist" || true     // 1 of 1
        "7" | XPATH2          | ">0"      | ",,tErm2, term6"      | "whitelist" || true
        "7" | XPATH2          | "1"       | ", ,, teRm2, term6"   | "whitelist" || true
        "7" | XPATH2          | "0"       | "terM2, term6"        | "whitelist" || false
        "8" | XPATH2          | "all"     | ",Term2, term6"       | "blacklist" || false    // 1 of 1 reverse results
        "8" | XPATH2          | ">0"      | ",,tErm2, term6"      | "blacklist" || false
        "8" | XPATH2          | "1"       | ", ,, teRm2, term6"   | "blacklist" || false
        "8" | XPATH2          | "0"       | "terM2, term6"        | "blacklist" || true
        "9" | XPATH2          | "all"     | "term5, term6"        | "whitelist" || false    // 0 of 1
        "9" | XPATH2          | ">0"      | "term5, term6"        | "whitelist" || false
        "9" | XPATH2          | "1"       | "term5, term6"        | "whitelist" || false
        "9" | XPATH2          | "0"       | "term5, term6"        | "whitelist" || true
        "10"| XPATH2          | "all"     | "term5, term6"        | "blacklist" || true     // 0 of 1 reverse results
        "10"| XPATH2          | ">0"      | "term5, term6"        | "blacklist" || true
        "10"| XPATH2          | "1"       | "term5, term6"        | "blacklist" || true
        "10"| XPATH2          | "0"       | "term5, term6"        | "blacklist" || false
        "11"| "non-existent"  | "all"     | "term5, term6"        | "whitelist" || false    // xpath returns 0 nodes
        "11"| "non-existent"  | ">0"      | "term5, term6"        | "whitelist" || false
        "11"| "non-existent"  | "1"       | "term5, term6"        | "whitelist" || false
        "11"| "non-existent"  | "0"       | "term5, term6"        | "whitelist" || true
        "12"| "non-existent"  | "all"     | "term5, term6"        | "blacklist" || true     // xpath returns 0 nodes
        "12"| "non-existent"  | ">0"      | "term5, term6"        | "blacklist" || true
        "12"| "non-existent"  | "1"       | "term5, term6"        | "blacklist" || true
        "12"| "non-existent"  | "0"       | "term5, term6"        | "blacklist" || false
    }

}
