content: package org.aksw.rdfunit.model.impl.shacl;
  
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.ImmutableSet;
  import java.util.Set;
  import java.util.stream.Collectors;
  import lombok.Builder;
  import lombok.EqualsAndHashCode;
  import lombok.Getter;
  import lombok.NonNull;
  import lombok.Value;
  import org.aksw.rdfunit.enums.RLOGLevel;
  import org.aksw.rdfunit.enums.TestAppliesTo;
  import org.aksw.rdfunit.enums.TestGenerationType;
  import org.aksw.rdfunit.model.helper.MessagePrebinding;
  import org.aksw.rdfunit.model.helper.QueryPrebinding;
  import org.aksw.rdfunit.model.impl.ManualTestCaseImpl;
  import org.aksw.rdfunit.model.interfaces.ResultAnnotation;
  import org.aksw.rdfunit.model.interfaces.TestCase;
  import org.aksw.rdfunit.model.interfaces.TestCaseAnnotation;
  import org.aksw.rdfunit.model.interfaces.shacl.Shape;
  import org.aksw.rdfunit.model.interfaces.shacl.ShapePath;
  import org.aksw.rdfunit.model.interfaces.shacl.SparqlConstraint;
  import org.aksw.rdfunit.model.interfaces.shacl.Validator;
  import org.aksw.rdfunit.utils.JenaUtils;
  import org.aksw.rdfunit.vocabulary.SHACL;
  import org.apache.jena.query.Query;
  import org.apache.jena.query.QueryFactory;
  import org.apache.jena.rdf.model.Literal;
  import org.apache.jena.rdf.model.Resource;
  import org.apache.jena.rdf.model.ResourceFactory;
  
  @Builder
  @Value
  @EqualsAndHashCode(exclude = "sparqlWhere")
  public class SparqlConstraintImpl implements SparqlConstraint {
  
    @Getter
    @NonNull
    private final Shape shape;
    @Getter
    @NonNull
    private final Literal message;
    @Getter
    @NonNull
    private final Resource severity;
    @NonNull
    private final Validator validator;
    @Getter(lazy = true)
    @NonNull
    private final String sparqlWhere = generateSparqlWhere(validator.getSparqlQuery());
  
  
    @Override
    public TestCase getTestCase() {
  
      ManualTestCaseImpl.ManualTestCaseImplBuilder testBuilder = ManualTestCaseImpl.builder();
      String sparql;
      sparql = getSparqlWhere();
  
      return testBuilder
          .element(createTestCaseResource())
          .sparqlPrevalence("")
          .sparqlWhere(sparql)
          .prefixDeclarations(validator.getPrefixDeclarations())
          .testCaseAnnotation(generateTestAnnotations(validator.getSparqlQuery()))
          .build();
    }
  
    private String generateSparqlWhere(String sparqlString) {
  
      final String valuePath = shape.getPath()
          .map(ShapePath::asSparqlPropertyPath)
          .map(propertyPath -> " $this " + propertyPath + " $value . ")
          //.orElse(" BIND ($this AS $value) . ");
          .orElse("");
  
      String sparqlWhere = sparqlString
          .substring(sparqlString.indexOf('{') + 1);
      return replaceBindings("{" + valuePath + sparqlWhere);
    }
  
    private String replaceBindings(String sparqlSnippet) {
      QueryPrebinding queryPrebinding = new QueryPrebinding(sparqlSnippet, shape);
      queryPrebinding.validateSparqlQuery();
      return queryPrebinding.applyBindings(ImmutableMap.of());
    }
  
  
    private Literal generateMessage() {
  
      if (shape.getMessage().isPresent()) {
        return shape.getMessage().get();
      } else {
        String messageLanguage = message.getLanguage();
        String message = new MessagePrebinding(this.message.getLexicalForm(), shape).applyBindings();
        if (messageLanguage == null || messageLanguage.isEmpty()) {
          return ResourceFactory.createStringLiteral(message);
        } else {
          return ResourceFactory.createLangLiteral(message, messageLanguage);
        }
      }
    }
  
    // hack for now
    private TestCaseAnnotation generateTestAnnotations(String originalSelectQuery) {
      return new TestCaseAnnotation(
          createTestCaseResource(),
          TestGenerationType.AutoGenerated,
          null,
          TestAppliesTo.Schema, // TODO check
          SHACL.namespace,      // TODO check
          ImmutableSet.of(),
          generateMessage().getLexicalForm(),
          RLOGLevel.ERROR, //FIXME
          createResultAnnotations(originalSelectQuery)
      );
    }
  
    private Set<ResultAnnotation> createResultAnnotations(String originalSelectQuery) {
      String prefixes = validator.getPrefixDeclarations().stream()
          .map(p -> "PREFIX " + p.getPrefix() + ": <" + p.getNamespace() + ">")
          .collect(Collectors.joining("\n"));
  
      String originalSelectClause = validator.getSparqlQuery()
          .substring(0, validator.getSparqlQuery().indexOf('{'));
      String constructedQuery = prefixes + "\n" + originalSelectClause + getSparqlWhere()
  
          .replaceFirst("(?i)\\s*ASK\\s*\\{", "SELECT \\?this WHERE \\{");
  
      try {
        Query query = QueryFactory.create(constructedQuery);
  
        return ResultAnnotationParser.builder()
            .query(query)
            .shape(shape)
            .validator(validator)
            .canBindValueVariable(true)
            .build()
            .getResultAnnotations();
      } catch (Exception e) {
  
        throw new IllegalArgumentException(constructedQuery, e);
      }
  
  
    }
  
    private Resource createTestCaseResource() {
      // FIXME temporary solution until we decide how to build stable unique test uris
      return ResourceFactory.createProperty(JenaUtils.getUniqueIri());
    }
  }
  