@Override
  public EncounterQueryResult evaluate(EncounterQuery definition, EvaluationContext context)
      throws EvaluationException {
    context = ObjectUtil.nvl(context, new EvaluationContext());

    BasicEncounterQuery query = (BasicEncounterQuery) definition;
    EncounterQueryResult result = new EncounterQueryResult(query, context);

    if (context.getBaseCohort() != null && context.getBaseCohort().isEmpty()) {
      return result;
    }
    if (context instanceof EncounterEvaluationContext) {
      EncounterIdSet baseEncounters = ((EncounterEvaluationContext) context).getBaseEncounters();
      if (baseEncounters != null && baseEncounters.getMemberIds().isEmpty()) {
        return result;
      }
    }

    HqlQueryBuilder q = new HqlQueryBuilder();
    q.select("e.encounterId");
    q.from(Encounter.class, "e");
    q.whereIn("e.encounterType", query.getEncounterTypes());
    q.whereIn("e.form", query.getForms());
    q.whereGreaterOrEqualTo("e.encounterDatetime", query.getOnOrAfter());
    q.whereLessOrEqualTo("e.encounterDatetime", query.getOnOrBefore());
    q.whereIn("e.location", query.getLocationList());
    q.whereEncounterIn("e.encounterId", context);

    if (query.getWhich() == null || query.getWhich() == TimeQualifier.ANY) {
      List<Integer> results = evaluationService.evaluateToList(q, Integer.class);
      result.getMemberIds().addAll(results);
    } else {
      q.innerJoin("e.patient", "p");
      q.select("p.patientId");
      if (query.getWhich() == TimeQualifier.LAST) {
        q.orderDesc("e.encounterDatetime");
      } else {
        q.orderAsc("e.encounterDatetime");
      }

      ListMap<Integer, Integer> foundEncountersForPatients = new ListMap<Integer, Integer>();
      int maxNumPerPatient = ObjectUtil.nvl(query.getWhichNumber(), 1);
      for (Object[] row : evaluationService.evaluateToList(q)) {
        Integer encounterId = (Integer) row[0];
        Integer patientId = (Integer) row[1];
        foundEncountersForPatients.putInList(patientId, encounterId);
        if (foundEncountersForPatients.get(patientId).size() <= maxNumPerPatient) {
          result.add(encounterId);
        }
      }
    }

    return result;
  }
  /** @should return person data for each obs in the passed context */
  @Override
  public EvaluatedObsData evaluate(ObsDataDefinition definition, EvaluationContext context)
      throws EvaluationException {

    EvaluatedObsData c = new EvaluatedObsData(definition, context);

    // create a map of obs ids -> patient ids

    HqlQueryBuilder q = new HqlQueryBuilder();
    q.select("o.obsId", "o.personId");
    q.from(Obs.class, "o");
    q.whereObsIn("o.obsId", context);

    Map<Integer, Integer> convertedIds =
        evaluationService.evaluateToMap(q, Integer.class, Integer.class, context);

    if (!convertedIds.keySet().isEmpty()) {
      // create a new (person) evaluation context using the retrieved ids
      PersonEvaluationContext personEvaluationContext = new PersonEvaluationContext();
      personEvaluationContext.setBasePersons(
          new PersonIdSet(new HashSet<Integer>(convertedIds.values())));

      // evaluate the joined definition via this person context
      PersonToObsDataDefinition def = (PersonToObsDataDefinition) definition;
      EvaluatedPersonData pd =
          Context.getService(PersonDataService.class)
              .evaluate(def.getJoinedDefinition(), personEvaluationContext);

      // now create the result set by mapping the results in the person data set to obs ids
      for (Integer obsId : convertedIds.keySet()) {
        c.addData(obsId, pd.getData().get(convertedIds.get(obsId)));
      }
    }
    return c;
  }
  /**
   * @see PersonDataEvaluator#evaluate(PersonDataDefinition, EvaluationContext)
   * @should return the most preferred name for each person in the passed context
   * @should return empty result set for an empty base cohort
   * @should return the preferred name for all persons
   */
  public EvaluatedPersonData evaluate(PersonDataDefinition definition, EvaluationContext context)
      throws EvaluationException {
    EvaluatedPersonData c = new EvaluatedPersonData(definition, context);

    if (context.getBaseCohort() != null && context.getBaseCohort().isEmpty()) {
      return c;
    }

    HqlQueryBuilder q = new HqlQueryBuilder();
    q.select("pn.person.personId", "pn");
    q.from(PersonName.class, "pn");
    q.wherePersonIn("pn.person.personId", context);
    q.orderAsc("pn.preferred");

    Map<Integer, Object> data = evaluationService.evaluateToMap(q, Integer.class, Object.class);
    c.setData(data);

    return c;
  }
  /**
   * @see CohortDefinitionEvaluator#evaluate(CohortDefinition, EvaluationContext)
   * @should return patients in the specified states before the start date
   * @should return patients in the specified states after the start date
   * @should return patients in the specified states before the end date
   * @should return patients in the specified states after the end date
   * @should find patients in specified states on the before start date if passed in time is at
   *     midnight
   * @should find patients in specified states on the before end date if passed in time is at
   *     midnight
   */
  public EvaluatedCohort evaluate(CohortDefinition cohortDefinition, EvaluationContext context) {

    PatientStateCohortDefinition def = (PatientStateCohortDefinition) cohortDefinition;

    HqlQueryBuilder qb = new HqlQueryBuilder();
    qb.select("ps.patientProgram.patient.patientId");
    qb.from(PatientState.class, "ps");
    qb.whereIn("ps.state", def.getStates());
    qb.whereGreaterOrEqualTo("ps.startDate", def.getStartedOnOrAfter());
    qb.whereLessOrEqualTo("ps.startDate", def.getStartedOnOrBefore());
    qb.whereGreaterOrEqualTo("ps.endDate", def.getEndedOnOrAfter());
    qb.whereLessOrEqualTo("ps.endDate", def.getEndedOnOrBefore());
    qb.whereIn("ps.patientProgram.location", def.getLocationList());
    qb.wherePatientIn("ps.patientProgram.patient.patientId", context);

    List<Integer> pIds = evaluationService.evaluateToList(qb, Integer.class, context);

    return new EvaluatedCohort(new Cohort(pIds), cohortDefinition, context);
  }
  /**
   * @see PersonDataEvaluator#evaluate(PersonDataDefinition, EvaluationContext)
   * @should return the vital status by person
   */
  public EvaluatedPersonData evaluate(PersonDataDefinition definition, EvaluationContext context)
      throws EvaluationException {
    EvaluatedPersonData c = new EvaluatedPersonData(definition, context);

    HqlQueryBuilder q = new HqlQueryBuilder();
    q.select("p.personId", "p.dead", "p.deathDate", "cod");
    q.from(Person.class, "p");
    q.leftOuterJoin("p.causeOfDeath", "cod");
    q.wherePersonIn("p.personId", context);

    List<Object[]> results = evaluationService.evaluateToList(q, context);
    for (Object[] row : results) {
      Integer pId = (Integer) row[0];
      boolean dead = (row[1] == Boolean.TRUE);
      Date deathDate = (dead ? (Date) row[2] : null);
      Concept causeOfDeath = (dead ? (Concept) row[3] : null);
      c.addData(pId, new VitalStatus(dead, deathDate, causeOfDeath));
    }

    return c;
  }