/**
   * Commit the contained item to the main archive. The item is associated with the relevant
   * collection, added to the search index, and any other tasks such as assigning dates are
   * performed.
   *
   * @return the fully archived item.
   */
  @Override
  public Item archive(Context context, BasicWorkflowItem workflowItem)
      throws SQLException, IOException, AuthorizeException {
    // FIXME: Check auth
    Item item = workflowItem.getItem();
    Collection collection = workflowItem.getCollection();

    log.info(
        LogManager.getHeader(
            context,
            "archive_item",
            "workflow_item_id="
                + workflowItem.getID()
                + "item_id="
                + item.getID()
                + "collection_id="
                + collection.getID()));

    installItemService.installItem(context, workflowItem);

    // Log the event
    log.info(
        LogManager.getHeader(
            context,
            "install_item",
            "workflow_id=" + workflowItem.getID() + ", item_id=" + item.getID() + "handle=FIXME"));

    return item;
  }
  /**
   * Return the workflow item to the workspace of the submitter. The workflow item is removed, and a
   * workspace item created.
   *
   * @param c Context
   * @param wfi WorkflowItem to be 'dismantled'
   * @return the workspace item
   */
  protected WorkspaceItem returnToWorkspace(Context c, BasicWorkflowItem wfi)
      throws SQLException, IOException, AuthorizeException {
    // FIXME: How should this interact with the workflow system?
    // FIXME: Remove license
    // FIXME: Provenance statement?
    // Create the new workspace item row
    WorkspaceItem workspaceItem = workspaceItemService.create(c, wfi);

    workspaceItem.setMultipleFiles(wfi.hasMultipleFiles());
    workspaceItem.setMultipleTitles(wfi.hasMultipleTitles());
    workspaceItem.setPublishedBefore(wfi.isPublishedBefore());
    workspaceItemService.update(c, workspaceItem);

    // myitem.update();
    log.info(
        LogManager.getHeader(
            c,
            "return_to_workspace",
            "workflow_item_id=" + wfi.getID() + "workspace_item_id=" + workspaceItem.getID()));

    // Now remove the workflow object manually from the database
    workflowItemService.deleteWrapper(c, wfi);

    return workspaceItem;
  }
  @Override
  public WorkspaceItem abort(Context context, BasicWorkflowItem workflowItem, EPerson e)
      throws SQLException, AuthorizeException, IOException {
    // authorize a DSpaceActions.ABORT
    if (!authorizeService.isAdmin(context)) {
      throw new AuthorizeException("You must be an admin to abort a workflow");
    }

    // stop workflow regardless of its state
    taskListItemService.deleteByWorkflowItem(context, workflowItem);

    log.info(
        LogManager.getHeader(
            context,
            "abort_workflow",
            "workflow_item_id="
                + workflowItem.getID()
                + "item_id="
                + workflowItem.getItem().getID()
                + "collection_id="
                + workflowItem.getCollection().getID()
                + "eperson_id="
                + e.getID()));

    // convert into personal workspace
    return returnToWorkspace(context, workflowItem);
  }
  protected void notifyOfReject(
      Context context, BasicWorkflowItem workflowItem, EPerson e, String reason) {
    try {
      // Get the item title
      String title = getItemTitle(workflowItem);

      // Get the collection
      Collection coll = workflowItem.getCollection();

      // Get rejector's name
      String rejector = getEPersonName(e);
      Locale supportedLocale = I18nUtil.getEPersonLocale(e);
      Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_reject"));

      email.addRecipient(workflowItem.getSubmitter().getEmail());
      email.addArgument(title);
      email.addArgument(coll.getName());
      email.addArgument(rejector);
      email.addArgument(reason);
      email.addArgument(getMyDSpaceLink());

      email.send();
    } catch (RuntimeException re) {
      // log this email error
      log.warn(
          LogManager.getHeader(
              context,
              "notify_of_reject",
              "cannot email user eperson_id="
                  + e.getID()
                  + " eperson_email="
                  + e.getEmail()
                  + " workflow_item_id="
                  + workflowItem.getID()
                  + ":  "
                  + re.getMessage()));

      throw re;
    } catch (Exception ex) {
      // log this email error
      log.warn(
          LogManager.getHeader(
              context,
              "notify_of_reject",
              "cannot email user eperson_id="
                  + e.getID()
                  + " eperson_email="
                  + e.getEmail()
                  + " workflow_item_id="
                  + workflowItem.getID()
                  + ":  "
                  + ex.getMessage()));
    }
  }
  // Record approval provenance statement
  protected void recordApproval(Context context, BasicWorkflowItem workflowItem, EPerson e)
      throws SQLException, IOException, AuthorizeException {
    Item item = workflowItem.getItem();

    // Get user's name + email address
    String usersName = getEPersonName(e);

    // Get current date
    String now = DCDate.getCurrent().toString();

    // Here's what happened
    String provDescription =
        "Approved for entry into archive by " + usersName + " on " + now + " (GMT) ";

    // add bitstream descriptions (name, size, checksums)
    provDescription += installItemService.getBitstreamProvenanceMessage(context, item);

    // Add to item as a DC field
    itemService.addMetadata(
        context,
        item,
        MetadataSchema.DC_SCHEMA,
        "description",
        "provenance",
        "en",
        provDescription);
    itemService.update(context, item);
  }
  @Override
  public void unclaim(Context context, BasicWorkflowItem workflowItem, EPerson e)
      throws SQLException, IOException, AuthorizeException {
    int taskstate = workflowItem.getState();

    switch (taskstate) {
      case WFSTATE_STEP1:

        // authorize DSpaceActions.STEP1
        doState(context, workflowItem, WFSTATE_STEP1POOL, e);

        break;

      case WFSTATE_STEP2:

        // authorize DSpaceActions.APPROVE
        doState(context, workflowItem, WFSTATE_STEP2POOL, e);

        break;

      case WFSTATE_STEP3:

        // authorize DSpaceActions.STEP3
        doState(context, workflowItem, WFSTATE_STEP3POOL, e);

        break;

        // error handling? shouldn't get here
        // FIXME - what to do with error - log it?
    }

    log.info(
        LogManager.getHeader(
            context,
            "unclaim_workflow",
            "workflow_item_id="
                + workflowItem.getID()
                + ",item_id="
                + workflowItem.getItem().getID()
                + ",collection_id="
                + workflowItem.getCollection().getID()
                + ",old_state="
                + taskstate
                + ",new_state="
                + workflowItem.getState()));
  }
  @Override
  public String getItemTitle(BasicWorkflowItem wi) throws SQLException {
    Item myitem = wi.getItem();
    String title = myitem.getName();

    // only return the first element, or "Untitled"
    if (StringUtils.isNotBlank(title)) {
      return title;
    } else {
      return I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.untitled ");
    }
  }
  // send notices of curation activity
  @Override
  public void notifyOfCuration(
      Context c,
      BasicWorkflowItem wi,
      List<EPerson> ePeople,
      String taskName,
      String action,
      String message)
      throws SQLException, IOException {
    try {
      // Get the item title
      String title = getItemTitle(wi);

      // Get the submitter's name
      String submitter = getSubmitterName(wi);

      // Get the collection
      Collection coll = wi.getCollection();

      for (EPerson epa : ePeople) {
        Locale supportedLocale = I18nUtil.getEPersonLocale(epa);
        Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "flowtask_notify"));
        email.addArgument(title);
        email.addArgument(coll.getName());
        email.addArgument(submitter);
        email.addArgument(taskName);
        email.addArgument(message);
        email.addArgument(action);
        email.addRecipient(epa.getEmail());
        email.send();
      }
    } catch (MessagingException e) {
      log.warn(
          LogManager.getHeader(
              c,
              "notifyOfCuration",
              "cannot email users of workflow_item_id " + wi.getID() + ":  " + e.getMessage()));
    }
  }
  @Override
  public BasicWorkflowItem start(Context context, WorkspaceItem wsi)
      throws SQLException, AuthorizeException, IOException {
    // FIXME Check auth
    Item myitem = wsi.getItem();
    Collection collection = wsi.getCollection();

    log.info(
        LogManager.getHeader(
            context,
            "start_workflow",
            "workspace_item_id="
                + wsi.getID()
                + "item_id="
                + myitem.getID()
                + "collection_id="
                + collection.getID()));

    // record the start of the workflow w/provenance message
    recordStart(context, myitem);

    // create the WorkflowItem
    BasicWorkflowItem wfi = workflowItemService.create(context, myitem, collection);
    wfi.setMultipleFiles(wsi.hasMultipleFiles());
    wfi.setMultipleTitles(wsi.hasMultipleTitles());
    wfi.setPublishedBefore(wsi.isPublishedBefore());

    // remove the WorkspaceItem
    workspaceItemService.deleteWrapper(context, wsi);

    // now get the workflow started
    wfi.setState(WFSTATE_SUBMIT);
    advance(context, wfi, null);

    // Return the workflow item
    return wfi;
  }
  @Override
  public void claim(Context context, BasicWorkflowItem workflowItem, EPerson e)
      throws SQLException, IOException, AuthorizeException {
    int taskstate = workflowItem.getState();

    switch (taskstate) {
      case WFSTATE_STEP1POOL:

        // authorize DSpaceActions.SUBMIT_REVIEW
        doState(context, workflowItem, WFSTATE_STEP1, e);

        break;

      case WFSTATE_STEP2POOL:

        // authorize DSpaceActions.SUBMIT_STEP2
        doState(context, workflowItem, WFSTATE_STEP2, e);

        break;

      case WFSTATE_STEP3POOL:

        // authorize DSpaceActions.SUBMIT_STEP3
        doState(context, workflowItem, WFSTATE_STEP3, e);

        break;

        // if we got here, we weren't pooled... error?
        // FIXME - log the error?
    }

    log.info(
        LogManager.getHeader(
            context,
            "claim_task",
            "workflow_item_id="
                + workflowItem.getID()
                + "item_id="
                + workflowItem.getItem().getID()
                + "collection_id="
                + workflowItem.getCollection().getID()
                + "newowner_id="
                + workflowItem.getOwner().getID()
                + "old_state="
                + taskstate
                + "new_state="
                + workflowItem.getState()));
  }
  protected void notifyGroupOfTask(
      Context c, BasicWorkflowItem wi, Group mygroup, List<EPerson> epa)
      throws SQLException, IOException {
    // check to see if notification is turned off
    // and only do it once - delete key after notification has
    // been suppressed for the first time
    UUID myID = wi.getItem().getID();

    if (noEMail.containsKey(myID)) {
      // suppress email, and delete key
      noEMail.remove(myID);
    } else {
      try {
        // Get the item title
        String title = getItemTitle(wi);

        // Get the submitter's name
        String submitter = getSubmitterName(wi);

        // Get the collection
        Collection coll = wi.getCollection();

        String message = "";

        for (EPerson anEpa : epa) {
          Locale supportedLocale = I18nUtil.getEPersonLocale(anEpa);
          Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_task"));
          email.addArgument(title);
          email.addArgument(coll.getName());
          email.addArgument(submitter);

          ResourceBundle messages = ResourceBundle.getBundle("Messages", supportedLocale);
          switch (wi.getState()) {
            case WFSTATE_STEP1POOL:
              message = messages.getString("org.dspace.workflow.WorkflowManager.step1");

              break;

            case WFSTATE_STEP2POOL:
              message = messages.getString("org.dspace.workflow.WorkflowManager.step2");

              break;

            case WFSTATE_STEP3POOL:
              message = messages.getString("org.dspace.workflow.WorkflowManager.step3");

              break;
          }
          email.addArgument(message);
          email.addArgument(getMyDSpaceLink());
          email.addRecipient(anEpa.getEmail());
          email.send();
        }
      } catch (MessagingException e) {
        String gid = (mygroup != null) ? String.valueOf(mygroup.getID()) : "none";
        log.warn(
            LogManager.getHeader(
                c,
                "notifyGroupofTask",
                "cannot email user group_id="
                    + gid
                    + " workflow_item_id="
                    + wi.getID()
                    + ":  "
                    + e.getMessage()));
      }
    }
  }
  @Override
  public WorkspaceItem sendWorkflowItemBackSubmission(
      Context context,
      BasicWorkflowItem workflowItem,
      EPerson ePerson,
      String provenancePrefix,
      String rejection_message)
      throws SQLException, AuthorizeException, IOException {

    int oldState = workflowItem.getState();
    // authorize a DSpaceActions.REJECT
    // stop workflow
    taskListItemService.deleteByWorkflowItem(context, workflowItem);

    // rejection provenance
    Item myitem = workflowItem.getItem();

    // Get current date
    String now = DCDate.getCurrent().toString();

    // Get user's name + email address
    String usersName = getEPersonName(ePerson);

    // Here's what happened
    String provDescription =
        "Rejected by " + usersName + ", reason: " + rejection_message + " on " + now + " (GMT) ";

    // Add to item as a DC field
    itemService.addMetadata(
        context,
        myitem,
        MetadataSchema.DC_SCHEMA,
        "description",
        "provenance",
        "en",
        provDescription);
    itemService.update(context, myitem);

    // convert into personal workspace
    WorkspaceItem wsi = returnToWorkspace(context, workflowItem);

    // notify that it's been rejected
    notifyOfReject(context, workflowItem, ePerson, rejection_message);

    log.info(
        LogManager.getHeader(
            context,
            "reject_workflow",
            "workflow_item_id="
                + workflowItem.getID()
                + "item_id="
                + workflowItem.getItem().getID()
                + "collection_id="
                + workflowItem.getCollection().getID()
                + "eperson_id="
                + ePerson.getID()));

    logWorkflowEvent(
        context,
        wsi.getItem(),
        workflowItem,
        ePerson,
        WFSTATE_SUBMIT,
        null,
        wsi.getCollection(),
        oldState,
        null);

    return wsi;
  }
  // returns true if archived
  protected boolean doState(
      Context context, BasicWorkflowItem workflowItem, int newstate, EPerson newowner)
      throws SQLException, IOException, AuthorizeException {
    Collection mycollection = workflowItem.getCollection();
    Group mygroup = null;
    boolean archived = false;

    // Gather our old data for launching the workflow event
    int oldState = workflowItem.getState();

    workflowItem.setState(newstate);

    switch (newstate) {
      case WFSTATE_STEP1POOL:

        // any reviewers?
        // if so, add them to the tasklist
        workflowItem.setOwner(null);

        // get reviewers (group 1 )
        mygroup = collectionService.getWorkflowGroup(mycollection, 1);

        if ((mygroup != null) && !(groupService.isEmpty(mygroup))) {
          // get a list of all epeople in group (or any subgroups)
          List<EPerson> epa = groupService.allMembers(context, mygroup);

          // there were reviewers, change the state
          //  and add them to the list
          createTasks(context, workflowItem, epa);
          workflowItemService.update(context, workflowItem);

          // email notification
          notifyGroupOfTask(context, workflowItem, mygroup, epa);
        } else {
          // no reviewers, skip ahead
          workflowItem.setState(WFSTATE_STEP1);
          archived = advance(context, workflowItem, null, true, false);
        }

        break;

      case WFSTATE_STEP1:

        // remove reviewers from tasklist
        // assign owner
        taskListItemService.deleteByWorkflowItem(context, workflowItem);
        workflowItem.setOwner(newowner);

        break;

      case WFSTATE_STEP2POOL:

        // clear owner
        // any approvers?
        // if so, add them to tasklist
        // if not, skip to next state
        workflowItem.setOwner(null);

        // get approvers (group 2)
        mygroup = collectionService.getWorkflowGroup(mycollection, 2);

        if ((mygroup != null) && !(groupService.isEmpty(mygroup))) {
          // get a list of all epeople in group (or any subgroups)
          List<EPerson> epa = groupService.allMembers(context, mygroup);

          // there were approvers, change the state
          //  timestamp, and add them to the list
          createTasks(context, workflowItem, epa);

          // email notification
          notifyGroupOfTask(context, workflowItem, mygroup, epa);
        } else {
          // no reviewers, skip ahead
          workflowItem.setState(WFSTATE_STEP2);
          archived = advance(context, workflowItem, null, true, false);
        }

        break;

      case WFSTATE_STEP2:

        // remove admins from tasklist
        // assign owner
        taskListItemService.deleteByWorkflowItem(context, workflowItem);
        workflowItem.setOwner(newowner);

        break;

      case WFSTATE_STEP3POOL:

        // any editors?
        // if so, add them to tasklist
        workflowItem.setOwner(null);
        mygroup = collectionService.getWorkflowGroup(mycollection, 3);

        if ((mygroup != null) && !(groupService.isEmpty(mygroup))) {
          // get a list of all epeople in group (or any subgroups)
          List<EPerson> epa = groupService.allMembers(context, mygroup);

          // there were editors, change the state
          //  timestamp, and add them to the list
          createTasks(context, workflowItem, epa);

          // email notification
          notifyGroupOfTask(context, workflowItem, mygroup, epa);
        } else {
          // no editors, skip ahead
          workflowItem.setState(WFSTATE_STEP3);
          archived = advance(context, workflowItem, null, true, false);
        }

        break;

      case WFSTATE_STEP3:

        // remove editors from tasklist
        // assign owner
        taskListItemService.deleteByWorkflowItem(context, workflowItem);
        workflowItem.setOwner(newowner);

        break;

      case WFSTATE_ARCHIVE:

        // put in archive in one transaction
        // remove workflow tasks
        taskListItemService.deleteByWorkflowItem(context, workflowItem);

        mycollection = workflowItem.getCollection();

        Item myitem = archive(context, workflowItem);

        // now email notification
        notifyOfArchive(context, myitem, mycollection);
        archived = true;

        break;
    }

    logWorkflowEvent(
        context,
        workflowItem.getItem(),
        workflowItem,
        context.getCurrentUser(),
        newstate,
        newowner,
        mycollection,
        oldState,
        mygroup);

    if (!archived) {
      workflowItemService.update(context, workflowItem);
    }

    return archived;
  }
  @Override
  public boolean advance(
      Context context, BasicWorkflowItem workflowItem, EPerson e, boolean curate, boolean record)
      throws SQLException, IOException, AuthorizeException {
    int taskstate = workflowItem.getState();
    boolean archived = false;

    // perform curation tasks if needed
    if (curate && workflowCuratorService.needsCuration(workflowItem)) {
      if (!workflowCuratorService.doCuration(context, workflowItem)) {
        // don't proceed - either curation tasks queued, or item rejected
        log.info(
            LogManager.getHeader(
                context,
                "advance_workflow",
                "workflow_item_id="
                    + workflowItem.getID()
                    + ",item_id="
                    + workflowItem.getItem().getID()
                    + ",collection_id="
                    + workflowItem.getCollection().getID()
                    + ",old_state="
                    + taskstate
                    + ",doCuration=false"));
        return false;
      }
    }

    switch (taskstate) {
      case WFSTATE_SUBMIT:
        archived = doState(context, workflowItem, WFSTATE_STEP1POOL, e);

        break;

      case WFSTATE_STEP1:

        // authorize DSpaceActions.SUBMIT_REVIEW
        // Record provenance
        if (record) {
          recordApproval(context, workflowItem, e);
        }
        archived = doState(context, workflowItem, WFSTATE_STEP2POOL, e);

        break;

      case WFSTATE_STEP2:

        // authorize DSpaceActions.SUBMIT_STEP2
        // Record provenance
        if (record) {
          recordApproval(context, workflowItem, e);
        }
        archived = doState(context, workflowItem, WFSTATE_STEP3POOL, e);

        break;

      case WFSTATE_STEP3:

        // authorize DSpaceActions.SUBMIT_STEP3
        // We don't record approval for editors, since they can't reject,
        // and thus didn't actually make a decision
        archived = doState(context, workflowItem, WFSTATE_ARCHIVE, e);

        break;

        // error handling? shouldn't get here
    }

    log.info(
        LogManager.getHeader(
            context,
            "advance_workflow",
            "workflow_item_id="
                + workflowItem.getID()
                + ",item_id="
                + workflowItem.getItem().getID()
                + ",collection_id="
                + workflowItem.getCollection().getID()
                + ",old_state="
                + taskstate
                + ",new_state="
                + workflowItem.getState()));
    return archived;
  }
  @Override
  public String getSubmitterName(BasicWorkflowItem wi) throws SQLException {
    EPerson e = wi.getSubmitter();

    return getEPersonName(e);
  }