/*
 * 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.Iterator;
import java.util.LinkedList;

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

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



/**
 * This class defines a generic argument that may be used in the argument list
 * for an application.  This is an abstract class that must be subclassed in
 * order to provide specific functionality.
 */
public abstract class Argument
{
  // Indicates whether this argument should be hidden in the usage information.
  private boolean isHidden;

  // Indicates whether this argument may be specified more than once for
  // multiple values.
  private boolean isMultiValued;

  // Indicates whether this argument was provided in the set of command-line
  // arguments.
  private boolean isPresent;

  // Indicates whether this argument is required to have a value.
  private boolean isRequired;

  // Indicates whether this argument requires a value.
  private boolean needsValue;

  // The single-character identifier for this argument.
  private Character shortIdentifier;

  // The unique ID of the description for this argument.
  private Message description;

  // The set of values for this argument.
  private LinkedList<String> values;

  // The default value for the argument if none other is provided.
  private String defaultValue;

  // The long identifier for this argument.
  private String longIdentifier;

  // The generic name that will be used to refer to this argument.
  private String name;

  // The name of the property that can be used to set the default value.
  private String propertyName;

  // The value placeholder for this argument, which will be used in usage
  // information.
  private Message valuePlaceholder;

  // Indicates whether this argument was provided in the set of properties
  // found is a properties file.
  private boolean isValueSetByProperty;


  /**
   * Creates a new argument with the provided information.
   *
   * @param  name              The generic name that should be used to refer to
   *                           this argument.
   * @param  shortIdentifier   The single-character identifier for this
   *                           argument, or <CODE>null</CODE> if there is none.
   * @param  longIdentifier    The long identifier for this argument, or
   *                           <CODE>null</CODE> if there is none.
   * @param  isRequired        Indicates whether this argument must be specified
   *                           on the command line.
   * @param  isMultiValued     Indicates whether this argument may be specified
   *                           more than once to provide multiple values.
   * @param  needsValue        Indicates whether this argument requires a value.
   * @param  valuePlaceholder  The placeholder for the argument value that will
   *                           be displayed in usage information, or
   *                           <CODE>null</CODE> if this argument does not
   *                           require a value.
   * @param  defaultValue      The default value that should be used for this
   *                           argument if none is provided in a properties file
   *                           or on the command line.  This may be
   *                           <CODE>null</CODE> if there is no generic default.
   * @param  propertyName      The name of the property in a property file that
   *                           may be used to override the default value but
   *                           will be overridden by a command-line argument.
   * @param  description       Message for the description of this
   *                           argument.
   *
   * @throws  ArgumentException  If there is a problem with any of the
   *                             parameters used to create this argument.
   */
  protected Argument(String name, Character shortIdentifier,
                     String longIdentifier, boolean isRequired,
                     boolean isMultiValued, boolean needsValue,
                     Message valuePlaceholder, String defaultValue,
                     String propertyName,
                     Message description)
            throws ArgumentException
  {
    this.name             = name;
    this.shortIdentifier  = shortIdentifier;
    this.longIdentifier   = longIdentifier;
    this.isRequired       = isRequired;
    this.isMultiValued    = isMultiValued;
    this.needsValue       = needsValue;
    this.valuePlaceholder = valuePlaceholder;
    this.defaultValue     = defaultValue;
    this.propertyName     = propertyName;
    this.description      = description;
    this.isValueSetByProperty = false ;

    if ((shortIdentifier == null) && (longIdentifier == null))
    {
      Message message = ERR_ARG_NO_IDENTIFIER.get(name);
      throw new ArgumentException(message);
    }

    if (needsValue && (valuePlaceholder == null))
    {
      Message message = ERR_ARG_NO_VALUE_PLACEHOLDER.get(name);
      throw new ArgumentException(message);
    }

    values    = new LinkedList<String>();
    isPresent = false;
    isHidden  = false;
  }



  /**
   * Retrieves the generic name that will be used to refer to this argument.
   *
   * @return  The generic name that will be used to refer to this argument.
   */
  public String getName()
  {
    return name;
  }



  /**
   * Retrieves the single-character identifier that may be used to specify the
   * value of this argument.
   *
   * @return  The single-character identifier that may be used to specify the
   *          value of this argument, or <CODE>null</CODE> if there is none.
   */
  public Character getShortIdentifier()
  {
    return shortIdentifier;
  }



  /**
   * Retrieves the long (multi-character) identifier that may be used to specify
   * the value of this argument.
   *
   * @return  The long (multi-character) identifier that may be used to specify
   *          the value of this argument.
   */
  public String getLongIdentifier()
  {
    return longIdentifier;
  }



  /**
   * Indicates whether this argument is required to have at least one value.
   *
   * @return  <CODE>true</CODE> if this argument is required to have at least
   *          one value, or <CODE>false</CODE> if it does not need to have a
   *          value.
   */
  public boolean isRequired()
  {
    return isRequired;
  }



  /**
   * Specifies whether this argument is required to have at least one value.
   *
   * @param  isRequired  Indicates whether this argument is required to have at
   *                     least one value.
   */
  public void setRequired(boolean isRequired)
  {
    this.isRequired = isRequired;
  }



  /**
   * Indicates whether this argument is present in the parsed set of
   * command-line arguments.
   *
   * @return  <CODE>true</CODE> if this argument is present in the parsed set of
   *          command-line arguments, or <CODE>false</CODE> if not.
   */
  public boolean isPresent()
  {
    return isPresent;
  }



  /**
   * Specifies whether this argument is present in the parsed set of
   * command-line arguments.
   *
   * @param  isPresent  Indicates whether this argument is present in the set of
   *                    command-line arguments.
   */
  public void setPresent(boolean isPresent)
  {
    this.isPresent = isPresent;
  }



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



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



  /**
   * Indicates whether this argument may be provided more than once on the
   * command line to specify multiple values.
   *
   * @return  <CODE>true</CODE> if this argument may be provided more than once
   *          on the command line to specify multiple values, or
   *          <CODE>false</CODE> if it may have at most one value.
   */
  public boolean isMultiValued()
  {
    return isMultiValued;
  }



  /**
   * Specifies whether this argument may be provided more than once on the
   * command line to specify multiple values.
   *
   * @param  isMultiValued  Indicates whether this argument may be provided more
   *                        than once on the command line to specify multiple
   *                        values.
   */
  public void setMultiValued(boolean isMultiValued)
  {
    this.isMultiValued = isMultiValued;
  }



  /**
   * Indicates whether a value must be provided with this argument if it is
   * present.
   *
   * @return  <CODE>true</CODE> if a value must be provided with the argument if
   *          it is present, or <CODE>false</CODE> if the argument does not take
   *          a value and the presence of the argument identifier itself is
   *          sufficient to convey the necessary information.
   */
  public boolean needsValue()
  {
    return needsValue;
  }



  /**
   * Specifies whether a value must be provided with this argument if it is
   * present.  If this is changed from <CODE>false</CODE> to <CODE>true</CODE>,
   * then a value placeholder must also be provided.
   *
   * @param  needsValue  Indicates whether a value must be provided with this
   *                     argument if it is present.

   */
  public void setNeedsValue(boolean needsValue)
  {
    this.needsValue = needsValue;
  }



  /**
   * Retrieves the value placeholder that will be displayed for this argument in
   * the generated usage information.
   *
   * @return  The value placeholder that will be displayed for this argument in
   *          the generated usage information, or <CODE>null</CODE> if there is
   *          none.
   */
  public Message getValuePlaceholder()
  {
    return valuePlaceholder;
  }



  /**
   * Specifies the value placeholder that will be displayed for this argument in
   * the generated usage information.  It may be <CODE>null</CODE> only if
   * <CODE>needsValue()</CODE> returns <CODE>false</CODE>.
   *
   * @param  valuePlaceholder  The value placeholder that will be displayed for
   *                           this argument in the generated usage information.
   */
  public void setValuePlaceholder(Message valuePlaceholder)
  {
    this.valuePlaceholder = valuePlaceholder;
  }



  /**
   * Retrieves the default value that will be used for this argument if it is
   * not specified on the command line and it is not set from a properties file.
   *
   * @return  The default value that will be used for this argument if it is not
   *          specified on the command line and it is not set from a properties
   *          file, or <CODE>null</CODE> if there is no default value.
   */
  public String getDefaultValue()
  {
    return defaultValue;
  }



  /**
   * Specifies the default value that will be used for this argument if it is
   * not specified on the command line and it is not set from a properties file.
   *
   * @param  defaultValue  The default value that will be used for this argument
   *                       if it is not specified on the command line and it is
   *                       not set from a properties file.
   */
  public void setDefaultValue(String defaultValue)
  {
    this.defaultValue = defaultValue;
  }



  /**
   * Retrieves the name of a property in a properties file that may be used to
   * set the default value for this argument if it is present.  A value read
   * from a properties file will override the default value returned from the
   * <CODE>getDefaultValue</CODE>, but the properties file value will be
   * overridden by a value supplied on the command line.
   *
   * @return  The name of a property in a properties file that may be used to
   *          set the default value for this argument if it is present.
   */
  public String getPropertyName()
  {
    return propertyName;
  }



  /**
   * Specifies the name of a property in a properties file that may be used to
   * set the default value for this argument if it is present.
   *
   * @param  propertyName  The name of a property in a properties file that may
   *                       be used to set the default value for this argument if
   *                       it is present.
   */
  public void setPropertyName(String propertyName)
  {
    this.propertyName = propertyName;
  }

  /**
   * Indicates whether this argument was provided in the set of
   * properties found is a properties file.
   *
   * @return <CODE>true</CODE> if this argument was provided in the
   *         set of properties found is a properties file, or
   *         <CODE>false</CODE> if not.
   */
  public boolean isValueSetByProperty()
  {
    return isValueSetByProperty;
  }

  /**
   * Specifies whether this argument was provided in the set of
   * properties found is a properties file.
   *
   * @param isValueSetByProperty
   *          Specify whether this argument was provided in the set
   *          of properties found is a properties file.
   */
  public void setValueSetByProperty(boolean isValueSetByProperty)
  {
    this.isValueSetByProperty = isValueSetByProperty;
  }

  /**
   * Retrieves the human-readable description for this argument.
   *
   * @return  The human-readable description for this argument.
   */
  public Message getDescription()
  {
    return description != null ? description : Message.EMPTY;
  }



  /**
   * Indicates whether this argument has at least one value.
   *
   * @return  <CODE>true</CODE> if this argument has at least one value, or
   *          <CODE>false</CODE> if it does not have any values.
   */
  public boolean hasValue()
  {
    return (! values.isEmpty());
  }



  /**
   * Retrieves the string vale for this argument.  If it has multiple values,
   * then the first will be returned.  If it does not have any values, then the
   * default value will be returned.
   *
   * @return  The string value for this argument, or <CODE>null</CODE> if there
   *          are no values and no default value has been given.
   */
  public String getValue()
  {
    if (values.isEmpty())
    {
      return defaultValue;
    }

    return values.getFirst();
  }



  /**
   * Retrieves the set of string values for this argument.
   *
   * @return  The set of string values for this argument.
   */
  public LinkedList<String> getValues()
  {
    return values;
  }



  /**
   * Retrieves the value of this argument as an integer.
   *
   * @return  The value of this argument as an integer.
   *
   * @throws  ArgumentException  If there are multiple values, or the value
   *                             cannot be parsed as an integer.
   */
  public int getIntValue()
         throws ArgumentException
  {
    if (values.isEmpty())
    {
      Message message = ERR_ARG_NO_INT_VALUE.get(name);
      throw new ArgumentException(message);
    }

    Iterator<String> iterator = values.iterator();
    String valueString = iterator.next();

    int intValue;
    try
    {
      intValue = Integer.parseInt(valueString);
    }
    catch (Exception e)
    {
      Message message = ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
      throw new ArgumentException(message, e);
    }

    if (iterator.hasNext())
    {
      Message message = ERR_ARG_INT_MULTIPLE_VALUES.get(name);
      throw new ArgumentException(message);
    }
    else
    {
      return intValue;
    }
  }



  /**
   * Retrieves the set of values for this argument as a list of integers.
   *
   * @return  A list of the integer representations of the values for this
   *          argument.
   *
   * @throws  ArgumentException  If any of the values cannot be parsed as an
   *                             integer.
   */
  public LinkedList<Integer> getIntValues()
         throws ArgumentException
  {
    LinkedList<Integer> intList = new LinkedList<Integer>();

    Iterator<String> iterator = values.iterator();
    while (iterator.hasNext())
    {
      String valueString = iterator.next();

      try
      {
        intList.add(Integer.valueOf(valueString));
      }
      catch (Exception e)
      {
        Message message = ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
        throw new ArgumentException(message, e);
      }
    }

    return intList;
  }



  /**
   * Retrieves the value of this argument as a <CODE>Boolean</CODE>.
   *
   * @return  The value of this argument as a <CODE>Boolean</CODE>.
   *
   * @throws  ArgumentException  If this argument cannot be interpreted as a
   *                             Boolean value.
   */
  public boolean getBooleanValue()
         throws ArgumentException
  {
    if (values.isEmpty())
    {
      Message message = ERR_ARG_NO_BOOLEAN_VALUE.get(name);
      throw new ArgumentException(message);
    }

    Iterator<String> iterator = values.iterator();
    String valueString = toLowerCase(iterator.next());

    boolean booleanValue;
    if (valueString.equals("true") || valueString.equals("yes") ||
        valueString.equals("on") || valueString.equals("1"))
    {
      booleanValue = true;
    }
    else if (valueString.equals("false") || valueString.equals("no") ||
             valueString.equals("off") || valueString.equals("0"))
    {
      booleanValue = false;
    }
    else
    {
      Message message = ERR_ARG_CANNOT_DECODE_AS_BOOLEAN.get(valueString, name);
      throw new ArgumentException(message);
    }

    if (iterator.hasNext())
    {
      Message message = ERR_ARG_BOOLEAN_MULTIPLE_VALUES.get(name);
      throw new ArgumentException(message);
    }
    else
    {
      return booleanValue;
    }
  }



  /**
   * Indicates whether the provided value is acceptable for use in this
   * argument.
   *
   * @param  valueString    The value for which to make the determination.
   * @param  invalidReason  A buffer into which the invalid reason may be
   *                        written if the value is not acceptable.
   *
   * @return  <CODE>true</CODE> if the value is acceptable, or
   *          <CODE>false</CODE> if it is not.
   */
  public abstract boolean valueIsAcceptable(String valueString,
                                            MessageBuilder invalidReason);



  /**
   * Adds a value to the set of values for this argument.  This should only be
   * called if the value is allowed by the <CODE>valueIsAcceptable</CODE>
   * method.
   *
   * @param  valueString  The string representation of the value to add to this
   *                      argument.
   */
  public void addValue(String valueString)
  {
    values.add(valueString);
  }



  /**
   * Clears the set of values assigned to this argument.
   */
  public void clearValues()
  {
    values.clear();
  }
}

