@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;
  }
  /**
   * @see DataSetEvaluator#evaluate(DataSetDefinition, EvaluationContext)
   * @should evaluate an EncounterAndObsDataSetDefinition
   */
  public DataSet evaluate(DataSetDefinition dataSetDefinition, EvaluationContext context)
      throws EvaluationException {

    EncounterAndObsDataSetDefinition definition =
        (EncounterAndObsDataSetDefinition) dataSetDefinition;
    SimpleDataSet dataSet = new SimpleDataSet(dataSetDefinition, context);
    context = ObjectUtil.nvl(context, new EvaluationContext());
    Cohort cohort = context.getBaseCohort();
    if (context.getLimit() != null) {
      CohortUtil.limitCohort(cohort, context.getLimit());
    }

    // Retrieve the rows for the dataset

    BasicEncounterQuery encounterQuery = new BasicEncounterQuery();
    encounterQuery.setWhich(definition.getWhichEncounterQualifier());
    encounterQuery.setWhichNumber(definition.getQuantity());
    encounterQuery.setEncounterTypes(definition.getEncounterTypes());
    encounterQuery.setForms(definition.getForms());
    encounterQuery.setOnOrBefore(definition.getEncounterDatetimeOnOrBefore());
    encounterQuery.setOnOrAfter(definition.getEncounterDatetimeOnOrAfter());

    EncounterQueryResult encounterIdRows = encounterQueryService.evaluate(encounterQuery, context);

    HqlQueryBuilder q = new HqlQueryBuilder();
    EncounterEvaluationContext eec = new EncounterEvaluationContext();
    eec.setBaseEncounters(encounterIdRows);
    q.from(Encounter.class, "e").whereEncounterIn("e.encounterId", eec);
    List<Encounter> encounters = evaluationService.evaluateToList(q, Encounter.class);

    // Determine what columns to display in the dataset

    List<EncounterAndObsDataSetDefinition.ObsOptionalColumn> optionalColumns =
        definition.getOptionalColumns();
    List<PatientIdentifierType> patientIdentifierTypes = definition.getPatientIdentifierTypes();
    List<EncounterAndObsDataSetDefinition.ColumnDisplayFormat> columnDisplayFormat =
        definition.getColumnDisplayFormat();
    Integer maxColumnHeaderWidth = definition.getMaxColumnHeaderWidth();

    if (patientIdentifierTypes == null) {
      patientIdentifierTypes = new ArrayList<PatientIdentifierType>();
    }

    if (columnDisplayFormat == null) {
      columnDisplayFormat = new ArrayList<ColumnDisplayFormat>();
    }

    if (columnDisplayFormat.size() == 0) {
      columnDisplayFormat.add(EncounterAndObsDataSetDefinition.ColumnDisplayFormat.ID);
    }

    // section index should be added here

    // Store all encounters within a data structure that keeps track of obs, obs groups, and their
    // occurrences
    // in order that column headers are unique and children obs are grouped by their parent
    Map<Encounter, Map<ObsColumnDescriptor, Obs>> populatedFieldMap = populateFieldMap(encounters);

    // Keeps track of the column headers for all obs-related columns
    Set<ObsColumnDescriptor> allColumns = new TreeSet<ObsColumnDescriptor>();

    for (Encounter encounter : encounters) {
      Map<ObsColumnDescriptor, Obs> obsInEncounter = populatedFieldMap.get(encounter);
      // Not all encounters will have data for all columns but
      // each encounter row should have all column headers
      // so encounters line up properly under a common set of column headers
      allColumns.addAll(obsInEncounter.keySet());
    }

    // add the data to the DataSet
    return addData(
        dataSet,
        encounters,
        patientIdentifierTypes,
        optionalColumns,
        columnDisplayFormat,
        maxColumnHeaderWidth,
        allColumns,
        populatedFieldMap);
  }