/*
 * ====================================================================
 * Copyright (c) 2008 JavaGit Project.  All rights reserved.
 *
 * This software is licensed using the GNU LGPL v2.1 license.  A copy
 * of the license is included with the distribution of this source
 * code in the LICENSE.txt file.  The text of the license can also
 * be obtained at:
 *
 *   http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *
 * For more information on the JavaGit project, see:
 *
 *   http://www.javagit.com
 * ====================================================================
 */
package edu.nyu.cs.javagit.api.commands;

import edu.nyu.cs.javagit.api.Ref;
import edu.nyu.cs.javagit.utilities.ExceptionMessageMap;

/**
 * A class to manage passing branch options to the GitBranch command.
 */
public class GitBranchOptions {
  /*
   * Generic Options.
   */

  //The -v or --verbose option
  private boolean optVerbose = false;

  //The --track option
  private boolean optTrack = false;

  //The --no-track option. Ignore the branch.autosetupmerge configuration variable.
  private boolean optNoTrack = false;

  //The color option. Color branches to highlight current, local, and remote branches
  private boolean optColor = false;

  /*The --no-color option. Turn off branch colors, even when the configuration file gives the
   *default to color output.
   */
  private boolean optNoColor = false;

  //The -r (remote-tracking) option
  private boolean optR = false;

  //The --contains option. Prints only branches that contain the commit
  private Ref optContains;

  //The --abbrev option. --abbrev to set with default <n>.
  private boolean optAbbrev = false;

  //The default abbrev length.
  public static final int DEFAULT_ABBREV_LEN = 7;

  //The --abbrev option. --abbrev [<n>]   use <n> digits to display SHA-1s
  private int optAbbrevLen = DEFAULT_ABBREV_LEN;

  //The -no-abbrev option. Displays the full SHA1s in output listing rather than abbreviating them.
  private boolean optNoAbbrev = false;

  /*
   * Non-generic options.
   */
  //The -a option. List both remote-tracking and local branches.
  private boolean optA = false;

  //The -l option. Create the branch's reflog.
  private boolean optL = false;

  //The -f option. Force creation (when already exists).
  private boolean optF = false;

  //The --merged option. List only branches merged with HEAD.
  private boolean optMerged = false;

  //The --no-merged option. List only branches not merged with HEAD.
  private boolean optNoMerged = false;

  //The -d option. Deletes a branch or the branch list.
  private boolean optDLower = false;

  //The -D option. Force deletes a branch or the branch list.
  private boolean optDUpper = false;

  //The -m option. Moves old branch to new branch.
  private boolean optMLower = false;

  //The -M option. Moves old branch to new branch.
  private boolean optMUpper = false;

  /**
   * Indicates if the verbose option should be used.
   *
   * @return True if the verbose option should be used, false otherwise.
   */
  public boolean isOptVerbose() {
    return optVerbose;
  }

  /**
   * Checks whether the verbose option should be set and sets it.
   *
   * @param optVerbose
   *        True if the verbose option should be used, false otherwise.
   */
  public void setOptVerbose(boolean optVerbose) {
    checkCanSetNoArgOption("--verbose");
    if ((false == optVerbose) && (optAbbrev || optNoAbbrev)) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  --no-abbrev or --abbrev can only be used with --verbose.");
    }
    this.optVerbose = optVerbose;
  }

  /**
   * Indicates if the --abbrev option should be used.
   *
   * @return True if the --abbrev option should be used, false otherwise.
   */
  public boolean isOptAbbrev() {
    return optAbbrev;
  }

  /**
   * Checks whether the --abbrev option should be set and sets it.
   *
   * @param optAbbrev True if the --abbrev option should be used, false otherwise.
   */
  public void setOptAbbrev(boolean optAbbrev) {
    if (optVerbose) {
      if (optAbbrev && optNoAbbrev) {
        throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
            + "  --abbrev cannot be used with --no-abbrev.");
      }
      this.optAbbrev = optAbbrev;
    }
  }

  /**
   * Indicates if the length other than default length should be used for --abbrev option.
   *
   * @return An integer value for abbrev length if the --abbrev option should be used.
   */
  public int getOptAbbrevLen() {
    return optAbbrevLen;
  }

  /**
   * Sets the length to be used for --abbrev option to a value other than default length.
   *
   * @param optAbbrevLen An integer value for abbrev length if the --abbrev option should be used.
   */
  public void setOptAbbrevLen(int optAbbrevLen) {
    if (isOptAbbrev()) {
      this.optAbbrevLen = optAbbrevLen;
    }
  }

  /**
   * Indicates if the --no-abbrev option should be used.
   *
   * @return True if the --no-abbrev option should be used, false otherwise.
   */
  public boolean isOptNoAbbrev() {
    return optNoAbbrev;
  }

  /**
   * Checks whether the --no-abbrev option should be set and sets it.
   *
   * @param optNoAbbrev True if the --no-abbrev option should be used, false otherwise.
   */
  public void setOptNoAbbrev(boolean optNoAbbrev) {
    if (optVerbose) {
      if (optAbbrev && optNoAbbrev) {
        throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
            + "  --no-abbrev cannot be used with --abbrev.");
      }
      this.optNoAbbrev = optNoAbbrev;
    }
  }

  /**
   * Indicates if the --track option should be used.
   *
   * @return True if the --track option should be used, false otherwise.
   */
  public boolean isOptTrack() {
    return optTrack;
  }

  /**
   * Checks whether the --track option should be set and sets it.
   *
   * @param optTrack True if the --track option should be used, false otherwise.
   */
  public void setOptTrack(boolean optTrack) {
    checkCanSetCreateOption("--track");
    if (optNoTrack && optTrack) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  --track cannot be used with --no-track.");
    }
    this.optTrack = optTrack;
  }

  /**
   * Indicates if the --no-track option should be used.
   *
   * @return True if the --no-track option should be used, false otherwise.
   */
  public boolean isOptNoTrack() {
    return optNoTrack;
  }

  /**
   * Checks whether the --no-track option should be set and sets it.
   *
   * @param optNoTrack True if the --no-track option should be used, false otherwise.
   */
  public void setOptNoTrack(boolean optNoTrack) {
    checkCanSetNoArgOption("--no-track");
    if (optNoTrack && optTrack) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  --no-track cannot be used with --track.");
    }
    this.optNoTrack = optNoTrack;
  }

  /**
   * Indicates if the --color option should be used.
   *
   * @return True if the --color option should be used, false otherwise.
   */
  public boolean isOptColor() {
    return optColor;
  }

  /**
   * Checks whether the --color option should be set and sets it.
   *
   * @param optColor True if the --color option should be used, false otherwise.
   */
  public void setOptColor(boolean optColor) {
    checkCanSetNoArgOption("--color");
    if (optNoColor && optColor) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  --color cannot be used with --no-color.");
    }
    this.optColor = optColor;
  }

  /**
   * Indicates if the --no-color option should be used.
   *
   * @return True if the --no-color option should be used, false otherwise.
   */
  public boolean isOptNoColor() {
    return optNoColor;
  }

  /**
   * Checks whether the --no-color option should be set and sets it.
   *
   * @param optNoColor True if the --no-color option should be used, false otherwise.
   */
  public void setOptNoColor(boolean optNoColor) {
    checkCanSetNoArgOption("--no-color");
    if (optNoColor && optColor) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  --no-color cannot be used with --color.");
    }
    this.optNoColor = optNoColor;
  }

  /**
   * Indicates if the -r option should be used.
   *
   * @return True if the -r option should be used, false otherwise.
   */
  public boolean isOptR() {
    return optR;
  }

  /**
   * Checks whether the -r option should be set and sets it.
   *
   * @param optR True if the -r option should be used, false otherwise.
   */
  public void setOptR(boolean optR) {
    if (true == optR) {
      checkCanSetNoArgOption("-r");
    }
    this.optR = optR;
  }

  /**
   * Indicates if the --contains option should be used. This is the commit value for --contains.
   *
   * @return Ref
   *          Ref if the verbose option should be used, null otherwise.
   */
  public Ref getOptContains() {
    return optContains;
  }

  /**
   * Checks if the --contains option should be used and sets it to the commit Ref.
   *
   * @param commit
   *          Commit Ref if the --contains option should be used, null otherwise.
   */
  public void setOptContains(Ref commit) {
    if (null != commit) {
      checkCanSetNoArgOption("--contains");
    }
    this.optContains = commit;
  }

  /**
   * Indicates if the -a option should be used.
   *
   * @return True if the -a option should be used, false otherwise.
   */
  public boolean isOptA() {
    return optA;
  }

  /**
   * Checks whether the -a option should be set and sets it.
   *
   * @param optA True if the -a option should be used, false otherwise.
   */
  public void setOptA(boolean optA) {
    checkCanSetNoArgOption("-a");
    this.optA = optA;
  }

  /**
   * Indicates if the -l option should be used.
   *
   * @return True if the -l option should be used, false otherwise.
   */
  public boolean isOptL() {
    return optL;
  }

  /**
   * Checks whether the -l option should be set and sets it.
   *
   * @param optL True if the -l option should be used, false otherwise.
   */
  public void setOptL(boolean optL) {
    checkCanSetCreateOption("-l");
    this.optL = optL;
  }

  /**
   * Indicates if the -f option should be used.
   *
   * @return True if the -f option should be used, false otherwise.
   */
  public boolean isOptF() {
    return optF;
  }

  /**
   * Checks whether the -f option should be set and sets it.
   *
   * @param optF True if the -f option should be used, false otherwise.
   */
  public void setOptF(boolean optF) {
    checkCanSetCreateOption("-f");
    this.optF = optF;
  }

  /**
   * Indicates if the --merged option should be used.
   *
   * @return True if the --merged option should be used, false otherwise.
   */
  public boolean isOptMerged() {
    return optMerged;
  }

  /**
   * Checks whether the --merged option should be set and sets it.
   *
   * @param optMerged True if the --merged option should be used, false otherwise.
   */
  public void setOptMerged(boolean optMerged) {
    checkCanSetNoArgOption("--merged");
    if (optNoMerged && optMerged) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  --merged cannot be used with --no-merged.");
    }
    this.optMerged = optMerged;
  }

  /**
   * Indicates if the --no-merged option should be used.
   *
   * @return True if the --no-merged option should be used, false otherwise.
   */
  public boolean isOptNoMerged() {
    return optNoMerged;
  }

  /**
   * Checks whether the --no-merged option should be set and sets it.
   *
   * @param optNoMerged True if the --no-merged option should be used, false otherwise.
   */
  public void setOptNoMerged(boolean optNoMerged) {
    checkCanSetNoArgOption("--no-merged");
    if (optNoMerged && optMerged) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  --no-merged cannot be used with --merged.");
    }
    this.optNoMerged = optNoMerged;
  }

  /**
   * Indicates if the -d option should be used.
   *
   * @return True if the -d option should be used, false otherwise.
   */
  public boolean isOptDLower() {
    return optDLower;
  }

  /**
   * Checks whether the -d option should be set and sets it.
   *
   * @param optDLower True if the -d option should be used, false otherwise.
   */
  public void setOptDLower(boolean optDLower) {
    checkCanSetDeleteOption("-d");
    if (optDLower && optDUpper) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  -d cannot be used with -D.");
    }
    this.optDLower = optDLower;
  }

  /**
   * Indicates if the -D option should be used.
   *
   * @return True if the -D option should be used, false otherwise.
   */
  public boolean isOptDUpper() {
    return optDUpper;
  }

  /**
   * Checks whether the -D option should be set and sets it.
   *
   * @param optDUpper True if the -D option should be used, false otherwise.
   */
  public void setOptDUpper(boolean optDUpper) {
    checkCanSetDeleteOption("-D");
    if (optDLower && optDUpper) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  -D cannot be used with -d.");
    }
    this.optDUpper = optDUpper;
  }

  /**
   * Indicates if the -m option should be used.
   *
   * @return True if the -m option should be used, false otherwise.
   */
  public boolean isOptMLower() {
    return optMLower;
  }

  /**
   * Checks whether the -m option should be set and sets it.
   *
   * @param optMLower True if the -m option should be used, false otherwise.
   */
  public void setOptMLower(boolean optMLower) {
    checkCanSetRenameOption("-m");
    if (optMLower && optMUpper) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  -m cannot be used with -M.");
    }
    this.optMLower = optMLower;
  }

  /**
   * Indicates if the -M option should be used.
   *
   * @return True if the -M option should be used, false otherwise.
   */
  public boolean isOptMUpper() {
    return optMUpper;
  }

  /**
   * Checks whether the -M option should be set and sets it.
   *
   * @param optMUpper True if the -M option should be used, false otherwise.
   */
  public void setOptMUpper(boolean optMUpper) {
    checkCanSetRenameOption("-M");
    if (optMLower && optMUpper) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + "  -M cannot be used with -m.");
    }
    this.optMUpper = optMUpper;
  }

  /**
   * Checks whether an option for displaying branches can be set. If not, throws an
   * IllegalArgumentException.
   *
   * @param option
   *        The name of the option being checked; for use in exception messages.
   */
  public void checkCanSetNoArgOption(String option) {
    if (isOptTrack() || isOptNoTrack() || isOptL() || isOptF() || isOptDLower() || isOptDUpper()
        || isOptMLower() || isOptMUpper()) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120")
          + option + " should be used without arguments, to display branches");
    }
  }

  /**
   * Checks whether an option for creating a branch can be set. If not, throws an
   * IllegalArgumentException.
   *
   * @param option
   *        The name of the option being checked; for use in exception messages.
   */
  public void checkCanSetCreateOption(String option) {
    if (isOptColor() || isOptNoColor() || isOptR() || isOptA() || isOptVerbose() ||
        isOptMerged() || isOptNoMerged() || (null != getOptContains()) || isOptMLower() ||
        isOptMUpper() || isOptDLower() || isOptDUpper()) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120") + option +
      " should be used with a branch name and optional start point, to create a branch");
    }
  }

  /**
   * Checks whether an option for deleting a branch or branch list can be set. If not, throws an
   * IllegalArgumentException.
   *
   * @param option
   *        The name of the option being checked; for use in exception messages.
   */
  public void checkCanSetDeleteOption(String option) {
    if (isOptColor() || isOptNoColor() || isOptA() || isOptVerbose() || isOptMerged() ||
        isOptNoMerged() || (null != getOptContains()) || isOptTrack() || isOptNoTrack() ||
        isOptL() || isOptF() || isOptMLower() || isOptMUpper()) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120") + option +
      " should be used with branch(es), to delete the branch(es).");
    }
  }

  /**
   * Checks whether an option for renaming a branch can be set. If not, throws an
   * IllegalArgumentException.
   *
   * @param option
   *        The name of the option being checked; for use in exception messages.
   */
  public void checkCanSetRenameOption(String option) {
    if (isOptColor() || isOptNoColor() || isOptR() || isOptA() || isOptVerbose() ||
        isOptMerged() || isOptNoMerged() || (null != getOptContains()) || isOptTrack() ||
        isOptNoTrack() || isOptL() || isOptF() || isOptDLower() || isOptDUpper()) {
      throw new IllegalArgumentException(ExceptionMessageMap.getMessage("000120") + option +
      " should be used with optional oldbranch and newbranch, to rename oldbranch/current branch" +
      "to newbranch.");
    }
  }
}


package org.ucmtwine.proxy;

import intradoc.common.ExecutionContext;
import intradoc.common.LocaleUtils;
import intradoc.common.ScriptExtensionsAdaptor;
import intradoc.common.ScriptInfo;
import intradoc.common.ServiceException;
import intradoc.common.SystemUtils;
import intradoc.server.Service;
import intradoc.server.script.ScriptExtensionUtils;
import intradoc.shared.UserData;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import net.balusc.util.ObjectConverter;

import org.ucmtwine.annotation.IdocFunction;
import org.ucmtwine.annotation.IdocVariable;
import org.ucmtwine.parameter.ParameterMarshaller;

public class ScriptProxy extends ScriptExtensionsAdaptor {
  private List<Class<?>[]> functionParameterTypes;

  private List<Class<?>[]> variableParameterTypes;

  private List<String> functionMethodNames;

  private List<String> variableMethodNames;

  private Class<?> m_class;

  private Method functionMethods[];

  public static final int RETURN_VOID = -1;
  public static final int RETURN_STRING = 0;
  public static final int RETURN_BOOLEAN = 1;
  public static final int RETURN_INTEGER = 2;
  public static final int RETURN_FLOAT = 3;

  public static final int NONE = -1;

  public ScriptProxy(Class<?> klass) {
    m_class = klass;
    generateDefinitions();
  }

  /**
   * Configuration data for functions. This list must align with the
   * "m_functionTable" list. In order the values are "id number",
   * "Number of arguments", "First argument type", "Second argument type",
   * "Return Type". Return type has the following possible values: 0 generic
   * object (such as strings) 1 boolean 2 integer 3 double. The value "-1" means
   * the value is unspecified.
   *
   * @param klass
   *          The annotated class
   */
  private void generateDefinitions() {
    functionParameterTypes = new ArrayList<Class<?>[]>();
    functionMethodNames = new ArrayList<String>();
    variableParameterTypes = new ArrayList<Class<?>[]>();
    variableMethodNames = new ArrayList<String>();

    List<String> functionNames = new ArrayList<String>();
    List<ParameterMarshaller> functionParams = new ArrayList<ParameterMarshaller>();
    List<Integer> functionReturnTypes = new ArrayList<Integer>();

    List<String> variableNames = new ArrayList<String>();
    List<Integer> variableReturnTypes = new ArrayList<Integer>();

    Method ms[] = m_class.getDeclaredMethods();

    functionMethods = new Method[ms.length];

    Map<String, Method> methods = new TreeMap<String, Method>();

    for (Method m : ms) {
      methods.put(m.getName(), m);
    }

    int functionCounter = 0;

    for (String methodName : methods.keySet()) {
      Method m = methods.get(methodName);
      IdocFunction functionInfo = m.getAnnotation(IdocFunction.class);

      if (functionInfo != null) {
        if (functionInfo.name().equals("")) {
          functionNames.add(m.getName());
        } else {
          functionNames.add(functionInfo.name());
        }
        functionMethodNames.add(m.getName());
        functionMethods[functionCounter] = m;
        functionParams.add(new ParameterMarshaller(m));
        functionReturnTypes.add(getFunctionReturnType(m));
        functionParameterTypes.add(m.getParameterTypes());
        functionCounter++;

      } else {
        IdocVariable varInfo = m.getAnnotation(IdocVariable.class);

        if (varInfo != null) {
          if (varInfo.name().equals("")) {
            variableNames.add(m.getName());
          } else {
            variableNames.add(varInfo.name());
          }
          variableMethodNames.add(m.getName());
          variableReturnTypes.add(getVariableReturnType(m));
          variableParameterTypes.add(m.getParameterTypes());
        }
      }
    }

    writeVariableTables(variableNames, variableReturnTypes);
    writeFunctionTables(functionNames, functionParams, functionReturnTypes);
  }

  /**
   * Build a static int[m][2] array of variable info where the int array is
   * <code>{ variable_index, return_type }</code>.
   *
   * @param functionParams
   */
  private void writeVariableTables(List<String> variableNames, List<Integer> variableReturnTypes) {
    m_variableTable = variableNames.toArray(new String[variableNames.size()]);

    m_variableDefinitionTable = new int[variableNames.size()][2];

    for (int i = 0; i < variableNames.size(); i++) {
      m_variableDefinitionTable[i][0] = i;
      m_variableDefinitionTable[i][1] = variableReturnTypes.get(i);
    }
  }

  /**
   * Build a static int[m][n] array of function info from a dynamic array where
   * n changes size. The last parameter will always remain the last parameter
   * but any short rows will pad their parameter types as -1 (type not
   * specified)
   *
   * @param functionParams
   */
  private void writeFunctionTables(List<String> functionNames, List<ParameterMarshaller> functionParams,
      List<Integer> returnTypes) {
    m_functionTable = functionNames.toArray(new String[functionNames.size()]);

    int maxParams = 0;

    // find the max of all parameter counts
    for (int i = 0; i < functionParams.size(); i++) {
      int paramCount = functionParams.get(i).getParameterCount(false);
      if (paramCount > maxParams) {
        maxParams = paramCount;
      }
    }

    m_functionDefinitionTable = new int[functionParams.size()][maxParams + ParameterMarshaller.EXTRA_FUNC_DEF_VALUES];

    // output params now
    for (int i = 0; i < functionParams.size(); i++) {
      m_functionDefinitionTable[i] = functionParams.get(i).getFunctionDefinitionArray(i, maxParams, returnTypes.get(i));
    }
  }

  /**
   * Get idoc function return type.
   *
   * @param m
   * @return
   */
  public Integer getFunctionReturnType(Method m) {
    Class<?> type = m.getReturnType();

    if (type == Void.class || type == void.class) {
      return RETURN_VOID;
    }

    if (type == Boolean.class || type == boolean.class) {
      return RETURN_BOOLEAN;
    }

    if (type == Integer.class || type == int.class || type == Long.class || type == long.class) {
      return RETURN_INTEGER;
    }

    if (type == Float.class || type == float.class || type == Double.class || type == double.class) {
      return RETURN_FLOAT;
    }

    return RETURN_STRING;
  }

  /**
   * Get idoc variable type (limited to string or conditional).
   *
   * @param m
   * @return
   */
  public Integer getVariableReturnType(Method m) {
    if (m.getReturnType() == Boolean.class || m.getReturnType() == boolean.class) {
      return RETURN_BOOLEAN;
    }
    return RETURN_STRING;
  }

  /**
   * This is where the custom IdocScript function is evaluated.
   */
  public boolean evaluateFunction(ScriptInfo info, Object[] args, ExecutionContext context) throws ServiceException {
    /**
     * This code below is optimized for speed, not clarity. Do not modify the
     * code below when making new IdocScript functions. It is needed to prepare
     * the necessary variables for the evaluation and return of the custom
     * IdocScript functions. Only customize the switch statement below.
     */
    int config[] = (int[]) info.m_entry;
    String functionCalled = info.m_key;
    int functionIndex = config[0];

    int nargs = args.length - 1;
    int allowedParams = config[1];
    if (allowedParams >= 0 && allowedParams != nargs) {
      String msg = LocaleUtils.encodeMessage("csScriptEvalNotEnoughArgs", null, functionCalled, "" + allowedParams);
      throw new IllegalArgumentException(msg);
    }

    UserData userData = (UserData) context.getCachedObject("UserData");
    if (userData == null) {
      String msg = LocaleUtils.encodeMessage("csUserDataNotAvailable", null, functionCalled);
      throw new ServiceException(msg);
    }

    if (functionIndex > m_functionTable.length) {
      SystemUtils.trace("twine", "Unknown function with index" + functionIndex);
      return false;
    }

    try {
      args[nargs] = runFunctionMethod(functionIndex, args, context);
    } catch (Exception e) {
      String msg = e.getMessage();
      if (e instanceof InvocationTargetException) {
        msg = ((InvocationTargetException) e).getTargetException().getMessage();
      }
      msg = "Unable to execute function '" + functionCalled + "()': " + msg;
      SystemUtils.err(e, msg);
      SystemUtils.trace("twine", msg);

      throw new ServiceException(e);
    }

    // Handled function.
    return true;
  }

  /**
   * Enumerates the correct parameters for the delegated method. Injects any
   * autowired types if present in the method signature.
   *
   * @param method
   * @param args
   * @param ctx
   * @return
   * @throws IllegalArgumentException
   * @throws ServiceException
   */
  public Object[] getInjectedValueArray(Method method, Object[] args, ExecutionContext ctx)
      throws IllegalArgumentException, ServiceException {

    ParameterMarshaller marshaller = new ParameterMarshaller(method);

    if ((ctx instanceof Service) == false) {
      throw new ServiceException("Tried to create parameters with injection and not inside a service.");
    }

    return marshaller.getValueArray(args, (Service) ctx);
  }

  /**
   * Executes the annotated method.
   *
   * @param functionIndex
   * @param args
   * @param ctx
   * @return
   * @throws IllegalArgumentException
   * @throws ServiceException
   * @throws SecurityException
   * @throws NoSuchMethodException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  public Object runFunctionMethod(int functionIndex, Object[] args, ExecutionContext ctx) throws SecurityException,
      NoSuchMethodException, IllegalArgumentException, ServiceException, IllegalAccessException,
      InvocationTargetException {
    Method method = functionMethods[functionIndex];

    Object params[] = getInjectedValueArray(method, args, ctx);

    Object result;
    try {
      result = method.invoke(m_class.newInstance(), params);

    } catch (InstantiationException e) {
      // TODO catch and re-throw ewwwww
      throw new ServiceException("Cannot delegate instantiate script context: " + e.getMessage());
    }

    if (result == null) {
      return result;
    }

    return convertReturnValue(result);
  }

  /**
   * Convert the method return value into a PageMerger internal type of String,
   * Long or Double
   *
   * @param result
   * @return
   */
  private Object convertReturnValue(Object result) {
    if (boolean.class.isInstance(result) || result instanceof Boolean) {
      return ScriptExtensionUtils.computeReturnObject(1, ((Boolean) result).booleanValue(), 0, 0.0, null);

    } else if (long.class.isInstance(result)) {
      return (Long) result;

    } else if (int.class.isInstance(result) || result instanceof Integer) {
      return new Long((Integer) result);

    } else if (double.class.isInstance(result)) {
      return (Double) result;

    }

    // String/Double/Long/Float
    return result;
  }

  /**
   * This is where the custom IdocScript variable is evaluated.
   */
  public boolean evaluateValue(ScriptInfo info, boolean[] returnBool, String[] returnString, ExecutionContext context,
      boolean isConditional) throws ServiceException {
    /**
     * This code, like the beginning block of code in evaluateFunction, is
     * required for preparing the data for evaluation. It should not be altered.
     * Only customize the switch statement below.
     */
    int config[] = (int[]) info.m_entry;
    String key = info.m_key;

    if ((context instanceof Service) == false) {
      // Some variables will evaluate trivially instead of throwing an
      // exception.
      if (config[1] == RETURN_BOOLEAN) {
        returnBool[0] = false;
        returnString[0] = "";
        return true;
      }

      throw new ServiceException("Script variable " + key + " must have be evaluated in "
          + "context of a Service object.");
    }

    UserData userData = (UserData) context.getCachedObject("UserData");

    if (userData == null) {
      throw new ServiceException("Script variable " + key + " must have user data context.");
    }

    int variableIndex = config[0];
    String variableRequested = m_variableTable[variableIndex];

    if (variableIndex > m_variableTable.length) {
      return false; // unknown variable
    }

    Object result = null;

    try {
      result = runVariableMethod(variableIndex, context);

    } catch (Exception e) {
      String msg = "Unable to handle variable " + variableRequested + ": " + e.getMessage();
      SystemUtils.err(e, msg);
      SystemUtils.trace("twine", msg);
      throw new ServiceException(msg);
    }

    if (isConditional) {
      returnBool[0] = ObjectConverter.convert(result, boolean.class);
    } else {
      returnString[0] = ObjectConverter.convert(result, String.class);
    }

    return true;
  }

  public Object runVariableMethod(int variableIndex, ExecutionContext ctx) throws SecurityException,
      NoSuchMethodException, IllegalArgumentException, ServiceException, IllegalAccessException,
      InvocationTargetException {
    Method method = m_class
        .getMethod(variableMethodNames.get(variableIndex), variableParameterTypes.get(variableIndex));

    // faked argument array for injection.
    Object args[] = { 0 };

    Object params[] = getInjectedValueArray(method, args, ctx);

    Object result;
    try {
      result = method.invoke(m_class.newInstance(), params);

    } catch (InstantiationException e) {
      // TODO catch and re-throw ewwwww
      throw new ServiceException("Cannot delegate instantiate script context: " + e.getMessage());
    }

    if (result.getClass() == boolean.class || result.getClass() == Boolean.class) {
      return ScriptExtensionUtils.computeReturnObject(1, ((Boolean) result).booleanValue(), 0, 0.0, null);

    } else if (result.getClass() == Integer.class || result.getClass() == int.class || result.getClass() == long.class) {
      return (Long) result;

    } else if (result.getClass() == double.class) {
      return (Double) result;
    }

    // String/Object
    return result;

  }
}


/*
 * ====================================================================
 * Copyright (c) 2008 JavaGit Project.  All rights reserved.
 *
 * This software is licensed using the GNU LGPL v2.1 license.  A copy
 * of the license is included with the distribution of this source
 * code in the LICENSE.txt file.  The text of the license can also
 * be obtained at:
 *
 *   http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *
 * For more information on the JavaGit project, see:
 *
 *   http://www.javagit.com
 * ====================================================================
 */
package edu.nyu.cs.javagit.client.cli;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;

import edu.nyu.cs.javagit.api.JavaGitConfiguration;
import edu.nyu.cs.javagit.api.JavaGitException;
import edu.nyu.cs.javagit.api.Ref;
import edu.nyu.cs.javagit.api.commands.GitStatusOptions;
import edu.nyu.cs.javagit.api.commands.GitStatusResponse;
import edu.nyu.cs.javagit.client.GitStatusResponseImpl;
import edu.nyu.cs.javagit.client.IGitStatus;
import edu.nyu.cs.javagit.utilities.CheckUtilities;
import edu.nyu.cs.javagit.utilities.ExceptionMessageMap;

/**
 * Command-line implementation of the IGitStatus interface.
 *
 * TODO - Need to parse -v option in a better way. Currently GitStatusResponse
 * does not save any output related to -v options such as lines containing diffs, or +++ etc.
 */
public class CliGitStatus implements IGitStatus {

  /**
   * Patterns for matching lines for deleted files, modified files, new files and empty lines.
   */
  public static enum Patterns {
    DELETED("^#\\s+deleted:\\s+.*"),
    MODIFIED("^#\\s+modified:\\s+.*"),
    NEW_FILE("^#\\s+new file:\\s+.*"),
    EMPTY_HASH_LINE("^#\\s*$"),
    RENAMED("^#\\s+renamed:\\s+.*");

    String pattern;

    Patterns(String pattern) {
      this.pattern = pattern;
    }

    public boolean matches(String line) {
      return line.matches(this.pattern);
    }
  }

  private File inputFile = null;

  /**
   * Implementation of IGitStatus method for getting the status of a list of files
   *
   */
  public GitStatusResponse status(File repositoryPath, GitStatusOptions options, List<File> paths)
      throws JavaGitException, IOException {
    CheckUtilities.checkNullArgument(repositoryPath, "RepositoryPath");
    CheckUtilities.checkFileValidity(repositoryPath);
    List<String> command = buildCommandLine(options, paths);
    GitStatusParser parser;
    if (inputFile != null) {
      parser = new GitStatusParser(repositoryPath.getPath() + File.separator, inputFile);
    } else {
      parser = new GitStatusParser(repositoryPath.getPath() + File.separator);
    }
    GitStatusResponse response = (GitStatusResponseImpl) ProcessUtilities.runCommand(repositoryPath,
        command, parser);
    return response;
  }

  /**
   * Implementation of IGitStatus method for getting the status of a file.
   */
  public GitStatusResponse status(File repositoryPath, GitStatusOptions options, File file)
  throws JavaGitException, IOException {
    List<File> paths = new ArrayList<File>();
    paths.add(file);
    return status(repositoryPath, options, paths);
  }

  /**
   * Implementation of IGitStatus method with only options passed to <git-status> command.
   */
  public GitStatusResponse status(File repositoryPath, GitStatusOptions options)
  throws JavaGitException, IOException {
    List<File> paths = null;
    return status( repositoryPath, options, paths);
  }

  /**
   * Implementation of IGitStatus method with file-paths passed to <git-status> command.
   */
  public GitStatusResponse status(File repositoryPath, List<File> paths) throws JavaGitException,
  IOException {
    return status(repositoryPath, null, paths);
  }

  /**
   * Implementation of IGitStatus method for getting the status of repository
   * with no options or files provided.
   */
  public GitStatusResponse status(File repositoryPath) throws JavaGitException, IOException {
    GitStatusOptions options = null;
    List<File> paths = null;
    return status(repositoryPath, options, paths);
  }

  /**
   * Implementation of IGitStatus method with options set to all(-a)
   */
  public GitStatusResponse statusAll(File repositoryPath) throws JavaGitException, IOException {
    GitStatusOptions options = new GitStatusOptions();
    options.setOptAll(true);
    return status(repositoryPath, options);
  }

  /**
   * Return status for a single File
   *
   * @param repositoryPath
   *          Directory path to the root of the repository.
   * @param options
   *          Options that are passed to <git-status> command.
   * @param file
   *          File instance
   * @return GitStatusResponse object
   * @throws JavaGitException
   *           Exception thrown if the repositoryPath is null
   * @throws IOException
   *           Exception is thrown if any of the IO operations fail.
   */
  public GitStatusResponse getSingleFileStatus(File repositoryPath, GitStatusOptions options, File file)
    throws JavaGitException, IOException {
    CheckUtilities.checkNullArgument(repositoryPath, "RepositoryPath");
    CheckUtilities.checkFileValidity(repositoryPath);
    List<String> command  = buildCommandLine(options, null);
    GitStatusParser parser = new GitStatusParser(repositoryPath.getPath() + File.separator,
        file);

    return (GitStatusResponseImpl) ProcessUtilities.runCommand(repositoryPath, command, parser);
  }

  /**
   * Parses options provided by the GitStatusOptions object and adds them to the
   * command.
   *
   * @param options
   *          GitStatusOptions provided by <gitclipse>.
   * @param paths
   *          List of file paths.
   * @return command to be executed.
   */
  private List<String> buildCommandLine(GitStatusOptions options, List<File> paths) {
    List<String> command = new ArrayList<String>();

    command.add(JavaGitConfiguration.getGitCommand());
    command.add("status");

    if (options != null) {
      setOptions(command, options);
    }

    if (paths != null) {
      for (File file : paths) {
        command.add(file.getPath());
      }
    }
    return command;
  }

  private void setOptions(List<String> argsList, GitStatusOptions options) {
    if (options.isOptAll()) {
      argsList.add("-a");
    }
    if (options.isOptQuiet()) {
      argsList.add("-q");
    }
    if (options.isOptVerbose()) {
      argsList.add("-v");
    }
    if (options.isOptSignOff()) {
      argsList.add("-s");
    }
    if (options.isOptEdit()) {
      argsList.add("-e");
    }
    if (options.isOptInclude()) {
      argsList.add("-i");
    }
    if (options.isOptOnly()) {
      argsList.add("-o");
    }
    if (options.isOptNoVerify()) {
      argsList.add("-n");
    }
    if (options.isOptUntrackedFiles()) {
      argsList.add("--untracked-files");
    }
    if (options.isOptAllowEmpty()) {
      argsList.add("--allow-empty");
    }
    if (!options.isOptReadFromLogFileNull()) {
      argsList.add("-F");
      argsList.add(options.getOptReadFromLogFile().getPath());
    }
    if (!options.isAuthorNull()) {
      argsList.add("--author");
      argsList.add(options.getAuthor());
    }
  }

  public static class GitStatusParser implements IParser {

    private enum State {
      FILES_TO_COMMIT, NOT_UPDATED, UNTRACKED_FILES
    }

    private State outputState;
    private int lineNum;
    private GitStatusResponseImpl response;
    private File inputFile = null;

    // The working directory for the command that was run.
    private String workingDirectory;

    public GitStatusParser(String workingDirectory) {
      this.workingDirectory = workingDirectory;
      lineNum = 0;
      response = new GitStatusResponseImpl(workingDirectory);
    }

    public GitStatusParser(String workingDirectory, File in) {
      this.workingDirectory = workingDirectory;
      inputFile = in;
      lineNum = 0;
      response = new GitStatusResponseImpl(workingDirectory);
    }

    public void parseLine(String line) {
      //System.out.println(line);
      if (line == null || line.length() == 0) {
        return;
      }
      ++lineNum;
      if ( isError(line) ) {
        return;
      }
      if (lineNum == 1) {
        parseLineOne(line);
      } else {
        parseOtherLines(line);
      }
    }

    /*
     * Seems like a valid ( non-error ) line 1 always start
     * with a '#' and contains the branch name.
     */
    private void parseLineOne(String line) {
      if (!line.startsWith("#")) {
        return;
      }
      String branch = getBranch(line);
      if (branch != null) {
        String branchName = getBranch(line);
        response.setBranch(Ref.createBranchRef(branchName));
      }
    }

    private void parseOtherLines(String line) {
      if (!(line.charAt(0) == '#')) {
        response.setStatusOutputComment(line);
        return;
      }
      if (line.contains("Changes to be committed") || line.contains("Changes not staged for commit") ) {
        outputState = State.FILES_TO_COMMIT;
        return;
      } else if (line.contains("Changed but not updated")) {
        outputState = State.NOT_UPDATED;
        return;
      } else {
        if (line.contains("Untracked files")) {
          outputState = State.UNTRACKED_FILES;
          return;
        }
      }
      if (ignoreOutput(line)) {
        return;
      }
      if (Patterns.DELETED.matches(line)) {
        String deletedFile = getFilename(line);
        if ((inputFile != null) && (!deletedFile.matches(inputFile.getName())))
          return;
        addDeletedFile(deletedFile);
        return;
      }
      if (Patterns.MODIFIED.matches(line)) {
        String modifiedFile = getFilename(line);
        if ((inputFile != null) && (!modifiedFile.matches(inputFile.getName())))
          return;
        addModifiedFile(modifiedFile);
        return;
      }
      if (Patterns.NEW_FILE.matches(line)) {
        String newFile = getFilename(line);
        if ((inputFile != null) && (!newFile.matches(inputFile.getName())))
          return;
        addNewFile(newFile);
        return;
      }
      if (outputState == State.UNTRACKED_FILES) {
        String untrackedFile = getFilename(line);
        if ((inputFile != null) && (!untrackedFile.matches(inputFile.getName())))
          return;
        addUntrackedFile(untrackedFile);
      }
      if ( Patterns.RENAMED.matches(line)) {
    	String renamedFile = getFilename(line);
    	if ((inputFile != null) && (!renamedFile.matches(inputFile.getName())))
          return;
    	addRenamedFileToCommit(renamedFile);
      }
    }

    private boolean isError(String line) {
      if (line.startsWith("fatal") || line.startsWith("Error") || line.startsWith("error")) {
        response.setError(lineNum, line);
        return true;
      }
      return false;
    }

    private void addNewFile(String filename) {
      response.addToNewFilesToCommit(new File(workingDirectory + filename));
    }

    private void addDeletedFile(String filename) {
      File file = new File(workingDirectory + filename);
      switch (outputState) {
      case FILES_TO_COMMIT:
        response.addToDeletedFilesToCommit(file);
        break;

      case NOT_UPDATED:
        response.addToDeletedFilesNotUpdated(file);
        break;
      }
    }

    private void addModifiedFile(String filename) {
      File file = new File(workingDirectory + filename);
      switch (outputState) {
      case FILES_TO_COMMIT:
        response.addToModifiedFilesToCommit(file);
        break;

      case NOT_UPDATED:
        response.addToModifiedFilesNotUpdated(file);
        break;
      }
    }

    private void addRenamedFileToCommit(String renamedFile) {
      response.addToRenamedFilesToCommit(new File(workingDirectory + renamedFile));
    }

    private void addUntrackedFile(String filename) {
      response.addToUntrackedFiles(new File(workingDirectory + filename));
    }

    private String getBranch(String line) {
      StringTokenizer st = new StringTokenizer(line);
      String last = null;
      while (st.hasMoreTokens()) {
        last = st.nextToken();
      }
      return last;
    }

    //

      public String getFilename(String line) {
          String filename = "";
          boolean checkForColon = line.contains(":") && line.contains("#");

          Scanner scanner = new Scanner(line);
          boolean startFileName = false;
          while (scanner.hasNext() && !startFileName) {
              String s = scanner.next();
              // If line contains only # then no deleted: new file: or other comment ending in : mark the file name start
              if (checkForColon)
                  startFileName = s.endsWith(":");
              else
                  startFileName = s.endsWith("#");
          }

          boolean continueFilename = true;
          while (scanner.hasNext() && continueFilename) {
              String s = scanner.next();
              if (s.equals("->")) {
                  filename = line.substring( line.indexOf("->") + 2 ).trim();
                  continueFilename = false;
              } else {
                  String separator = filename == "" ? "" : " ";
                  filename += separator + s;
                  continueFilename = true;
              }

          }

          return filename;
    }

    private boolean ignoreOutput(String line) {
      if (line.contains("(use \"git reset")) {
        return true;
      }
      if (line.contains("(use \"git add ")) {
        return true;
      }
      if (line.contains("(use \"git add/rm")) {
        return true;
      }
      if (Patterns.EMPTY_HASH_LINE.matches(line)) {
        return true;
      }
      return false;
    }

    public void processExitCode(int code) {
    }

    public GitStatusResponse getResponse() throws JavaGitException {
      if( response.errorState() ) {
        throw new JavaGitException(438000, ExceptionMessageMap.getMessage("438000") +
            " - git status error message: { " + response.getError() + " }");
      }
      return response;
    }
  }
}


/*
 * ====================================================================
 * Copyright (c) 2008 JavaGit Project.  All rights reserved.
 *
 * This software is licensed using the GNU LGPL v2.1 license.  A copy
 * of the license is included with the distribution of this source
 * code in the LICENSE.txt file.  The text of the license can also
 * be obtained at:
 *
 *   http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *
 * For more information on the JavaGit project, see:
 *
 *   http://www.javagit.com
 * ====================================================================
 */
package edu.nyu.cs.javagit.api.commands;

import java.io.File;
import java.io.IOException;
import java.util.List;

import edu.nyu.cs.javagit.api.JavaGitException;
import edu.nyu.cs.javagit.api.Ref;
import edu.nyu.cs.javagit.client.ClientManager;
import edu.nyu.cs.javagit.client.IClient;
import edu.nyu.cs.javagit.client.IGitBranch;
import edu.nyu.cs.javagit.utilities.CheckUtilities;

/**
 * GitBranch provides an API for git-branch operation in a git repository.
 */
public final class GitBranch {
  /**
   * Does a basic git-branch without any options.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @return The result of the git branch.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse branch(File repositoryPath) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.branch(repositoryPath);
  }

  /**
   * Perform git-branch with the specified options against the repository.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param options
   *          The options for a git-branch command. If the value is null, a
   *          NullPointerException will be thrown.
   * @return The result of the git branch.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse branch(File repositoryPath, GitBranchOptions options)
      throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(options, "options");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.branch(repositoryPath, options);
  }

  /**
   * This method deletes the specified branch using the -d command line option.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param forceDelete
   *          True if force delete option -D should be used, false if -d should be used.
   * @param remote
   *          True if delete branch should be acted upon a remote branch.
   * @param branchName
   *          A branch to be deleted.
   * @return The result of the git-branch with delete option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse deleteBranch(File repositoryPath, boolean forceDelete, boolean remote,
      Ref branchName) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(branchName, "branch name");
    CheckUtilities.validateArgumentRefType(branchName, Ref.RefType.BRANCH, "branch name");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.deleteBranch(repositoryPath, forceDelete, remote, branchName);
  }

  /**
   * Deletes the specified branches using the -d command line option.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param forceDelete
   *          True if force delete option -D should be used, false if -d should be used.
   * @param remote
   *          True if delete branch should be acted upon a remote branch list.
   * @param branchList
   *          The list of branches to be deleted.
   * @return The result of the git-branch with -d option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse deleteBranch(File repositoryPath, boolean forceDelete, boolean remote,
      List<Ref> branchList) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullListArgument(branchList, "branch list");
    CheckUtilities.validateListRefType(branchList, Ref.RefType.BRANCH, "branch list");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.deleteBranches(repositoryPath, forceDelete, remote, branchList);
  }

  /**
   * Renames the current branch to new branch.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param forceRename
   *          True if force rename option -M should be used. False if -m should be used.
   * @param newName
   *          When renaming the current branch to a new branch name, this is the new branch name.
   * @return The result of the git branch with -m option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse renameBranch(File repositoryPath, boolean forceRename, Ref newName)
      throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(newName, "new name");
    CheckUtilities.validateArgumentRefType(newName, Ref.RefType.BRANCH, "new name");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.renameBranch(repositoryPath, forceRename, newName);
  }

  /**
   * Renames old branch to new branch.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param forceRename
   *          True if force rename option -M should be used. False if -m should be used.
   * @param oldName
   *          When renaming a branch to a different name, this is the old branch name.
   * @param newName
   *          When renaming a branch to a new branch name, this is the new branch name.
   * @return The result of the git branch with -m option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse renameBranch(File repositoryPath, boolean forceRename, Ref oldName,
      Ref newName) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(oldName, "old name");
    CheckUtilities.checkNullArgument(newName, "new name");
    CheckUtilities.validateArgumentRefType(oldName, Ref.RefType.BRANCH, "old name");
    CheckUtilities.validateArgumentRefType(newName, Ref.RefType.BRANCH, "new name");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.renameBranch(repositoryPath, forceRename, oldName, newName);
  }

  /**
   * Creates a branch. Indicates to use the current working branch as the branch start point.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param branchName
   *          Name of the branch to create.
   * @return The result of the git branch with -m option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse createBranch(File repositoryPath, Ref branchName) throws IOException,
      JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(branchName, "branch name");
    CheckUtilities.validateArgumentRefType(branchName, Ref.RefType.BRANCH, "branch name");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.createBranch(repositoryPath, branchName);
  }

  /**
   * Creates a branch according to given option. Indicate to use the current working branch as
   * the branch start point.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param options
   *          The options for a git-branch command. If the value is null, a
   *          NullPointerException will be thrown.
   * @param branchName
   *          Name of the branch to create.
   * @return The result of the git branch with -m option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse createBranch(File repositoryPath, GitBranchOptions options,
      Ref branchName) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(options, "options");
    CheckUtilities.checkNullArgument(branchName, "branch name");
    CheckUtilities.validateArgumentRefType(branchName, Ref.RefType.BRANCH, "branch name");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.createBranch(repositoryPath, options, branchName);
  }

  /**
   * Creates a branch. Allow the startPoint to be null to indicate to use the current working
   * branch as the branch start point.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param branchName
   *          Name of the branch to create.
   * @param startPoint
   *          Start point of the newly created branch.
   * @return The result of the git branch with -m option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse createBranch(File repositoryPath, Ref branchName, Ref startPoint)
      throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(branchName, "branch name");
    CheckUtilities.checkNullArgument(startPoint, "start point");
    CheckUtilities.validateArgumentRefType(branchName, Ref.RefType.BRANCH, "branch name");
    CheckUtilities.validateArgumentRefType(startPoint, Ref.RefType.HEAD, "start point");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.createBranch(repositoryPath, branchName, startPoint);
  }

  /**
   * Creates a branch. Allow the startPoint to be null to indicate to use the current working
   * branch as the branch start point.
   *
   * @param repositoryPath
   *          A File instance for the path to the repository. If null is passed,
   *          a NullPointerException will be thrown.
   * @param options
   *          The options for a git-branch command. If the value is null, a
   *          NullPointerException will be thrown.
   * @param branchName
   *          Name of the branch to create.
   * @param startPoint
   *          Start point of the newly created branch.
   * @return The result of the git branch with -m option.
   * @throws IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @throws JavaGitException
   *              Thrown when there is an error executing git-branch.
   */
  public GitBranchResponse createBranch(File repositoryPath, GitBranchOptions options,
      Ref branchName, Ref startPoint) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repositoryPath, "repository path");
    CheckUtilities.checkNullArgument(options, "options");
    CheckUtilities.checkNullArgument(branchName, "branch name");
    CheckUtilities.checkNullArgument(startPoint, "start point");
    CheckUtilities.validateArgumentRefType(branchName, Ref.RefType.BRANCH, "branch name");
    CheckUtilities.validateArgumentRefType(startPoint, Ref.RefType.HEAD, "start point");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitBranch gitBranch = client.getGitBranchInstance();
    return gitBranch.createBranch(repositoryPath, options, branchName, startPoint);
  }
}


/*
 * ====================================================================
 * Copyright (c) 2008 JavaGit Project.  All rights reserved.
 *
 * This software is licensed using the GNU LGPL v2.1 license.  A copy
 * of the license is included with the distribution of this source
 * code in the LICENSE.txt file.  The text of the license can also
 * be obtained at:
 *
 *   http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *
 * For more information on the JavaGit project, see:
 *
 *   http://www.javagit.com
 * ====================================================================
 */
package edu.nyu.cs.javagit.api;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import edu.nyu.cs.javagit.api.commands.GitAddResponse;
import edu.nyu.cs.javagit.api.commands.GitBranch;
import edu.nyu.cs.javagit.api.commands.GitBranchOptions;
import edu.nyu.cs.javagit.api.commands.GitBranchResponse;
import edu.nyu.cs.javagit.api.commands.GitCheckout;
import edu.nyu.cs.javagit.api.commands.GitCommit;
import edu.nyu.cs.javagit.api.commands.GitCommitResponse;
import edu.nyu.cs.javagit.api.commands.GitStatus;
import edu.nyu.cs.javagit.api.commands.GitStatusResponse;
import edu.nyu.cs.javagit.utilities.CheckUtilities;

/**
 * The WorkingTree represents the working copy of the files in the current branch.
 *
 */
public final class WorkingTree {
  // This guy's a per-repository singleton, so we need a static place to store our instances.
  private static final Map<String, WorkingTree> INSTANCES = new HashMap<String, WorkingTree>();

  // The directory that contains the .git in question.
  private final File path;

  /*
   * The canonical pathname from this file. Store this here so that we don't need to continually hit
   * the filesystem to resolve it.
   */
  private final String canonicalPath;

  // A git-specific representation of the same place this class is pointing.
  private GitDirectory rootDir;

  /**
   * The constructor. Private because this singleton-ish (per each repository) class is only
   * available via the getInstance method.
   *
   * @param path
   *          The path to the working directory represented by the instance being created.
   *
   */
  private WorkingTree(File path, String canonicalPath) {
    this.path = path;
    this.canonicalPath = canonicalPath;
    try {
      this.rootDir = new GitDirectory(path, this);
    }
    catch(JavaGitException e) {
      //that is really impossible
    }
  }

  /**
   * Static factory method for retrieving an instance of this class.
   *
   * @param path
   *          File object representing the path to the repository.
   * @return The WorkingTree instance for this path
   */
  public static synchronized WorkingTree getInstance(File path) {
    WorkingTree workingTree;

    // TODO (rs2705): make sure that path is valid

    /*
     * We want to make sure we're dealing with the canonical path here, since there are multiple
     * ways to refer to the same dir with different strings.
     */
    String canonicalPath = "";

    try {
      canonicalPath = path.getCanonicalPath();
    } catch (Exception e) {
      /*
       * TODO (rs2705): Figure out which exception to throw here, and throw it - or should we simply
       * let it propogate up as-is?
       */
      return null; // Temporary placeholder
    }

    if (!(INSTANCES.containsKey(canonicalPath))) {
      workingTree = new WorkingTree(path, canonicalPath);
      INSTANCES.put(canonicalPath, workingTree);
    } else {
      workingTree = INSTANCES.get(canonicalPath);
    }

    return workingTree;
  }

  /**
   * Convenience method for retrieving an instance of the class using a String
   * instead of a File.
   *
   * @param path
   *          String object representing the path to the repository.
   * @return The WorkingTree instance for this path
   */
  public static WorkingTree getInstance(String path) {
    // TODO (rs2705): make sure that path is valid
    return getInstance(new File(path));
  }

  /**
   * Adds all known and modified files in the working directory to the index.
   *
   * @return response from git add
   */
  public GitAddResponse add() throws IOException, JavaGitException {
    return rootDir.add();
  }

  /**
   * Adds a directory to the working directory (but not to the repository!)
   *
   * @param dir
   *          name of the directory
   *
   * @return The new GitDirectory object
   *
   * @throws JavaGitException
   *         File path specified does not belong to git repo/ working tree
   */
  public GitDirectory addDirectory(String dir) throws JavaGitException {
    return new GitDirectory(new File(dir), this);
  }

  /**
   * Since instances of this class are singletons, don't allow cloning.
   *
   * @return None - always throws exception
   */
  @Override
  protected Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
  }

  /**
   * Commits the objects specified in the index to the repository.
   *
   * @param comment
   *          Developer's comment about the change
   *
   * @return response from git commit
   */
  public GitCommitResponse commit(String comment) throws IOException, JavaGitException {
    GitCommit gitCommit = new GitCommit();
    return gitCommit.commit(path, comment);
  }

  /**
   * Automatically stage files that have been modified and deleted, but new files you have not
   * told git about are not affected
   *
   * @param comment
   *          Developer's comment about the change
   *
   * @return response from git commit
   */
  public GitCommitResponse commitAll(String comment) throws IOException, JavaGitException {
    GitCommit gitCommit = new GitCommit();
    return gitCommit.commitAll(path, comment);
  }

  /**
   * Stage all files and commit (including untracked)
   *
   * @param comment
   *          Developer's comment about the change
   * @return GitCommitResponse object
   * @throws IOException
   *          I/O operation fails
   * @throws JavaGitException
   *          git command fails
   */
  public GitCommitResponse addAndCommitAll(String comment) throws IOException, JavaGitException {
    return rootDir.commit(comment);
  }

  @Override
  public boolean equals(Object obj) {
    if (!(obj instanceof WorkingTree)) {
      return false;
    }

    WorkingTree workingTree = (WorkingTree) obj;
    return CheckUtilities.checkObjectsEqual(canonicalPath, workingTree.canonicalPath);
  }

  /**
   * Gets the currently checked-out branch of the working directory.
   *
   * @return The currently checked-out branch of the working directory.
   */
  public Ref getCurrentBranch() throws IOException, JavaGitException {
    GitBranch gitBranch = new GitBranch();
    GitBranchOptions options = new GitBranchOptions();
    GitBranchResponse response = gitBranch.branch(path, options);
    return response.getCurrentBranch();
  }

  /**
   * Take a standard File object and return it wrapped in a GitDirectory.
   *
   * @return A new GitDirectory object representing the given File.
   *
   * @throws JavaGitException
   *         File path specified does not belong to git repo/ working tree
   */
  public GitDirectory getDirectory(File file) throws JavaGitException {
    return new GitDirectory(file, this);
  }

  /**
   * Take a standard File</code object and return it wrapped in a GitFile.
   *
   * @return A new GitFile object representing the given File.
   *
   * @throws JavaGitException
   *         File path specified does not belong to git repo/ working tree
   */
  public GitFile getFile(File file) throws JavaGitException {
    return new GitFile(file, this);
  }

  /**
   * Show commit logs
   *
   * @return List of commits for the working directory
   */
  public List<Commit> getLog() {
    // TODO (ma1683): Implement this method
    return null;
  }

  /**
   * Gets the .git representation for this git repository
   *
   * @return The DotGit
   */
  public DotGit getDotGit() {
    return DotGit.getInstance(path);
  }

  /**
   * Gets the path to the working directory represented by an instance.
   *
   * @return The path to the working directory represented by an instance.
   */
  public File getPath() {
    return path;
  }

  /**
   * Gets the filesystem tree; equivalent to git-status
   *
   * @return The list of objects at the root directory
   *
   * @throws JavaGitException
   *         File path specified does not belong to git repo/ working tree
   */
  public List<GitFileSystemObject> getTree() throws IOException, JavaGitException {
    // TODO (rs2705): Make this work - will throw NullPointerException
    return new GitDirectory(path, this).getChildren();
  }

  @Override
  public int hashCode() {
    return canonicalPath.hashCode();
  }

  /**
   * Reverts the specified git commit
   *
   * @param commit
   *          Git commit that user wishes to revert
   */
  public void revert(Commit commit) {
    // TODO (ma1683): Implement this method
    // GitRevert.revert(commit.getSHA1());
  }

  /**
   * Switches to a new branch
   *
   * @param ref
   *          Git branch/sha1 to switch to
   */
  public void checkout(Ref ref) throws IOException, JavaGitException {
    GitCheckout gitCheckout = new GitCheckout();
    gitCheckout.checkout(path, null, ref);

    /*
     * TODO (rs2705): Figure out why this function is setting this.path. When does the WorkingTree
     * path change?
     */
    // this.path = branch.getBranchRoot().getPath();
  }

  /**
   * Gets the status of all files in the working directory
   *
   * @return GitStatusResponse object
   * @throws IOException
   *         Exception is thrown if any of the IO operations fail.
   * @throws JavaGitException
   *         Exception thrown if the repositoryPath is null
   */
  public GitStatusResponse getStatus() throws IOException, JavaGitException {
    GitStatus gitStatus = new GitStatus();
    return gitStatus.status(path);
  }

}


/*
 * ====================================================================
 * Copyright (c) 2008 JavaGit Project.  All rights reserved.
 *
 * This software is licensed using the GNU LGPL v2.1 license.  A copy
 * of the license is included with the distribution of this source
 * code in the LICENSE.txt file.  The text of the license can also
 * be obtained at:
 *
 *   http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *
 * For more information on the JavaGit project, see:
 *
 *   http://www.javagit.com
 * ====================================================================
 */
package edu.nyu.cs.javagit.api.commands;

import java.io.File;
import java.io.IOException;
import java.util.List;

import edu.nyu.cs.javagit.api.JavaGitException;
import edu.nyu.cs.javagit.client.ClientManager;
import edu.nyu.cs.javagit.client.IClient;
import edu.nyu.cs.javagit.client.IGitRm;
import edu.nyu.cs.javagit.utilities.CheckUtilities;

/**
 * GitRm provides an interface to remove files from a git repository.
 */

public class GitRm {

  // TODO (jhl388): Add test cases for this class.

  /**
   * Remove files relative to the path within the repository.
   *
   * @param repository
   *          A File instance for the path to the repository root (the parent
   *          directory of the .git directory) or a sub-directory in the working tree of the
   *          repository to run rm against. This argument must represent the absolute path to the
   *          desired directory as returned by the File.getPath() method. If null is
   *          passed, a NullPointerException will be thrown.
   * @param path
   *          A single file to remove. The specified path must be relative to the path specified in
   *          the repository parameter as returned by File.getPath().
   *          If null is passed, a NullPointerException will be thrown.
   * @exception IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @exception JavaGitException
   *              Thrown when there is an error making the commit.
   */
  public GitRmResponse rm(File repository, File path) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repository, "repository");
    CheckUtilities.checkNullArgument(path, "path");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitRm GitRm = client.getGitRmInstance();
    return GitRm.rm(repository, path);
  }

  /**
   * Remove files relative to the path within the repository.
   *
   * @param repository
   *          A File instance for the path to the repository root (the parent
   *          directory of the .git directory) or a sub-directory in the working tree of the
   *          repository to run rm against. This argument must represent the absolute path to the
   *          desired directory as returned by the File.getPath() method. If null is
   *          passed, a NullPointerException will be thrown.
   * @param options
   *          The options to run rm with. If null is passed, a NullPointerException
   *          will be thrown.
   * @param path
   *          A single file to remove. The specified path must be relative to the path specified in
   *          the repository parameter as returned by File.getPath().
   *          If null is passed, a NullPointerException will be thrown.
   * @exception IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @exception JavaGitException
   *              Thrown when there is an error making the commit.
   */
  public GitRmResponse rm(java.io.File repository, GitRmOptions options, File path)
      throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repository, "repository");
    CheckUtilities.checkNullArgument(options, "options");
    CheckUtilities.checkNullArgument(path, "path");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitRm GitRm = client.getGitRmInstance();
    return GitRm.rm(repository, options, path);
  }

  /**
   * Removes files from the specified repository.
   *
   * @param repository
   *          A File instance for the path to the repository root (the parent
   *          directory of the .git directory) or a sub-directory in the working tree of the
   *          repository to run rm against. This argument must represent the absolute path to the
   *          desired directory as returned by the File.getPath() method. If null is
   *          passed, a NullPointerException will be thrown.
   * @param paths
   *          A list of files to remove. The paths specified in this list must all be relative to
   *          the path specified in the repository parameter as returned by
   *          File.getPath(). A non-null and non-empty list is required for this
   *          parameter, otherwise a NullPointerException or
   *          IllegalArgumentException will be thrown.
   * @return The results from the rm.
   * @exception IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   */
  public GitRmResponse rm(File repository, List<File> paths) throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repository, "repository");
    CheckUtilities.checkNullListArgument(paths, "paths");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitRm GitRm = client.getGitRmInstance();
    return GitRm.rm(repository, paths);
  }

  /**
   * Remove files relative to the path within the repository.
   *
   * @param repository
   *          A File instance for the path to the repository root (the parent
   *          directory of the .git directory) or a sub-directory in the working tree of the
   *          repository to run rm against. This argument must represent the absolute path to the
   *          desired directory as returned by the File.getPath() method. If null is
   *          passed, a NullPointerException will be thrown.
   * @param options
   *          The options to run rm with. If null is passed, a NullPointerException
   *          will be thrown.
   * @param paths
   *          A list of files or folders to remove. The paths specified in this list must all be
   *          relative to the path specified in the repository parameter as returned
   *          by File.getPath(). A non-null and non-empty list is required for this
   *          parameter, otherwise a NullPointerException or
   *          IllegalArgumentException will be thrown.
   * @exception IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @exception JavaGitException
   *              Thrown when there is an error making the commit.
   */
  public GitRmResponse rm(File repository, GitRmOptions options, List<File> paths)
      throws IOException, JavaGitException {
    CheckUtilities.checkNullArgument(repository, "repository");
    CheckUtilities.checkNullArgument(options, "options");
    CheckUtilities.checkNullListArgument(paths, "paths");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitRm GitRm = client.getGitRmInstance();
    return GitRm.rm(repository, options, paths);
  }

  /**
   * Remove files relative to the path within the repository but only effect the index.
   *
   * @param repository
   *          A File instance for the path to the repository root (the parent
   *          directory of the .git directory) or a sub-directory in the working tree of the
   *          repository to run rm against. This argument must represent the absolute path to the
   *          desired directory as returned by the File.getPath() method. If null is
   *          passed, a NullPointerException will be thrown.
   * @param paths
   *          A list of files to remove. The paths specified in this list must all be relative to
   *          the path specified in the repository parameter as returned by
   *          File.getPath(). A non-null and non-empty list is required for this
   *          parameter, otherwise a NullPointerException or
   *          IllegalArgumentException will be thrown.
   * @exception IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @exception JavaGitException
   *              Thrown when there is an error making the commit.
   */
  public GitRmResponse rmCached(File repository, List<File> paths) throws IOException,
      JavaGitException {
    CheckUtilities.checkNullArgument(repository, "repository");
    CheckUtilities.checkNullListArgument(paths, "paths");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitRm GitRm = client.getGitRmInstance();
    return GitRm.rmCached(repository, paths);
  }

  /**
   * Recursively remove files relative to the path within the repository.
   *
   * @param repository
   *          A File instance for the path to the repository root (the parent
   *          directory of the .git directory) or a sub-directory in the working tree of the
   *          repository to run rm against. This argument must represent the absolute path to the
   *          desired directory as returned by the File.getPath() method. If null is
   *          passed, a NullPointerException will be thrown.
   * @param paths
   *          A list of files or folders to remove. The paths specified in this list must all be
   *          relative to the path specified in the repository parameter as returned
   *          by File.getPath(). A non-null and non-empty list is required for this
   *          parameter, otherwise a NullPointerException or
   *          IllegalArgumentException will be thrown.
   * @exception IOException
   *              There are many reasons for which an IOException may be thrown.
   *              Examples include:
   *

   *
a directory doesn't exist

   *
access to a file is denied

   *
a command is not found on the PATH

   *

   * @exception JavaGitException
   *              Thrown when there is an error making the commit.
   */
  public GitRmResponse rmRecursive(File repository, List<File> paths) throws IOException,
      JavaGitException {
    CheckUtilities.checkNullArgument(repository, "repository");
    CheckUtilities.checkNullListArgument(paths, "paths");

    IClient client = ClientManager.getInstance().getPreferredClient();
    IGitRm GitRm = client.getGitRmInstance();
    return GitRm.rmRecursive(repository, paths);
  }

}


/*
 * ====================================================================
 * Copyright (c) 2008 JavaGit Project.  All rights reserved.
 *
 * This software is licensed using the GNU LGPL v2.1 license.  A copy
 * of the license is included with the distribution of this source
 * code in the LICENSE.txt file.  The text of the license can also
 * be obtained at:
 *
 *   http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *
 * For more information on the JavaGit project, see:
 *
 *   http://www.javagit.com
 * ====================================================================
 */
package edu.nyu.cs.javagit.api.commands;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import edu.nyu.cs.javagit.api.GitFileSystemObject;
import edu.nyu.cs.javagit.api.JavaGitException;
import edu.nyu.cs.javagit.api.Ref;
import edu.nyu.cs.javagit.utilities.CheckUtilities;
import edu.nyu.cs.javagit.api.GitFileSystemObject.Status;

/**
 * A response data object for <git-status> command
 */
public abstract class GitStatusResponse implements CommandResponse {

  /**
   * List of new files that will be added next time <git-commit> is executed.
   */
  protected final List<File> newFilesToCommit;
  /**
   * List of files that will be deleted next time <git-commit> is executed.
   */
  protected final List<File> deletedFilesToCommit;
  /**
   * List of files that are modified and will be committed to git repository next time
   * <git-commit> is executed.
   */
  protected final List<File> modifiedFilesToCommit;
  /**
   * List of files that are in git-repository and have been deleted locally.
   */
  protected final List<File> deletedFilesNotUpdated;
  /**
   * List of files that are in git-repository and have been modified locally but not yet added to
   * git by <git-add> command.
   */
  protected final List<File> modifiedFilesNotUpdated;
  /**
   * List of files that are in git-repository and have been renamed to some other filename.
   */
  protected final List<File> renamedFilesToCommit;
  /**
   * List of new files that have been created locally and have not been added to repository by
   * <git-add> command yet.
   */
  protected final List<File> untrackedFiles;

  /**
   * List of files that have been renamed
   */
  protected final List<File> renamedFiles;
  /**
   * Name of the branch to which HEAD is pointing to.
   */
  protected Ref branch;
  /**
   * One line message that is generated by output of <git-status> command sometimes and it
   * does not start with a '#'.
   */
  protected String message;
  /**
   * Error messages generated by <git-status> command
   */
  protected List<ErrorDetails> errors;

  /**
   * File file representing repository
   */
  protected File repository;

  /**
   * path to the repository (absolute)
   */
  String repositoryPath;

  /**
   * Mapping of file to its status
   */
  protected Map<File, GitFileSystemObject.Status> fileToStatus;

  public GitStatusResponse(String repositoryPath) {
    this.repositoryPath = repositoryPath;
    this.repository = new File(repositoryPath);
    fileToStatus = new HashMap<File, GitFileSystemObject.Status>();

    newFilesToCommit = new ArrayList<File>();
    deletedFilesToCommit = new ArrayList<File>();
    modifiedFilesToCommit = new ArrayList<File>();
    deletedFilesNotUpdated = new ArrayList<File>();
    modifiedFilesNotUpdated = new ArrayList<File>();
    renamedFilesToCommit = new ArrayList<File>();
    untrackedFiles = new ArrayList<File>();
    renamedFiles = new ArrayList<File>();
    errors = new ArrayList<ErrorDetails>();
  }

  /**
   * Get the name of the file from newFilesToCommit list at a given index.
   *
   * @param index
   *          Index in the list must be positive and less than the number of new files to commit.
   * @return the name of the file.
   */
  public File getFileFromNewFilesToCommit(int index) {
    CheckUtilities.checkIntIndexInListRange(newFilesToCommit, index);
    return newFilesToCommit.get(index);
  }

  /**
   * Get the name of the deleted file that will be committed next time git-commit is executed
   * currently located at the given index in the list.
   *
   * @param index
   *          Index in the list must be positive and less than the number of new files to commit
   * @return the name of the file.
   */
  public File getFileFromDeletedFilesToCommit(int index) {
    CheckUtilities.checkIntIndexInListRange(deletedFilesToCommit, index);
    return deletedFilesToCommit.get(index);
  }

  /**
   * Get the name of the file that is modified and added to the repository by <git-add>
   * command and will be committed to repository next time <git-commit> is executed. This file
   * is at the given index in the list of the files.
   *
   * @param index
   *          Index in the list of files must be positive and less than the number of new files to
   *          commit
   * @return the name of the file.
   */
  public File getFileFromModifiedFilesToCommit(int index) {
    CheckUtilities.checkIntIndexInListRange(modifiedFilesToCommit, index);
    return modifiedFilesToCommit.get(index);
  }

  /**
   * Returns the name of the deleted file that is removed locally but not yet removed from
   * repository.
   *
   * @param index
   *          Index in the list must be positive and less than the number of new files to commit
   * @return the name of the file.
   */
  public File getFileFromDeletedFilesNotUpdated(int index) {
    CheckUtilities.checkIntIndexInListRange(deletedFilesNotUpdated, index);
    return deletedFilesNotUpdated.get(index);
  }

  /**
   * Returns the name of the file that is existing in the repository and has been locally modified.
   * This file is one of the files that has been locally modified and is located at given index in
   * the list.
   *
   * @param index
   *          Index in the list must be positive and less than the number of new files to commit
   * @return the name of the file
   */
  public File getFileFromModifiedFilesNotUpdated(int index) {
    CheckUtilities.checkIntIndexInListRange(modifiedFilesNotUpdated, index);
    return modifiedFilesNotUpdated.get(index);
  }

  /**
   * Returns the name of the file at the specified index that has been created locally but has not
   * yet been added to the repository by <git-add>.
   *
   * @param index
   *          Index in the list must be positive and less than the number of new files to commit
   * @return the name of the file
   */
  public File getFileFromUntrackedFiles(int index) {
    CheckUtilities.checkIntIndexInListRange(untrackedFiles, index);
    return untrackedFiles.get(index);
  }

  /**
   * Returns the file at the specified index in the list of renamed files.
   *
   * @param index
   *          Index in the list must be positive and less than the number of new files to commit
   * @return the name of the file
   */
  public File getFileFromRenamedFiles(int index) {
    CheckUtilities.checkIntIndexInListRange(renamedFilesToCommit, index);
    return renamedFilesToCommit.get(index);
  }

  /**
   * Creates a copy of newFilesToCommit list and returns the Iterable on this new list.
   *
   * @return Iterable New files to commit list.
   */
  public Iterable<File> getNewFilesToCommit() {
    Iterator<File> fileIterator = new ArrayList<File>(newFilesToCommit).iterator();
    return (new IterableIterator<File>(fileIterator));
  }

  /**
   * Creates a copy of deletedFilesToCommit list and returns the Iterable on this new
   * list.
   *
   * @return Iterable Deleted Files to commit list.
   */
  public Iterable<File> getDeletedFilesToCommit() {
    Iterator<File> fileIterator = new ArrayList<File>(deletedFilesToCommit).iterator();
    return (new IterableIterator<File>(fileIterator));
  }

  /**
   * Creates a copy of modifiedFilesToComit list and returns the Iterable on this new
   * list.
   *
   * @return Iterable Modified files to commit list.
   */
  public Iterable<File> getModifiedFilesToCommit() {
    Iterator<File> fileIterator = new ArrayList<File>(modifiedFilesToCommit).iterator();
    return (new IterableIterator<File>(fileIterator));
  }

  /**
   * Creates a copy of deletedFilesNotUpdated list and returns the Iterable on this
   * new list.
   *
   * @return Iterable Deleted files not updated list.
   */
  public Iterable<File> getDeletedFilesNotUpdated() {
    Iterator<File> fileIterator = new ArrayList<File>(deletedFilesNotUpdated).iterator();
    return (new IterableIterator<File>(fileIterator));
  }

  /**
   * Creates a copy of modifiedFilesNotUpdated list and returns the Iterable on this
   * new list.
   *
   * @return Iterable Modified files not updated list.
   */
  public Iterable<File> getModifiedFilesNotUpdated() {
    Iterator<File> fileIterator = new ArrayList<File>(modifiedFilesNotUpdated).iterator();
    return (new IterableIterator<File>(fileIterator));
  }

  /**
   * Creates a copy of renamedFiles list and returns the Iterable to this new list.
   *
   * @return Iterable Renamed files list.
   */
  public Iterable<File> getRenamedFilesToCommit() {
    Iterator<File> fileIterator = new ArrayList<File>(renamedFilesToCommit).iterator();
    return (new IterableIterator<File>(fileIterator));
  }

  /**
   * Creates a copy of untrackedFiles list and returns the Iterable on this new list.
   *
   * @return Iterable Untracked files list.
   */
  public Iterable<File> getUntrackedFiles() {
    Iterator<File> fileIterator = new ArrayList<File>(untrackedFiles).iterator();
    return (new IterableIterator<File>(fileIterator));
  }

  /**
   * Returns the size of newFilesToCommit list.
   *
   * @return size of list.
   */
  public int getNewFilesToCommitSize() {
    return newFilesToCommit.size();
  }

  /**
   * Returns the size of deletedFilesToCommit list.
   *
   * @return size of the list.
   */
  public int getDeletedFilesToCommitSize() {
    return deletedFilesToCommit.size();
  }

  /**
   * Returns the size of modifiedFilesToCommit list.
   *
   * @return size of the list.
   */
  public int getModifiedFilesToCommitSize() {
    return modifiedFilesToCommit.size();
  }

  /**
   * Returns the size of deletedFilesNotUpdated list.
   *
   * @return size of the list.
   */
  public int getDeletedFilesNotUpdatedSize() {
    return deletedFilesNotUpdated.size();
  }

  /**
   * Returns the size of modifiedFilesNotUpdated list.
   *
   * @return size of the list.
   */
  public int getModifiedFilesNotUpdatedSize() {
    return modifiedFilesNotUpdated.size();
  }

  /**
   * Returns the size of renamedFiles list.
   *
   * @return size of the list.
   */
  public int getRenamedFilesToCommitSize() {
    return renamedFilesToCommit.size();
  }

  /**
   * Returns the size of untrackedFiles list.
   *
   * @return size of the list.
   */
  public int getUntrackedFilesSize() {
    return untrackedFiles.size();
  }

  /**
   * Returns the name of the current branch in git-repository.
   *
   * @return name of the branch
   */
  public Ref getBranch() {
    return branch;
  }

  /**
   *
   * @param file
   *         File object
   * @return
   *         status of the file
   * @throws JavaGitException
   *         if file is not part of the repository
   */
  public Status getFileStatus(File file) throws JavaGitException {
    //get command argument file (relative to repository path); this will perform basic path check
    File relativeFile = GitFileSystemObject.getRelativePath(file, repository);

    File argumentFile = new File(repositoryPath + relativeFile.getPath());
    // first check the map
    if(fileToStatus.containsKey(argumentFile)) {
      return fileToStatus.get(argumentFile);
    }

    // default
    return Status.IN_REPOSITORY;
  }

  /**
   * Returns a message that is generally produced as a side comment by <git-status>
   *
   * @return Message.
   */
  public String getMessage() {
    return message;
  }

  /**
   * Returns the error message otherwise returns null;
   *
   * @return error message
   */
  public String getError(int index) {
    if (index < errors.size()) {
      ErrorDetails errorDetails = errors.get(index);
      return errorDetails.lineNumber + ". " + errorDetails.error;
    }
    return null;
  }

  /**
   * Gets all the errors generated by <git-status> command and returns them in String format.
   *
   * @return String errors concatenated together.
   */
  public String getError() {
    StringBuilder strBuilder = new StringBuilder();
    for (int i = 0; i < errors.size(); i++) {
      strBuilder.append(getError(i) + " ");
    }
    return strBuilder.toString();
  }

  /**
   * Gets the number of errors generated by GitStatus command.
   *
   * @return no. of errors
   */
  public int getErrorCount() {
    return errors.size();
  }

  /**
   * Gets the state of the GitStatusResposne object returned by <git-status>
   * command.
   *
   * @return true if the output processed generates any lines with error.
   */
  public boolean errorState() {
    return (errors.size() > 0);
  }

  public static class ErrorDetails {
    final int lineNumber;
    final String error;

    public ErrorDetails(int lineNumber, String error) {
      this.lineNumber = lineNumber;
      this.error = error;
    }
  }

  /**
   * An inner class for converting an iterator to iterable object.
   */
  public static class IterableIterator<T> implements Iterable<T> {

    private Iterator<T> iterator;

    public IterableIterator(Iterator<T> iterator) {
      this.iterator = iterator;
    }

    public Iterator<T> iterator() {
      return iterator;
    }

  }
}