package eu.dnetlib.validator2.validation.guideline;

import eu.dnetlib.validator2.engine.Helper;
import eu.dnetlib.validator2.engine.Rule;
import eu.dnetlib.validator2.engine.RuleContext;
import eu.dnetlib.validator2.engine.RuleEvaluationException;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

public final class SyntheticGuideline extends AbstractGuideline<Document> {

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SyntheticGuideline.class);

    private CompilationResult compilationResult;
    private static final ThreadLocal<GuidelineEvaluation> evaluation = new ThreadLocal<>();


    private SyntheticGuideline(String name, int weight) {
        super(name, weight);
    }

    private SyntheticGuideline(String name, String description, String link, int weight, RequirementLevel requirementLevel) {
        super(name, description, link, weight, requirementLevel);
    }

    private SyntheticGuideline(String name, String description, String link, String fairPrinciples, int weight, RequirementLevel requirementLevel) {
        super(name, description, link, fairPrinciples, weight, requirementLevel);
    }

    @Override
    public Result validate(String id, Document document) {
        logger.debug("Validating document {} in {}", id, Thread.currentThread());
        evaluation.set(new GuidelineEvaluation(id, document, getWeight()));
        Result result = evaluation.get().evaluate(compilationResult);
        logger.debug("Evaluated: {} in thread_{} with result {}", evaluation.get(), Thread.currentThread().getName(), result);
        evaluation.remove();
        return result;
    }

    public static SyntheticGuideline of(String name, int weight, ElementSpec elementSpec) {
        String canonical = Helper.ensureNonEmpty(name, () -> new IllegalArgumentException("Name cannot be empty"));

        // Here we define a guideline with a specific elementSpec as origin, we cannot allow null
        if ( elementSpec == null )
            throw new IllegalArgumentException("ElementSpec cannot be empty");

        if ( weight < 0 )
            throw new IllegalArgumentException("Weight cannot be negative");

        SyntheticGuideline guideline = new SyntheticGuideline(canonical, weight);
        guideline.compilationResult = new ElementSpecCompiler().compile(elementSpec, evaluation::get);
        return guideline;
    }

    // Note: the synthetic rules that will be created are completely independent of each other
    public static SyntheticGuideline of(String name, int weight, RequirementLevel requirementLevel, Rule<Document> rule) {
        String canonical = Helper.ensureNonEmpty(name, () -> new IllegalArgumentException("Name cannot be empty"));

        if ( weight < 0 )
            throw new IllegalArgumentException("Weight cannot be negative");

        if ( requirementLevel == null )
            throw new IllegalArgumentException("Requirement level cannot be empty");

        if ( rule == null )
            throw new IllegalArgumentException("Rule cannot be empty");

        SyntheticGuideline guideline = new SyntheticGuideline(canonical, weight);
        CompilationResult compilationResult = new CompilationResult();
        compilationResult.rootNodeRule = new SyntheticRule<Document>() {
            @Override
            public SyntheticRule<Document> parentRule() {
                return null;
            }

            @Override
            public Rule<Document> applicabilityRule() {
                return null;
            }

            @Override
            public <C extends RuleContext> C getContext() {
                return rule.getContext();
            }

            @Override
            public boolean test(Document document) throws RuleEvaluationException {
                return rule.test(document);
            }
        };
        compilationResult.ruleIdToRequirementLevel.put(rule.getContext().getIdProperty().getValue(), requirementLevel);
        guideline.compilationResult = compilationResult;
        return guideline;
    }


    public static SyntheticGuideline of(String name, String description, String link, int weight, RequirementLevel requirementLevel, ElementSpec elementSpec) {

        String canonicalName = Helper.ensureNonEmpty(name, () -> new IllegalArgumentException("Name cannot be empty"));
        String canonicalDescription = Helper.ensureNonEmpty(description, () -> new IllegalArgumentException("Description cannot be empty"));
        String canonicalLink = Helper.ensureNonEmpty(link, () -> new IllegalArgumentException("Link cannot be empty"));

        // Here we define a guideline with a specific elementSpec as origin, we cannot allow null
        if ( weight < 0 )
            throw new IllegalArgumentException("Weight cannot be negative");
        else if ( requirementLevel == null )
            throw new IllegalArgumentException("Requirement level cannot be empty");
        else if ( elementSpec == null )
            throw new IllegalArgumentException("ElementSpec cannot be empty");

        SyntheticGuideline guideline = new SyntheticGuideline(canonicalName, canonicalDescription, canonicalLink, weight, requirementLevel);
        guideline.compilationResult = new ElementSpecCompiler().compile(elementSpec, evaluation::get);
        return guideline;
    }


    public static SyntheticGuideline of(String name, String description, String link, String fairPrinciples, int weight, RequirementLevel requirementLevel, ElementSpec elementSpec) {

        String canonicalName = Helper.ensureNonEmpty(name, () -> new IllegalArgumentException("Name cannot be empty"));
        String canonicalDescription = Helper.ensureNonEmpty(description, () -> new IllegalArgumentException("Description cannot be empty"));
        String canonicalLink = Helper.ensureNonEmpty(link, () -> new IllegalArgumentException("Link cannot be empty"));
        String canonicalFairPrinciples = Helper.ensureNonEmpty(fairPrinciples, () -> new IllegalArgumentException("FairPrinciples cannot be empty"));

        // Here we define a guideline with a specific elementSpec as origin, we cannot allow null
        if ( weight < 0 )
            throw new IllegalArgumentException("Weight cannot be negative");
        else if ( requirementLevel == null )
            throw new IllegalArgumentException("Requirement level cannot be empty");
        else if ( elementSpec == null )
            throw new IllegalArgumentException("ElementSpec cannot be empty");

        SyntheticGuideline guideline = new SyntheticGuideline(canonicalName, canonicalDescription, canonicalLink, canonicalFairPrinciples, weight, requirementLevel);
        guideline.compilationResult = new ElementSpecCompiler().compile(elementSpec, evaluation::get);
        return guideline;
    }

}
