/**
   * Retrieves the group instance from the persistence given its id. The group instance is always
   * retrieved with its sub items.
   *
   * @return The group instance or <code>null</code> if group not found.
   * @param id The id of the group to be retrieved.
   * @throws IllegalArgumentException if the input id is less than or equal to zero.
   * @throws PersistenceException if error occurred while accessing the database.
   */
  public Group getGroup(long id) throws PersistenceException {
    if (id <= 0) {
      throw new IllegalArgumentException("The id must be positive. Id: " + id);
    }
    logger.log(Level.INFO, new LogMessage("Group", new Long(id), null, "retrieve group"));

    PreparedStatement pstmt = null;
    ResultSet rs = null;

    try {
      // create the statement and set the id
      pstmt = connection.prepareStatement(SELECT_SCORECARD_GROUP_BY_ID);
      pstmt.setLong(1, id);
      rs = pstmt.executeQuery();
      // if the group exists - create it
      if (rs.next()) {
        InformixSectionPersistence sectionPersistence = new InformixSectionPersistence(connection);
        Group group = populateGroup(rs);
        group.addSections(sectionPersistence.getSections(group.getId()));
        return group;
      }
    } catch (SQLException ex) {
      logger.log(
          Level.ERROR, new LogMessage("Group", new Long(id), null, "Failed to retrieve group", ex));
      throw new PersistenceException("Error occurs while retrieving the group.", ex);
    } finally {
      DBUtils.close(rs);
      DBUtils.close(pstmt);
    }
    // return null if group not found.
    return null;
  }
  /**
   * Creates the group in the database using the given group instance. The group instance can
   * include sub items such as sections and questions. Those sub items will be persisted as well.
   * The operator parameter is used as the creation/modification user of the group and its subitems.
   * The creation date and modification date will be the current date time when the group is
   * created.
   *
   * @param group The group instance to be created in the database.
   * @param order the sort order of this group.
   * @param operator The creation user of this group.
   * @param parentId The id of the scorecard that contains this.
   * @throws IllegalArgumentException if any input is null or the operator is empty string.
   * @throws PersistenceException if error occurred while accessing the database.
   */
  public void createGroup(Group group, int order, String operator, long parentId)
      throws PersistenceException {
    if (group == null) {
      throw new IllegalArgumentException("group cannot be null.");
    }
    if (operator == null) {
      throw new IllegalArgumentException("operator cannot be null.");
    }

    if (operator.trim().length() == 0) {
      throw new IllegalArgumentException("operator cannot be empty String.");
    }

    logger.log(
        Level.INFO,
        new LogMessage(
            "Group",
            null,
            operator,
            "create new Group with order:" + order + " and parentId:" + parentId));
    // get next id
    long groupId = DBUtils.nextId(IdGeneratorUtility.getGroupIdGenerator());
    Timestamp time = new Timestamp(System.currentTimeMillis());
    // create section persistence
    InformixSectionPersistence sectionPersistence = new InformixSectionPersistence(connection);
    PreparedStatement pstmt = null;

    logger.log(Level.INFO, "insert record into scorecard_group with group_id:" + groupId);
    try {
      // create statement
      pstmt = connection.prepareStatement(INSERT_SCORECARD_GROUP);

      // set the variables
      pstmt.setLong(1, groupId);
      pstmt.setLong(2, parentId);
      pstmt.setString(3, group.getName());
      pstmt.setFloat(4, group.getWeight());
      pstmt.setInt(5, order);

      pstmt.setString(6, operator);
      pstmt.setTimestamp(7, time);
      pstmt.setString(8, operator);
      pstmt.setTimestamp(9, time);

      // create group and create the sections
      pstmt.executeUpdate();
      sectionPersistence.createSections(group.getAllSections(), operator, groupId);
    } catch (SQLException ex) {
      logger.log(
          Level.ERROR,
          new LogMessage("Group", new Long(groupId), operator, "Fail to create Group.", ex));
      throw new PersistenceException("Error occur while creating the scorecard group.", ex);
    } finally {
      DBUtils.close(pstmt);
    }

    // set the group id.
    group.setId(groupId);
  }
  /**
   * Processes the datasets.
   *
   * @param args optional arguments (not-null, can be empty)
   */
  @Override
  public void processDataSets(String[] args) {
    String signature = CLASS_NAME + ".processDataSets()";
    LoggingWrapperUtility.logEntrance(logger, signature, null, null);

    // 1. Select the utility.
    DataSetUtility utility = null;
    for (DataSetUtility curUtility : utilities) {
      if (curUtility.getName().equalsIgnoreCase(utilityName)) {
        utility = curUtility;
        break;
      }
    }
    if (utility == null) {
      logger.log(Level.INFO, "Unknown utility: {0}", utilityName);
      StringBuilder sb = new StringBuilder("The following utilities are available: ");
      boolean first = true;
      for (DataSetUtility curUtility : utilities) {
        if (!first) {
          sb.append(", ");
        }
        first = false;
        sb.append(curUtility.getName());
      }
      logger.log(Level.INFO, sb.toString());
      return;
    }

    // 2. Select datasets for processing.
    List<DataSetInfo> dataSets =
        Helper.selectDataSets(dataSetSelector, startDataSet, endDataSet, logger);

    if (dataSets == null) {
      return;
    }

    // 3. Parse arguments
    List<String> utilityArgs = new ArrayList<String>();
    Map<String, String> utilityOptions = new HashMap<String, String>();

    for (int i = 0; i < args.length; i++) {
      String arg = args[i];
      if (arg.charAt(0) != '-') {
        utilityArgs.add(arg);
      } else {
        if (i == args.length - 1) {
          logger.log(Level.INFO, "The option {0} does not specify its value", arg);
          return;
        }
        utilityOptions.put(arg, args[i + 1]);
        i++;
      }
    }

    // 4. Run the utility
    utility.run(dataSets, utilityArgs, utilityOptions);
    LoggingWrapperUtility.logExit(logger, signature, null);
  }
  /**
   * This method creates the groups instances in the datastore. It has the same behavior as {@link
   * #createGroup(Group, int, String, long)} except it executes insert at once.
   *
   * @param groups the groups to be created.
   * @param operator the creation operator.
   * @param parentId the id of the parent scorecard.
   * @throws PersistenceException if error occurred while accessing the database.
   */
  void createGroups(Group[] groups, String operator, long parentId) throws PersistenceException {
    logger.log(
        Level.INFO,
        new LogMessage("Group", null, operator, "create new Groups with parentId:" + parentId));

    // generate the ids
    long[] groupIds =
        DBUtils.generateIdsArray(groups.length, IdGeneratorUtility.getGroupIdGenerator());
    Timestamp time = new Timestamp(System.currentTimeMillis());
    // create sections persistence
    InformixSectionPersistence sectionPersistence = new InformixSectionPersistence(connection);
    PreparedStatement pstmt = null;

    try {
      pstmt = connection.prepareStatement(INSERT_SCORECARD_GROUP);
      // set the id of the parent
      pstmt.setLong(2, parentId);

      // for each group - set the variables
      for (int i = 0; i < groups.length; i++) {
        pstmt.setLong(1, groupIds[i]);

        pstmt.setString(3, groups[i].getName());
        pstmt.setFloat(4, groups[i].getWeight());
        pstmt.setInt(5, i);

        pstmt.setString(6, operator);
        pstmt.setTimestamp(7, time);
        pstmt.setString(8, operator);
        pstmt.setTimestamp(9, time);

        // execute the update and creates sections of this group
        pstmt.executeUpdate();

        logger.log(
            Level.INFO, "insert record into scorecard_group table with groupId:" + groupIds[i]);
        sectionPersistence.createSections(groups[i].getAllSections(), operator, groupIds[i]);
      }
    } catch (SQLException ex) {
      logger.log(
          Level.INFO,
          new LogMessage(
              "Group",
              null,
              operator,
              "Failed to create new Groups with parentId:" + parentId,
              ex));
      throw new PersistenceException("Error occur while creating the scorecard group.", ex);
    } finally {
      DBUtils.close(pstmt);
    }

    // set ids to groups
    for (int i = 0; i < groups.length; i++) {
      groups[i].setId(groupIds[i]);
    }
  }
  /**
   * Retrieves the group instances from the persistence with the given parent id. The group instance
   * is always retrieved with its sub items.
   *
   * @param parentId the id of the paren scorecard.
   * @return the list of groups for the given parent.
   * @throws PersistenceException if database error occur.
   */
  Group[] getGroups(long parentId) throws PersistenceException {
    PreparedStatement pstmt = null;
    ResultSet rs = null;

    try {
      // create the statement
      pstmt = connection.prepareStatement(SELECT_SCORECARD_GROUP_BY_PARENT_ID);
      pstmt.setLong(1, parentId);

      // get all groups
      rs = pstmt.executeQuery();
      List result = new ArrayList();
      InformixSectionPersistence sectionPersistence = new InformixSectionPersistence(connection);
      while (rs.next()) {
        Group group = populateGroup(rs);
        // get the sections for the group
        group.addSections(sectionPersistence.getSections(group.getId()));
        result.add(group);
      }

      return (Group[]) result.toArray(new Group[result.size()]);
    } catch (SQLException ex) {
      logger.log(
          Level.ERROR,
          new LogMessage(
              "Group", null, null, "Failed to retrieve group with parentId:" + parentId, ex));
      throw new PersistenceException("Error occurs while retrieving the group.", ex);
    } finally {
      DBUtils.close(rs);
      DBUtils.close(pstmt);
    }
  }
  /**
   * OIG exclusion screening.
   *
   * @param item the work item to abort
   * @param manager the work item manager
   */
  public void executeWorkItem(WorkItem item, WorkItemManager manager) {
    log.log(Level.INFO, "Checking provider exclusion.");
    EnrollmentProcess processModel = (EnrollmentProcess) item.getParameter("model");

    ProviderInformationType provider = XMLUtility.nsGetProvider(processModel);
    ScreeningResultsType screeningResults = XMLUtility.nsGetScreeningResults(processModel);
    ScreeningResultType screeningResultType = new ScreeningResultType();
    screeningResults.getScreeningResult().add(screeningResultType);

    ExternalSourcesScreeningResultType results = null;
    try {
      OIGExclusionSearchClient client = new OIGExclusionSearchClient();
      results = client.verify(XMLUtility.nsGetProvider(processModel));

      VerificationStatusType verificationStatus = XMLUtility.nsGetVerificationStatus(processModel);
      if (!results.getSearchResults().getSearchResultItem().isEmpty()) {
        OIGExclusionServiceMatcher matcher = new OIGExclusionServiceMatcher();
        MatchStatus status = matcher.match(provider, null, results);
        if (status == MatchStatus.EXACT_MATCH) {
          verificationStatus.setNonExclusion("N");
        } else {
          verificationStatus.setNonExclusion("Y");
        }
      } else {
        verificationStatus.setNonExclusion("Y");
      }
    } catch (TransformerException e) {
      log.log(Level.ERROR, e);
      results = new ExternalSourcesScreeningResultType();
      results.setStatus(XMLUtility.newStatus("ERROR"));
    } catch (JAXBException e) {
      log.log(Level.ERROR, e);
      results = new ExternalSourcesScreeningResultType();
      results.setStatus(XMLUtility.newStatus("ERROR"));
    } catch (IOException e) {
      log.log(Level.ERROR, e);
      results = new ExternalSourcesScreeningResultType();
      results.setStatus(XMLUtility.newStatus("ERROR"));
    }

    screeningResultType.setExclusionVerificationResult(results.getSearchResults());
    screeningResultType.setScreeningType("EXCLUDED PROVIDERS");
    screeningResultType.setStatus(XMLUtility.newStatus("SUCCESS"));

    item.getResults().put("model", processModel);
    manager.completeWorkItem(item.getId(), item.getResults());
  }
  /**
   * Updates the group (the scorecard_group table) in the database.
   *
   * @param conn the database connection to be used.
   * @param group the groupt to update.
   * @param operator the update operator name.
   * @param parentId the parent of this group (scorecard id).
   * @param order the order of this group in the database.
   * @throws PersistenceException if any database error occurs.
   */
  private static void updateGroup(
      Connection conn, Group group, String operator, long parentId, int order)
      throws PersistenceException {

    logger.log(Level.INFO, "update scorecard_group with groupId:" + group.getId());
    PreparedStatement pstmt = null;
    try {
      // prepare the statement
      pstmt = conn.prepareStatement(UPDATE_SCORECARD_GROUP);

      // set the variables
      pstmt.setLong(1, parentId);
      pstmt.setString(2, group.getName());
      pstmt.setFloat(3, group.getWeight());
      pstmt.setInt(4, order);

      // set the modification user and time
      Timestamp time = new Timestamp(System.currentTimeMillis());
      pstmt.setString(5, operator);
      pstmt.setTimestamp(6, time);
      pstmt.setLong(7, group.getId());

      if (pstmt.executeUpdate() != 1) {
        logger.log(Level.ERROR, "No group with id = " + group.getId());
        throw new PersistenceException("No group with id = " + group.getId());
      }
    } catch (SQLException ex) {
      logger.log(
          Level.ERROR,
          new LogMessage(
              "Group",
              new Long(group.getId()),
              operator,
              "Error occurs while updating the group.",
              ex));
      throw new PersistenceException("Error occurs while updating the group.", ex);
    } finally {
      DBUtils.close(pstmt);
    }
  }
  /**
   * Remove the groups with the given array of ids from the persistence. Group deletion is required
   * when user udpate a scorecard and remove a group from its group list.
   *
   * @param ids The id of the group to remove.
   * @throws IllegalArgumentException if the ids is less than or equal to zero. Or the input array
   *     is null or empty.
   * @throws PersistenceException if error occurred while accessing the database.
   */
  public void deleteGroups(long[] ids) throws PersistenceException {
    DBUtils.checkIdsArray(ids, "ids");

    logger.log(
        Level.INFO,
        new LogMessage(
            "Group",
            null,
            null,
            "Delete Groups with ids:" + InformixPersistenceHelper.generateIdString(ids)));

    PreparedStatement pstmt = null;
    PreparedStatement pstmt2 = null;
    ResultSet rs = null;
    try {
      // selects the sections ids for given groups
      pstmt =
          connection.prepareStatement(
              SELECT_SCORECARD_SECTION_ID + DBUtils.createQuestionMarks(ids.length));
      logger.log(
          Level.INFO,
          "delete record from scorecard_groups with ids:"
              + InformixPersistenceHelper.generateIdString(ids));
      // deletes the given groups
      pstmt2 =
          connection.prepareStatement(
              DELETE_SCORECARD_GROUPS + DBUtils.createQuestionMarks(ids.length));

      // set the ids for both statements
      for (int i = 0; i < ids.length; i++) {
        pstmt.setLong(i + 1, ids[i]);
        pstmt2.setLong(i + 1, ids[i]);
      }

      // get all the sections to be removed
      rs = pstmt.executeQuery();
      List idsToDelete = new ArrayList();
      while (rs.next()) {
        idsToDelete.add(new Long(rs.getLong(1)));
      }

      // delete the sections
      if (idsToDelete.size() > 0) {
        InformixSectionPersistence sectionPersistence = new InformixSectionPersistence(connection);
        sectionPersistence.deleteSections(DBUtils.listToArray(idsToDelete));
      }

      // delete the groups
      pstmt2.executeUpdate();
    } catch (SQLException ex) {
      logger.log(
          Level.ERROR,
          new LogMessage(
              "Group",
              null,
              null,
              "Failed to Delete Groups with ids:" + InformixPersistenceHelper.generateIdString(ids),
              ex));
      throw new PersistenceException("Error occurs while deleting the questions.", ex);
    } finally {
      DBUtils.close(rs);
      DBUtils.close(pstmt);
      DBUtils.close(pstmt2);
    }
  }
  /**
   * Update the given group instance into the database. The group instance can include sub items
   * such as sections and questions. Those sub items will be updated as well. If sub items are
   * removed from the group, they will be deleted from the persistence. Likewise, if new sub items
   * are added, they will be created in the persistence. The operator parameter is used as the
   * modification user of the group and its subitems. The modification date will be the current date
   * time when the group is updated.
   *
   * @param group The group instance to be updated into the database.
   * @param order the position of the group.
   * @param operator The modification user of this group.
   * @param parentId The id of the scorecard that contains this.
   * @param oldScorecard The scorecard instance before update. It is used to find out remeved items.
   * @param deletedSectionIds This is an output parameter. An empty array is expected to be passed
   *     in. Deleted section ids will be saved into this list.
   * @param deletedQuestionIds This is an output parameter. An empty array is expected to be passed
   *     in. Deleted question ids will be saved into this list. Delete question ids is collected
   *     from updateSection() call.
   * @throws IllegalArgumentException if any input is null or the operator is empty string.
   * @throws PersistenceException if error occurred while accessing the database.
   */
  public void updateGroup(
      Group group,
      int order,
      String operator,
      long parentId,
      Scorecard oldScorecard,
      List deletedSectionIds,
      List deletedQuestionIds)
      throws PersistenceException {
    if (group == null) {
      throw new IllegalArgumentException("group cannot be null.");
    }

    if (operator == null) {
      throw new IllegalArgumentException("operator cannot be null.");
    }

    if (operator.trim().length() == 0) {
      throw new IllegalArgumentException("operator cannot be empty String.");
    }

    if (oldScorecard == null) {
      throw new IllegalArgumentException("oldScorecard cannot be null.");
    }

    if (deletedSectionIds == null) {
      throw new IllegalArgumentException("deletedSectionIds cannot be null.");
    }

    if (deletedQuestionIds == null) {
      throw new IllegalArgumentException("deletedQuestionIds cannot be null.");
    }

    logger.log(
        Level.INFO,
        new LogMessage(
            "Group",
            new Long(group.getId()),
            operator,
            "create new Group with order:"
                + order
                + " ,parentId:"
                + parentId
                + ", oldScorecard:"
                + oldScorecard.getId()));

    Set oldSectionIds = getSectionsIds(group, oldScorecard);
    // mark all old section as 'to delete'
    deletedSectionIds.addAll(oldSectionIds);

    // get the section and create its persistence
    Section[] sections = group.getAllSections();
    InformixSectionPersistence sectionPersistence = new InformixSectionPersistence(connection);

    // for each new section
    for (int i = 0; i < sections.length; i++) {
      Long longId = new Long(sections[i].getId());
      // if is new - create it
      if (sections[i].getId() == NamedScorecardStructure.SENTINEL_ID) {
        sectionPersistence.createSection(sections[i], i, operator, group.getId());
      } else if (oldSectionIds.contains(longId)) {
        // if is old - update it and removed from delete list
        sectionPersistence.updateSection(
            sections[i], i, operator, group.getId(), oldScorecard, deletedQuestionIds);
        deletedSectionIds.remove(longId);
      }
    }
    // update the group in the database
    updateGroup(connection, group, operator, parentId, order);
  }