/**
   * Checks if the specified piece of evidence is relevant for the supplied trustor. More
   * specifically, a piece of evidence is relevant for indirect trust evaluation if:
   *
   * <ol>
   *   <li>type == {@link TrustEvidenceType#DIRECTLY_TRUSTED DIRECTLY_TRUSTED}
   *   <li>trustorId != evidence.subjectId, i.e. ignore trust opinions originating <i>from</i> the
   *       trustor
   *   <li>trustorId != evidence.objectId, i.e. ignore trust opinions <i>about</i> the trustor
   *   <li>evidence.subjectId != evidence.objectId, i.e. ignore self-referencing trust opinions
   * </ol>
   *
   * @param trustorId
   * @param evidence
   * @return <code>true</code> if the specified piece of evidence is relevant for the supplied
   *     trustor; <code>false</code> otherwise.
   */
  private boolean areRelevant(final TrustedEntityId trustorId, final ITrustEvidence evidence) {

    boolean result = false;

    if (TrustEvidenceType.DIRECTLY_TRUSTED == evidence.getType()
        && !trustorId.equals(evidence.getSubjectId())
        && !trustorId.equals(evidence.getObjectId())
        && !evidence.getSubjectId().equals(evidence.getObjectId())) {
      result = true;
    }

    LOG.debug(
        "areRelevant: trustorId={}, evidence={}, result={}",
        new Object[] {trustorId, evidence, result});
    return result;
  }
  private static Set<TrustEvidence> iEvidenceToEvidence(Set<ITrustEvidence> evidenceSet) {

    final Set<TrustEvidence> result = new LinkedHashSet<TrustEvidence>(evidenceSet.size());
    for (final ITrustEvidence evidence : evidenceSet) {
      result.add(
          new TrustEvidence(
              evidence.getSubjectId(),
              evidence.getObjectId(),
              evidence.getType(),
              evidence.getTimestamp(),
              evidence.getInfo(),
              evidence.getSourceId()));
    }

    return result;
  }
  /*
   * @see org.societies.privacytrust.trust.api.engine.IIndirectTrustEngine#evaluate(org.societies.api.privacytrust.trust.model.TrustedEntityId, org.societies.privacytrust.trust.api.evidence.model.ITrustEvidence)
   */
  @Override
  public Set<ITrustedEntity> evaluate(
      final TrustedEntityId trustorId, final ITrustEvidence evidence) throws TrustEngineException {

    if (trustorId == null) {
      throw new NullPointerException("trustorId can't be null");
    }
    if (evidence == null) {
      throw new NullPointerException("evidence can't be null");
    }

    LOG.debug("evaluate: trustorId={}, evidence={}", trustorId, evidence);

    final Set<ITrustedEntity> resultSet = new HashSet<ITrustedEntity>();

    if (!this.areRelevant(trustorId, evidence)) {
      return resultSet;
    }

    try {
      // Does similarity between trustor and subject needs re-evaluation?
      boolean doSimilarityEval = false;
      // Create the trusted entity the evidence object refers to if not already available
      ITrustedEntity trustee =
          (ITrustedEntity) this.trustRepo.retrieveEntity(trustorId, evidence.getObjectId());
      if (trustee == null) {
        trustee = super.trustRepo.createEntity(trustorId, evidence.getObjectId());
      } else {
        doSimilarityEval = true;
      }
      LOG.debug("evaluate: doSimilarity={}", doSimilarityEval);
      resultSet.add(trustee);

      switch (evidence.getType()) {

          // Update value
        case DIRECTLY_TRUSTED:
          // Check if similarity between trustor and subject needs re-evaluation
          if (doSimilarityEval
              && TrustedEntityType.CSS == evidence.getSubjectId().getEntityType()) {
            final ITrustedCss subject =
                (ITrustedCss) super.createEntityIfAbsent(trustorId, evidence.getSubjectId());
            final Double similarity =
                this.trustSimilarityEvaluator.evaluateCosineSimilarity(
                    trustorId, evidence.getSubjectId());
            LOG.debug("evaluate: similarity={}", similarity);
            if (similarity != null && !Double.isNaN(similarity)) {
              subject.setSimilarity(similarity);
              super.trustRepo.updateEntity(subject);
            }
          }
          // Fetch top N users
          final Map<TrustedEntityId, ITrustedCss> topNCssMap = this.retrieveTopNCss(trustorId);
          LOG.debug("evaluate: topNCssMap={}", topNCssMap);
          double weightedOpinionSum = 0d;
          double weightSum = 0d;
          // Retrieve all Indirect Trust Evidence related to the object
          // referenced in the specified TrustEvidence
          final Set<ITrustEvidence> evidenceSet =
              super.trustEvidenceRepo.retrieveLatestEvidence(
                  null, evidence.getObjectId(), evidence.getType(), null);
          LOG.debug("evaluate: evidenceSet={}", evidenceSet);
          for (final ITrustEvidence relatedEvidence : evidenceSet) {
            if (!(relatedEvidence.getInfo() instanceof Double)) {
              LOG.warn("Related evidence " + relatedEvidence + " has no trust value!");
              continue;
            }
            final ITrustedCss opinionSource = topNCssMap.get(relatedEvidence.getSubjectId());
            if (opinionSource == null) {
              LOG.warn(
                  "Could not find CSS trust relationship with related evidence subject '"
                      + relatedEvidence.getSubjectId()
                      + "'");
              continue;
            }
            final Double weight = evaluateWeight(opinionSource);
            Double weightedOpinion = null;
            if (weight != null) {
              final double meanOpinion =
                  this.retrieveMeanTrustOpinion(relatedEvidence.getSubjectId());
              weightedOpinion = weight * ((Double) relatedEvidence.getInfo() - meanOpinion);
              LOG.debug(
                  "evaluate: subjectId={}, meanOpinion={}, weightedOpinion={}",
                  new Object[] {relatedEvidence.getSubjectId(), meanOpinion, weightedOpinion});
            }
            if (weightedOpinion == null) {
              LOG.warn(
                  "Ignoring related evidence " + relatedEvidence + ": Weighted opinion is null");
              continue;
            }
            weightedOpinionSum += weightedOpinion;
            weightSum += Math.abs(weight);
          }
          LOG.debug("evaluate: weightedOpinionSum={}, weightSum={}", weightedOpinionSum, weightSum);
          // t_x,i = avg(t_x) + weighted opinions
          double value =
              super.trustRepo.retrieveMeanTrustValue(trustorId, TrustValueType.DIRECT, null);
          final double confidence;
          if (weightSum > 0) {
            value += weightedOpinionSum / weightSum;
            confidence = 0.5d; // TODO constant or what?
          } else {
            confidence = 0.25d; // TODO constant or what?
          }
          LOG.debug("evaluate: value={}", value);
          if (value > 1) {
            value = 1.0d; // TODO use constant
          } else if (value < 0) {
            value = 0.0d; // TODO use constant
          }
          trustee.getIndirectTrust().setValue(value);
          trustee.getIndirectTrust().setConfidence(confidence);
          break;

        default:
          throw new TrustEngineException("Unsupported type: " + evidence.getType());
      }

      // Add related evidence to trustee
      trustee.addEvidence(evidence);

      // Persist updated TrustedEntities in the Trust Repository
      for (final ITrustedEntity entity : resultSet) {
        super.trustRepo.updateEntity(entity);
      }

    } catch (Exception e) {
      throw new TrustEngineException(
          "Could not evaluate indirect trust evidence " + evidence + ": " + e.getLocalizedMessage(),
          e);
    }

    return resultSet;
  }