package eu.dnetlib.validator2.validation.guideline

import eu.dnetlib.validator2.engine.Helper
import eu.dnetlib.validator2.engine.XMLHelper
import eu.dnetlib.validator2.engine.builtins.AlwaysFailRule
import eu.dnetlib.validator2.engine.builtins.AlwaysSucceedRule
import org.w3c.dom.Document
import spock.lang.Shared
import spock.lang.Specification

import static eu.dnetlib.validator2.validation.guideline.Cardinality.ONE
import static eu.dnetlib.validator2.validation.guideline.Cardinality.ONE_TO_N
import static eu.dnetlib.validator2.validation.guideline.RequirementLevel.*

//TODO: Test occurrence
class ElementSpecCompilerSpecification extends Specification {

    private static final String FILE = "/eu/dnetlib/validator2/validation/guideline/xml-element-spec-compiler-spec.xml"

    @Shared
    Document doc = XMLHelper.parse(FILE)
    // def groovyXml = XMLHelper.groovyParse(FILE)

    def elementSpecCompiler = new ElementSpecCompiler()

    def "Compiler generates expected rule for simple ElementSpec"() {
        given:
        def elementSpec = Helper.buildElement(elementName, requirementLevel, cardinality).build()
        def evaluation = new GuidelineEvaluation("test", doc, 1)

        when:
        CompilationResult compilationResult = elementSpecCompiler.compile(elementSpec){ -> evaluation}
        println compilationResult

        then:
        compilationResult.rootNodeRule.test(doc) == result

        // here we validate that the rule evaluates independently of the requirement level (the requirement level
        // is interpreted by the guideline)
        where:
        elementName   | requirementLevel  | cardinality  | result
        "baseElement" | OPTIONAL          | ONE          | true
        "baseElement" | OPTIONAL          | ONE_TO_N     | true
        "notElement"  | OPTIONAL          | ONE          | false
        "notElement"  | OPTIONAL          | ONE_TO_N     | false
        "baseElement" | RECOMMENDED       | ONE          | true
        "baseElement" | RECOMMENDED       | ONE_TO_N     | true
        "notElement"  | RECOMMENDED       | ONE          | false
        "notElement"  | RECOMMENDED       | ONE_TO_N     | false
        "baseElement" | MANDATORY         | ONE          | true
        "baseElement" | MANDATORY         | ONE_TO_N     | true
        "notElement"  | MANDATORY         | ONE          | false
        "notElement"  | MANDATORY         | ONE_TO_N     | false
    }

    def "Compiler generates expected rule for mandatoryIfApplicable ElementSpec"() {
        given:
        def elementSpec = Builders.forMandatoryIfApplicableElement(elementName, ONE, applicabilityRule).build()
        def evaluation = new GuidelineEvaluation("test", doc, 1)

        when:
        CompilationResult compilationResult = elementSpecCompiler.compile(elementSpec) { -> evaluation}
        String id = compilationResult.rootNodeRule.getContext().getIdProperty().getValue()

        then: "the resulting rule is valid"
        verifyAll(compilationResult.rootNodeRule) {
            test(doc) == result
            evaluation.getRequirementLevelOf(id) == dynReqLevel
        }

        // here we validate the behavior of the conditional rule that determines the applicability
        where:
        elementName   |  applicabilityRule       | dynReqLevel    | result
        "baseElement" |  new AlwaysSucceedRule() | MANDATORY      | true
        "baseElement" |  new AlwaysFailRule<>()  | NOT_APPLICABLE | true //this is the default behavior for MA elements
        "notElement"  |  new AlwaysSucceedRule() | MANDATORY      | false
        "notElement"  |  new AlwaysFailRule<>()  | NOT_APPLICABLE | true //this is the default behavior for MA elements
    }

    def "Compiler generates expected rule for simple AttributeSpec"() {
        given:
        def builder = Builders.forMandatoryElement("baseElement", ONE)
        def elementSpec = Helper.addAttribute(builder, attrName, requirementLevel).build()
        def evaluation = new GuidelineEvaluation("test", doc, 1)

        when:
        CompilationResult compilationResult = elementSpecCompiler.compile(elementSpec) { -> evaluation }
        compilationResult.rootNodeRule.test(doc) // We need rules to be evaluated in order, from root to nodes

        then:
        compilationResult.nodeRules[0].test(doc) == result

        // here we validate that the attribute rule evaluates independently of the requirement level (the requirement level
        // is interpreted by the guideline)
        where:
        attrName  | requirementLevel | result
        "attr1"   | OPTIONAL         | true
        "attr2"   | OPTIONAL         | true
        "notAttr" | OPTIONAL         | false
        "attr1"   | RECOMMENDED      | true
        "attr2"   | RECOMMENDED      | true
        "notAttr" | RECOMMENDED      | false
        "attr1"   | MANDATORY        | true
        "attr2"   | MANDATORY        | true
        "notAttr" | MANDATORY        | false
    }

    def "Compiler generates expected rule for mandatoryIfApplicable AttributeSpec"() {
        given:
        def elementSpec = Builders.forMandatoryElement("baseElement", ONE).
                withMandatoryIfApplicableAttribute(attrName, applicabilityRule).
                build()
        def evaluation = new GuidelineEvaluation("test", doc, 1)

        when:
        CompilationResult compilationResult = elementSpecCompiler.compile(elementSpec) { -> evaluation }
        String id = compilationResult.nodeRules[0].getContext().getIdProperty().getValue()

        then: "the resulting rule is valid"
        compilationResult.rootNodeRule.test(doc) // We need rules to be evaluated in order, from root to nodes
        verifyAll (compilationResult.nodeRules[0]){
            test(doc) == result
            evaluation.getRequirementLevelOf(id) == dynReqLevel
        }

        // here we validate the behavior of the conditional rule that determines the applicability
        where:
        attrName  | applicabilityRule        | dynReqLevel    | result
        "attr1"   |  new AlwaysSucceedRule() | MANDATORY      | true
        "attr1"   |  new AlwaysFailRule<>()  | NOT_APPLICABLE | true //this is the default behavior for MA elements
        "notAttr" |  new AlwaysSucceedRule() | MANDATORY      | false
        "notAttr" |  new AlwaysFailRule<>()  | NOT_APPLICABLE | true //this is the default behavior for MA elements

    }

}
