/** @author Jeremy Debattista */
public class DigitalSignatureUsage implements QualityMetric {

  private static final Resource ENDORSEMENT =
      ModelFactory.createDefaultModel().createResource("http://xmlns.com/wot/0.1/Endorsement");
  private static final Property ASSURANCE =
      ModelFactory.createDefaultModel().createProperty("http://xmlns.com/wot/0.1/assurance");
  private static final Property ENDORSER =
      ModelFactory.createDefaultModel().createProperty("http://xmlns.com/wot/0.1/endorser");

  private HTreeMap<String, DigitalSignature> docs =
      MapDbFactory.createFilesystemDB().createHashMap("dig-sig-docs").make();
  private HTreeMap<String, DigitalSignature> endorsements =
      MapDbFactory.createFilesystemDB().createHashMap("endorcements-docs").make();

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

  @Override
  public void compute(Quad quad) {
    logger.debug("Computing : {} ", quad.asTriple().toString());

    Node subject = quad.getSubject();
    Node object = quad.getObject();

    if (quad.getObject().equals(FOAF.Document.asNode())) {
      docs.putIfAbsent(subject.toString(), new DigitalSignature());
    }

    if (quad.getPredicate().equals(ASSURANCE.asNode())) {
      DigitalSignature ds;
      if (endorsements.containsKey(object.getURI())) {
        ds = endorsements.get(object.getURI());
      } else {
        ds = new DigitalSignature();
        ds.endorcement = object.getURI();
      }

      ds.assurance = subject.getURI();

      docs.put(subject.toString(), ds);
      endorsements.put(object.getURI(), ds);
    }

    if (quad.getPredicate().equals(ENDORSER.asNode())) {
      DigitalSignature ds;
      if (endorsements.containsKey(object.getURI())) {
        ds = endorsements.get(object.getURI());
      } else {
        ds = new DigitalSignature();
        ds.endorcement = subject.getURI();
      }

      ds.endorcer = object.getURI();
    }

    if (quad.getObject().equals(ENDORSEMENT.asNode())) {
      DigitalSignature ds = new DigitalSignature();
      ds.endorcement = subject.getURI();

      endorsements.putIfAbsent(subject.getURI(), ds);
    }
  }

  @Override
  public double metricValue() {
    double noDocs = this.docs.size();
    double noDocsWithoutEndorcement = 0.0;

    for (DigitalSignature ds : this.docs.values())
      noDocsWithoutEndorcement += (ds.fullEndorcement()) ? 1 : 0;

    statsLogger.info(
        "DigitalSignatureUsage. Dataset: {} - Total # Documents : {}; # Documents without Endorcement : {};",
        EnvironmentProperties.getInstance().getDatasetURI(),
        noDocs,
        noDocsWithoutEndorcement);

    return (noDocsWithoutEndorcement / noDocs);
  }

  @Override
  public Resource getMetricURI() {
    return DQM.DigitalSignatureUsage;
  }

  @Override
  public ProblemList<?> getQualityProblems() {
    return null;
  }

  @Override
  public boolean isEstimate() {
    return false;
  }

  @Override
  public Resource getAgentURI() {
    return DQM.LuzzuProvenanceAgent;
  }

  private class DigitalSignature implements Serializable {

    /** */
    private static final long serialVersionUID = -2185956592313988605L;

    private String endorcement = null;
    private String endorcer = null;
    private String assurance = null;

    public boolean fullEndorcement() {
      if ((endorcement == null) || (endorcer == null) || (assurance == null)) return false;
      return true;
    }

    @Override
    public int hashCode() {
      return endorcement.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof DigitalSignature) {
        DigitalSignature other = (DigitalSignature) obj;
        return this.endorcement.equals(other.endorcement);
      }
      return false;
    }
  }
}
/**
 * This metric is based on the metric defined by Hogan et al. Weaving the Pedantic Web. This metric
 * checks if the assessed dataset has a defined classed placed in the triple's predicate and defined
 * property in the object position. If an undefined class or property is used, then it is ignored
 *
 * <p>Best Case : 1 Worst Case : 0
 *
 * @author Jeremy Debattista
 */
public class MisplacedClassesOrProperties implements QualityMetric {

  private final Resource METRIC_URI = DQM.MisplacedClassesOrPropertiesMetric;
  private static Logger logger = LoggerFactory.getLogger(MisplacedClassesOrProperties.class);

  private HTreeMap<String, Boolean> seenProperties =
      MapDbFactory.createFilesystemDB()
          .createHashMap("misplaced-classes-seenProperties")
          .makeOrGet();
  private HTreeMap<String, Boolean> seenClasses =
      MapDbFactory.createFilesystemDB().createHashMap("misplaced-classes-seenClasses").makeOrGet();

  private double misplacedClassesCount = 0.0;
  private double totalClassesCount = 0.0;
  private double misplacedPropertiesCount = 0.0;
  private double totalPropertiesCount = 0.0;
  private List<Model> problemList = new ArrayList<Model>();

  public void compute(Quad quad) {
    //		logger.debug("Assessing {}", quad.asTriple().toString());

    Node predicate = quad.getPredicate(); // retrieve predicate
    Node object = quad.getObject(); // retrieve object

    // checking if classes are found in the property position
    //		logger.info("Is the used predicate {} actually a class?", predicate.getURI());
    this.totalPropertiesCount++;
    if (seenProperties.containsKey(predicate.toString())) {
      if (!(seenProperties.get(predicate.toString()))) {
        this.misplacedPropertiesCount++;
        this.createProblemModel(quad.getSubject(), predicate, DQM.MisplacedClass);
      }
    } else {
      if ((VocabularyLoader.isClass(predicate)) && (VocabularyLoader.checkTerm(predicate))) {
        this.misplacedPropertiesCount++;
        this.createProblemModel(quad.getSubject(), predicate, DQM.MisplacedClass);
        seenProperties.put(predicate.toString(), false);
      }
      seenProperties.put(predicate.toString(), true);
    }

    // checking if properties are found in the object position
    if ((object.isURI())
        && (predicate.getURI().equals(RDF.type.getURI()))
        && (VocabularyLoader.checkTerm(object))) {
      //			logger.info("Checking {} for misplaced class", object.getURI());
      this.totalClassesCount++;
      if (seenClasses.containsKey(object.toString())) {
        if (!(seenClasses.get(object.toString()))) {
          this.misplacedClassesCount++;
          this.createProblemModel(quad.getSubject(), object, DQM.MisplacedProperty);
        }
      } else {
        if (VocabularyLoader.isProperty(object)) {
          this.misplacedClassesCount++;
          this.createProblemModel(quad.getSubject(), object, DQM.MisplacedProperty);
          seenClasses.put(object.toString(), false);
        }
        seenClasses.put(object.toString(), true);
      }
    }
  }

  private void createProblemModel(Node resource, Node classOrProperty, Resource type) {
    Model m = ModelFactory.createDefaultModel();

    Resource subject = m.createResource(resource.toString());
    m.add(new StatementImpl(subject, QPRO.exceptionDescription, type));

    if (type.equals(DQM.MisplacedClass))
      m.add(new StatementImpl(subject, DQM.hasMisplacedClass, m.asRDFNode(classOrProperty)));
    else m.add(new StatementImpl(subject, DQM.hasMisplacedProperty, m.asRDFNode(classOrProperty)));

    this.problemList.add(m);
  }

  /**
   * This method computes metric value for the object of this class.
   *
   * @return (total number of undefined classes or properties) / (total number of classes or
   *     properties)
   */
  public double metricValue() {
    logger.info("Number of Misplaced Classes: {}", this.misplacedClassesCount);
    logger.info("Number of Misplaced Properties: {}", this.misplacedPropertiesCount);

    double metricValue = 1.0;

    double misplaced = this.misplacedPropertiesCount + this.misplacedPropertiesCount;
    if (misplaced > 0.0)
      metricValue = 1.0 - (misplaced / (this.totalPropertiesCount + this.totalClassesCount));

    logger.info("Metric Value: {}", metricValue);
    return metricValue;
  }

  /**
   * Returns Metric URI
   *
   * @return metric URI
   */
  public Resource getMetricURI() {
    return this.METRIC_URI;
  }

  /**
   * Returns list of problematic Quads
   *
   * @return list of problematic quads
   */
  public ProblemList<?> getQualityProblems() {
    ProblemList<Model> tmpProblemList = null;
    try {
      if (this.problemList != null && this.problemList.size() > 0) {
        tmpProblemList = new ProblemList<Model>(this.problemList);
      } else {
        tmpProblemList = new ProblemList<Model>();
      }
    } catch (ProblemListInitialisationException problemListInitialisationException) {
      logger.error(problemListInitialisationException.getMessage());
    }
    return tmpProblemList;
  }

  /* (non-Javadoc)
   * @see de.unibonn.iai.eis.luzzu.assessment.QualityMetric#isEstimate()
   */
  @Override
  public boolean isEstimate() {
    return false;
  }

  /* (non-Javadoc)
   * @see de.unibonn.iai.eis.luzzu.assessment.QualityMetric#getAgentURI()
   */
  @Override
  public Resource getAgentURI() {
    return DQM.LuzzuProvenanceAgent;
  }
}