package eu.dnetlib.validator2.validation.guideline.openaire;

import eu.dnetlib.validator2.engine.Helper;
import eu.dnetlib.validator2.engine.Rule;
import eu.dnetlib.validator2.engine.Status;
import eu.dnetlib.validator2.engine.builtins.StandardXMLContext;
import eu.dnetlib.validator2.engine.builtins.XMLCardinalityRule;
import eu.dnetlib.validator2.engine.builtins.XMLRule;
import eu.dnetlib.validator2.validation.StandardValidationResult;
import eu.dnetlib.validator2.validation.XMLApplicationProfile;
import eu.dnetlib.validator2.validation.guideline.ElementSpec;
import eu.dnetlib.validator2.validation.guideline.Guideline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public abstract class AbstractOpenAireProfile implements XMLApplicationProfile, Serializable {

    private static final Logger logger = LoggerFactory.getLogger(AbstractOpenAireProfile.class);


    private final String name;

    public AbstractOpenAireProfile(String name) {
        this.name = name;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public ValidationResult validate(String id, Document document) {
        int maxScore = maxScore();
        double score = 0;
        final Map<String, Guideline.Result> results = new HashMap<>();

        // Validate the document against all guideline-elements.
        for ( Guideline<Document> guidelineElement : guidelines() )
        {
            String guidelineElementName = guidelineElement.getName();
            if ( logger.isTraceEnabled() )
                logger.trace("Evaluating guideline-element: " + guidelineElementName);
            Guideline.Result result = guidelineElement.validate(document);
            results.put(guidelineElementName, result);

            score += (result.getStatus() == Status.SUCCESS ? result.getScore() : 0);
            logger.debug("Score after validating \"" + guidelineElementName + "\" = " + score);
        }

        double percentScore = (score / maxScore) * 100;

        return new StandardValidationResult(id, percentScore, results);
    }

    static Rule<Document> elementIsPresent(String elementName) {
        return XMLCardinalityRule
                .builder()
                .setId(ElementSpec.APPLICABILITY_RULE_ID)
                .setXPathExpression("//*[name()='" + elementName + "']")
                .setIsInclusive(true).setRange(1, Long.MAX_VALUE)
                .build();
    }

    static Rule<Document> elementIsPresentAndHasAttributeWithValue(String elementName,
                                                                   String attrName,
                                                                   String attrValue) {
        StandardXMLContext context = new StandardXMLContext();
        context.getIdProperty().setValue(ElementSpec.APPLICABILITY_RULE_ID);
        context.getNodeListActionProperty().setValue("custom");
        context.getXPathExpressionProperty().setValue("//*[name()='" + elementName + "']");
        return new XMLRule<>(context, (NodeList nodes) -> {
            // The nodes contain all the elementName nodes
            int len = nodes.getLength();
            if (len == 0) { return false; } // element is not present
            for ( int i = 0; i < len; i++ ) {
                Node elem = nodes.item(i);
                String value = Helper.getAttributeValue(elem, attrName);
                if ( !attrValue.equals(value) ) { return false; }
            }
            return true;
        });
    }

}
