/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.util.args;
import org.opends.messages.Message;



import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

import static org.opends.messages.UtilityMessages.*;

import static org.opends.server.util.StaticUtils.*;



/**
 * This class defines a data structure for holding information about a
 * subcommand that may be used with the subcommand argument parser.  The
 * subcommand has a name, a description, and a set of arguments.
 */
public class SubCommand
{
  // Indicates whether this subCommand should be hidden in the usage
  // information.
  private boolean isHidden;

  // The mapping between the short argument IDs and the arguments for this
  // subcommand.
  private HashMap<Character,Argument> shortIDMap;

  // The mapping between the long argument IDs and the arguments for this
  // subcommand.
  private HashMap<String,Argument> longIDMap;

  // The list of arguments associated with this subcommand.
  private LinkedList<Argument> arguments;

  // The description for this subcommand.
  private Message description;

  // The name of this subcommand.
  private String name;

  // The argument parser with which this subcommand is associated.
  private SubCommandArgumentParser parser;

  // Indicates whether this parser will allow additional unnamed
  // arguments at the end of the list.
  private boolean allowsTrailingArguments;

  // The maximum number of unnamed trailing arguments that may be
  // provided.
  private int maxTrailingArguments;

  // The minimum number of unnamed trailing arguments that may be
  // provided.
  private int minTrailingArguments;

  // The display name that will be used for the trailing arguments in
  // the usage information.
  private String trailingArgsDisplayName;

  /**
   * Creates a new subcommand with the provided information. The
   * subcommand will be automatically registered with the associated
   * parser.
   *
   * @param parser
   *          The argument parser with which this subcommand is
   *          associated.
   * @param name
   *          The name of this subcommand.
   * @param description
   *          The description of this subcommand.
   * @throws ArgumentException
   *           If the associated argument parser already has a
   *           subcommand with the same name.
   */
  public SubCommand(SubCommandArgumentParser parser, String name,
      Message description) throws ArgumentException
  {
    this(parser, name, false, 0, 0, null, description);
  }



  /**
   * Creates a new subcommand with the provided information. The
   * subcommand will be automatically registered with the associated
   * parser.
   *
   * @param parser
   *          The argument parser with which this subcommand is
   *          associated.
   * @param name
   *          The name of this subcommand.
   * @param allowsTrailingArguments
   *          Indicates whether this parser allows unnamed trailing
   *          arguments to be provided.
   * @param minTrailingArguments
   *          The minimum number of unnamed trailing arguments that
   *          must be provided. A value less than or equal to zero
   *          indicates that no minimum will be enforced.
   * @param maxTrailingArguments
   *          The maximum number of unnamed trailing arguments that
   *          may be provided. A value less than or equal to zero
   *          indicates that no maximum will be enforced.
   * @param trailingArgsDisplayName
   *          The display name that should be used as a placeholder
   *          for unnamed trailing arguments in the generated usage
   *          information.
   * @param description
   *          The description of this subcommand.
   * @throws ArgumentException
   *           If the associated argument parser already has a
   *           subcommand with the same name.
   */
  public SubCommand(SubCommandArgumentParser parser, String name,
      boolean allowsTrailingArguments, int minTrailingArguments,
      int maxTrailingArguments, String trailingArgsDisplayName,
      Message description) throws ArgumentException
  {
    this.parser = parser;
    this.name = name;
    this.description = description;
    this.allowsTrailingArguments = allowsTrailingArguments;
    this.minTrailingArguments = minTrailingArguments;
    this.maxTrailingArguments = maxTrailingArguments;
    this.trailingArgsDisplayName = trailingArgsDisplayName;
    this.isHidden  = false;

    String nameToCheck = name;
    if (parser.longArgumentsCaseSensitive())
    {
      nameToCheck = toLowerCase(name);
    }

    if (parser.hasSubCommand(nameToCheck))
    {
      Message message = ERR_ARG_SUBCOMMAND_DUPLICATE_SUBCOMMAND.get(name);
      throw new ArgumentException(message);
    }

    parser.addSubCommand(this);
    shortIDMap  = new HashMap<Character,Argument>();
    longIDMap   = new HashMap<String,Argument>();
    arguments   = new LinkedList<Argument>();
  }



  /**
   * Retrieves the name of this subcommand.
   *
   * @return The name of this subcommand.
   */
  public String getName()
  {
    return name;
  }


  /**
   * Retrieves the description for this subcommand.
   *
   * @return  The description for this subcommand.
   */
  public Message getDescription()
  {
    return description;
  }



  /**
   * Retrieves the set of arguments for this subcommand.
   *
   * @return  The set of arguments for this subcommand.
   */
  public LinkedList<Argument> getArguments()
  {
    return arguments;
  }



  /**
   * Retrieves the subcommand argument with the specified short identifier.
   *
   * @param  shortID  The short identifier of the argument to retrieve.
   *
   * @return  The subcommand argument with the specified short identifier, or
   *          <CODE>null</CODE> if there is none.
   */
  public Argument getArgument(Character shortID)
  {
    return shortIDMap.get(shortID);
  }



  /**
   * Retrieves the subcommand argument with the specified long identifier.
   *
   * @param  longID  The long identifier of the argument to retrieve.
   *
   * @return  The subcommand argument with the specified long identifier, or
   *          <CODE>null</CODE> if there is none.
   */
  public Argument getArgument(String longID)
  {
    return longIDMap.get(longID);
  }



  /**
   * Retrieves the subcommand argument with the specified name.
   *
   * @param  name  The name of the argument to retrieve.
   *
   * @return  The subcommand argument with the specified name, or
   *          <CODE>null</CODE> if there is no such argument.
   */
  public Argument getArgumentForName(String name)
  {
    for (Argument a : arguments)
    {
      if (a.getName().equals(name))
      {
        return a;
      }
    }

    return null;
  }



  /**
   * Adds the provided argument for use with this subcommand.
   *
   * @param  argument  The argument to add for use with this subcommand.
   *
   * @throws  ArgumentException  If either the short ID or long ID for the
   *                             argument conflicts with that of another
   *                             argument already associated with this
   *                             subcommand.
   */
  public void addArgument(Argument argument)
         throws ArgumentException
  {
    String argumentName = argument.getName();
    for (Argument a : arguments)
    {
      if (argumentName.equals(a.getName()))
      {
        Message message =
            ERR_ARG_SUBCOMMAND_DUPLICATE_ARGUMENT_NAME.get(name, argumentName);
        throw new ArgumentException(message);
      }
    }

    if (parser.hasGlobalArgument(argumentName))
    {
      Message message =
          ERR_ARG_SUBCOMMAND_ARGUMENT_GLOBAL_CONFLICT.get(argumentName, name);
      throw new ArgumentException(message);
    }


    Character shortID = argument.getShortIdentifier();
    if (shortID != null)
    {
      if (shortIDMap.containsKey(shortID))
      {
        Message message = ERR_ARG_SUBCOMMAND_DUPLICATE_SHORT_ID.
            get(argumentName, name, String.valueOf(shortID),
                shortIDMap.get(shortID).getName());
        throw new ArgumentException(message);
      }

      Argument arg = parser.getGlobalArgumentForShortID(shortID);
      if (arg != null)
      {
        Message message = ERR_ARG_SUBCOMMAND_ARGUMENT_SHORT_ID_GLOBAL_CONFLICT.
            get(argumentName, name, String.valueOf(shortID), arg.getName());
        throw new ArgumentException(message);
      }
    }


    String longID = argument.getLongIdentifier();
    if (longID != null)
    {
      if (! parser.longArgumentsCaseSensitive())
      {
        longID = toLowerCase(longID);
      }

      if (longIDMap.containsKey(longID))
      {
        Message message = ERR_ARG_SUBCOMMAND_DUPLICATE_LONG_ID.get(
            argumentName, name, argument.getLongIdentifier(),
            longIDMap.get(longID).getName());
        throw new ArgumentException(message);
      }

      Argument arg = parser.getGlobalArgumentForLongID(longID);
      if (arg != null)
      {
        Message message = ERR_ARG_SUBCOMMAND_ARGUMENT_LONG_ID_GLOBAL_CONFLICT.
            get(argumentName, name, argument.getLongIdentifier(),
                arg.getName());
        throw new ArgumentException(message);
      }
    }


    arguments.add(argument);

    if (shortID != null)
    {
      shortIDMap.put(shortID, argument);
    }

    if (longID != null)
    {
      longIDMap.put(longID, argument);
    }
  }



  /**
   * Indicates whether this sub-command will allow unnamed trailing
   * arguments. These will be arguments at the end of the list that
   * are not preceded by either a long or short identifier and will
   * need to be manually parsed by the application using this parser.
   * Note that once an unnamed trailing argument has been identified,
   * all remaining arguments will be classified as such.
   *
   * @return <CODE>true</CODE> if this sub-command allows unnamed
   *         trailing arguments, or <CODE>false</CODE> if it does
   *         not.
   */
  public boolean allowsTrailingArguments()
  {
    return allowsTrailingArguments;
  }



  /**
   * Retrieves the minimum number of unnamed trailing arguments that
   * must be provided.
   *
   * @return The minimum number of unnamed trailing arguments that
   *         must be provided, or a value less than or equal to zero
   *         if no minimum will be enforced.
   */
  public int getMinTrailingArguments()
  {
    return minTrailingArguments;
  }



  /**
   * Retrieves the maximum number of unnamed trailing arguments that
   * may be provided.
   *
   * @return The maximum number of unnamed trailing arguments that may
   *         be provided, or a value less than or equal to zero if no
   *         maximum will be enforced.
   */
  public int getMaxTrailingArguments()
  {
    return maxTrailingArguments;
  }



  /**
   * Retrieves the trailing arguments display name.
   *
   * @return Returns the trailing arguments display name.
   */
  public String getTrailingArgumentsDisplayName()
  {
    return trailingArgsDisplayName;
  }



  /**
   * Retrieves the set of unnamed trailing arguments that were provided on the
   * command line.
   *
   * @return  The set of unnamed trailing arguments that were provided on the
   *          command line.
   */
  public ArrayList<String> getTrailingArguments()
  {
    return parser.getTrailingArguments();
  }

  /**
   * Indicates whether this subcommand should be hidden from the usage
   * information.
   *
   * @return <CODE>true</CODE> if this subcommand should be hidden
   *         from the usage information, or <CODE>false</CODE> if
   *         not.
   */
  public boolean isHidden()
  {
    return isHidden;
  }



  /**
   * Specifies whether this subcommand should be hidden from the usage
   * information.
   *
   * @param isHidden
   *          Indicates whether this subcommand should be hidden from
   *          the usage information.
   */
  public void setHidden(boolean isHidden)
  {
    this.isHidden = isHidden;
  }
}

