package eu.dnetlib.validator2.validation.guideline

import eu.dnetlib.validator2.engine.Rule
import eu.dnetlib.validator2.engine.Status
import eu.dnetlib.validator2.engine.XMLHelper
import eu.dnetlib.validator2.engine.builtins.XMLCardinalityRule
import org.w3c.dom.Document
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import spock.util.concurrent.AsyncConditions

import java.util.concurrent.Executors

class SyntheticGuidelineSpecification extends Specification {

    @Shared
    def xmlCardinalityRule = XMLCardinalityRule.
            builder().
            setId("1").
            setRange(0, 5).
            setXPathExpression("//*[name()='ns:attr' and @id='cardinality-3']").
            build()

    def "Synthetic guideline returns expected Guideline from valid inputs with ElementSpec"() {
        given: "initial values"
        def name = "SynthGuideline name"
        def weight = 10
        def elementName = "testElement"
        def cardinality = Cardinality.ONE
        def elementSpec = Builders.forMandatoryElement(elementName, cardinality).build()

        when: "a SyntheticGuideline is created with these inputs"
        def syntheticGuideline = SyntheticGuideline.of(name, weight, elementSpec)

        then: "results are expected and no errors occur"
        noExceptionThrown()
        syntheticGuideline != null
        syntheticGuideline.getName() == name
        syntheticGuideline.getWeight() == weight

        // compilationResult is tested in ElementSpecCompiler
    }

    def "Synthetic guideline returns expected Guideline from valid inputs with rule"() {
        given: "initial values"
        def name = "SynthGuideline name"
        def weight = 10
        def reqLevel = RequirementLevel.MANDATORY

        when: "a SyntheticGuideline is created with these inputs and a rule"
        def syntheticGuideline = SyntheticGuideline.of(name, weight, reqLevel, xmlCardinalityRule)

        then: "results are expected and no errors occur"
        noExceptionThrown()
        syntheticGuideline != null
        syntheticGuideline.getName() == name
        syntheticGuideline.getWeight() == weight
        syntheticGuideline.compilationResult.rootNodeRule.getContext() == xmlCardinalityRule.getContext()
        syntheticGuideline.compilationResult.nodeRules.size() == 0
    }

    @Unroll
    def "SyntheticGuidelines validates properly against doc: #testDoc"() {
        given: "an elementSpec"
        def elementSpec = Builders.
                forMandatoryElement("testElement", Cardinality.ONE).
                withOptionalAttribute("testAttr").
                build()

        and: "initial values"
        def syntheticGuideline = SyntheticGuideline.of("SynthGuideline name", 5, elementSpec)
        Document doc = XMLHelper.parse("/eu/dnetlib/validator2/validation/guideline/$testDoc")

        when: "validating guideline against a doc"
        def result = syntheticGuideline.validate(doc)

        then: "expected validation result is returned"
        result.status() == resultStatus
        result.score() == resultScore
        result.errors().size() == errors
        result.warnings().size() == warnings
        result.internalError() == null

        where:
        testDoc                                              || resultStatus   | resultScore | errors | warnings
        "synthetic-guideline-spec-success.xml"               || Status.SUCCESS | 5           | 0      | 0
        "synthetic-guideline-spec-success-with-warnings.xml" || Status.SUCCESS | 5           | 0      | 1
        "synthetic-guideline-spec-failure.xml"               || Status.FAILURE | 0           | 1      | 0

        //TODO: Need to test for internal error? I assume it would not be that easy...
    }

    @Unroll
    def "SyntheticGuideline validates invalid inputs name: #name, weight: #weight, elementSpec: #elementSpec"() {
        when: "creating SyntheticGuideline with invalid parameters"
        SyntheticGuideline.of(name, weight, elementSpec)

        then: "relevant error occurs"
        def error = thrown(expectedException)
        error.message == expectedMessage

        where:
        name    | weight | elementSpec       || expectedException        | expectedMessage
        ""      | 5      | Mock(ElementSpec) || IllegalArgumentException | "Name cannot be empty"
        null    | 5      | Mock(ElementSpec) || IllegalArgumentException | "Name cannot be empty"
        "valid" | -1     | Mock(ElementSpec) || IllegalArgumentException | "Weight cannot be negative"
        "valid" | 0      | null              || IllegalArgumentException | "ElementSpec cannot be empty"
    }

    @Unroll
    def "SyntheticGuideline validates invalid inputs name: #name, weight: #weight, requirementLevel: #requirementLevel, 1 rule: #rule1"() {
        when: "creating SyntheticGuideline with invalid parameters with a single rule"
        SyntheticGuideline.of(name, weight, requirementLevel, rule)

        then: "relevant error occurs"
        def error = thrown(expectedException)
        error.message == expectedMessage

        where:
        name    | weight | requirementLevel          | rule       || expectedException        | expectedMessage
        ""      | 1      | RequirementLevel.OPTIONAL | Mock(Rule) || IllegalArgumentException | "Name cannot be empty"
        null    | 2      | RequirementLevel.OPTIONAL | Mock(Rule) || IllegalArgumentException | "Name cannot be empty"
        "valid" | -1     | RequirementLevel.OPTIONAL | Mock(Rule) || IllegalArgumentException | "Weight cannot be negative"
        "valid" | 0      | null                      | Mock(Rule) || IllegalArgumentException | "Requirement level cannot be empty"
        "valid" | 3      | RequirementLevel.OPTIONAL | null       || IllegalArgumentException | "Rule cannot be empty"
    }

    def "SyntheticGuidelines can be run by multiple threads"() {

        given:
        int count = 100
        def futures = []
        def conditions = new AsyncConditions(count)
        def executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
        def elementSpec = Builders.
                forMandatoryElement("testElement", Cardinality.ONE).
                withOptionalAttribute("testAttr").
                build()
        def syntheticGuideline = SyntheticGuideline.of("SynthGuideline name", 5, elementSpec)
        Document doc = XMLHelper.parse("/eu/dnetlib/validator2/validation/guideline/synthetic-guideline-spec-success.xml")

        when:
        for(int i = 0; i < count; i++)  {
            def threadMessage = "Thread-$i"
            futures << executor.submit {
                println threadMessage
                Guideline.Result result = syntheticGuideline.validate(doc)
                conditions.evaluate {
                    assert  result.status() == Status.SUCCESS &&
                            result.score() == 5 &&
                            result.errors().size() == 0 &&
                            result.warnings().size() == 0 &&
                            result.internalError() == null
                }
            }
        }

        then:
        futures.size() == count
        conditions.await(2)

        cleanup:
        executor.shutdownNow()
    }

}
