/* (non-Javadoc)
   * @see com.buildml.main.ICliCommand#invoke(com.buildml.model.BuildStore, java.lang.String[])
   */
  @Override
  public void invoke(IBuildStore buildStore, String buildStorePath, String[] args) {

    CliUtils.validateArgs(
        getName(),
        args,
        2,
        2,
        "You must specify a package name and a colon-separated list of action-specs.");

    IPackageMemberMgr pkgMemberMgr = buildStore.getPackageMemberMgr();
    IActionMgr actionMgr = buildStore.getActionMgr();

    /*
     * The package can be of the form: "pkg". There is no scope value allowed
     * for actions.
     */
    String pkgName = args[0];
    int pkgAndScopeIds[] = CliUtils.parsePackageAndScope(buildStore, pkgName, false);
    int pkgId = pkgAndScopeIds[0];

    /* compute the ActionSet from the user-supplied list of action-specs */
    String actionSpecs = args[1];
    ActionSet actionsToSet = CliUtils.getCmdLineActionSet(actionMgr, actionSpecs);

    /* now visit each action in the ActionSet and set its package */
    boolean prevState = buildStore.setFastAccessMode(true);
    for (int actionId : actionsToSet) {
      pkgMemberMgr.setPackageOfMember(IPackageMemberMgr.TYPE_ACTION, actionId, pkgId);
    }
    buildStore.setFastAccessMode(prevState);
  }
  /**
   * Given an actionId, move the action, and possibly all its predecessors to the new package. If
   * necessary, an output file group is created and also moved to the new package. So too are all
   * the files in that groups.
   *
   * @param actionId ID of the action to be moved.
   * @return ID of the output file group, or ErrorCode.NOT_FOUND if there's no output group.
   * @throws CanNotRefactorException If something goes wrong.
   */
  private int moveActionToPackage(int actionId) throws CanNotRefactorException {

    /* first, check to see if this action is already imported this package - if so, we're done */
    Object existingMap = actionCache.get(Integer.valueOf(actionId));
    if (existingMap instanceof Integer) {
      return (Integer) existingMap;
    }

    /* validate that the action is atomic */
    Integer children[] = actionMgr.getChildren(actionId);
    if (children.length != 0) {
      throw new CanNotRefactorException(Cause.ACTION_NOT_ATOMIC, actionId);
    }

    /*
     * The action is not yet imported, and it is atomic. Figure out its current package
     * and prepare to move it to the new package.
     */
    int fileGroupId = ErrorCode.NOT_FOUND;
    PackageDesc desc = pkgMemberMgr.getPackageOfMember(IPackageMemberMgr.TYPE_ACTION, actionId);
    if (desc == null) {
      throw new FatalError("Can't retrieve action's current package");
    }

    /* Create UndoOp for changing the action's package */
    ActionUndoOp actionOp = new ActionUndoOp(buildStore, actionId);
    actionOp.recordPackageChange(desc.pkgId, destPkgId);
    multiOp.add(actionOp);

    /* Create a new file group containing all the files that this action generates */
    Integer writtenFiles[] = actionMgr.getFilesAccessed(actionId, OperationType.OP_WRITE);
    if (writtenFiles.length > 0) {
      fileGroupId = createSourceFileGroup(Arrays.asList(writtenFiles));

      /* Connect the "output" slot from the action to this new file group */
      ActionUndoOp slotOp = new ActionUndoOp(buildStore, actionId);
      slotOp.recordSlotChange(IActionMgr.OUTPUT_SLOT_ID, null, fileGroupId);
      multiOp.add(slotOp);
    }

    /* compute/generate/move all the predecessor actions or file groups */
    computeInputActions(actionId);

    /*
     * Store actionId/fileGroupId in our cache, to avoid doing this import again
     * and ending up with multiple output file groups when only one is required.
     */
    actionCache.put(actionId, fileGroupId);

    /* return the ID of the output file group */
    return fileGroupId;
  }
  /**
   * Given a list of file IDs, create a new source file group and schedule the members to be added
   * to it (by appending to the multiOp). In addition to moving the file group into the destination
   * package, all the individual files are also moved. If any files are not within the source root,
   * throw a {@link CanNotRefactorException} with cause code of PATH_OUT_OF_RANGE.
   *
   * @param members A list of file IDs to be added to the file group.
   * @return The ID of the newly-created file group.
   * @throws CanNotRefactorException Something went wrong during the refactoring.
   */
  private int createSourceFileGroup(List<Integer> members) throws CanNotRefactorException {

    /*
     * Create the new fileGroup. Even though we won't populate it until the multiOp
     * is executed, we must still allocate a new fileGroup ID number.
     */
    int fileGroupId = fileGroupMgr.newSourceGroup(destPkgId);
    if (fileGroupId < 0) {
      throw new FatalError("Unable to create new file group");
    }
    FileGroupUndoOp op = new FileGroupUndoOp(buildStore, fileGroupId);
    op.recordMembershipChange(new ArrayList<Integer>(), members);
    multiOp.add(op);

    /*
     * Move all the files into the destination package, using FileUndoOps. Before
     * a file can be moved, we must ensure that it's within the source root of the package.
     */
    List<Integer> filesOutOfRange = new ArrayList<Integer>();

    /*
     * For each loose file, validate if it's within the package roots, and if so,
     * schedule an UndoOp to make the necessary change. If not, throw an exception.
     */
    for (int pathId : members) {
      PackageDesc oldDesc = pkgMemberMgr.getPackageOfMember(IPackageMemberMgr.TYPE_FILE, pathId);
      if (oldDesc == null) {
        throw new FatalError("Can't find pathId");
      }

      /*
       * Check that this path is within the source root. If so, schedule it to be move to
       * the destination package.
       */
      if (fileMgr.isAncestorOf(pkgRootId, pathId)) {
        FileUndoOp pkgChangeOp = new FileUndoOp(buildStore, pathId);
        pkgChangeOp.recordChangePackage(
            oldDesc.pkgId, oldDesc.pkgScopeId, destPkgId, IPackageMemberMgr.SCOPE_PRIVATE);
        multiOp.add(pkgChangeOp);
      }

      /*
       * Else, record this pathID as being out of range. We'll report an exception
       * once we've collected the complete list of invalid paths.
       */
      else {
        filesOutOfRange.add(pathId);
      }
    }

    /*
     * If any files were out of range, throw an exception.
     */
    if (filesOutOfRange.size() > 0) {
      throw new CanNotRefactorException(
          Cause.PATH_OUT_OF_RANGE, filesOutOfRange.toArray(new Integer[0]));
    }

    /* update the cache, with the members that the file group will contain (once the multiOp is executed) */
    fileGroupCache.put(fileGroupId, members);

    return fileGroupId;
  }