/*
 Copyright 2004-2008 Paul R. Holser, Jr.  All rights reserved.
 Licensed under the Academic Free License version 3.0
 */

package joptsimple;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * <p>Representation of a group of detected command line options, their arguments, and
 * non-option arguments.</p>
 *
 * @since 1.0
 * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
 * @version $Id: OptionSet.java,v 1.4 2008/04/15 02:19:07 pholser Exp $
 */
public class OptionSet {
    private final Map detectedOptions = new HashMap();
    private final List nonOptionArguments = new ArrayList();

    OptionSet() {
        // Empty on purpose.  Package-private because clients don't create these.
    }

    /**
     * <p>Tells whether the given option was detected.</p>
     *
     * @since 1.0
     * @param option the option to search for
     * @return <code>true</code> if the option was detected
     * @deprecated Use {@link #has(String) has} instead.
     */
    public boolean wasDetected( String option ) {
        return detectedOptions.containsKey( option );
    }

    /**
     * <p>Tells whether the given option was detected.</p>
     *
     * @since 2.4
     * @param option the option to search for
     * @return <code>true</code> if the option was detected
     */
    public boolean has( String option ) {
        return wasDetected( option );
    }

    /**
     * <p>Tells whether there are any arguments associated with the given option.</p>
     *
     * @since 1.0
     * @param option the option to search for
     * @return <code>true</code> if the option was detected and at least one argument was
     * detected for the option
     */
    public boolean hasArgument( String option ) {
        return !valuesOf( option ).isEmpty();
    }

    /**
     * <p>Gives the argument associated with the given option.</p>
     *
     * @since 1.0
     * @param option the option to search for
     * @return the argument of the given option as a {@link String}; <code>null</code> if
     * no argument is present, or that option was not detected
     * @throws OptionException if more than one argument was detected for the option
     * @throws ClassCastException if the argument was given a type other than
     * {@link String}
     * @see #valueOf(String)
     */
    public String argumentOf( String option ) {
        return (String) valueOf( option );
    }

    /**
     * <p>Gives any arguments associated with the given option.</p>
     *
     * @since 1.0
     * @param option the option to search for
     * @return the arguments associated with the option, as a list of objects of the
     * type given to the arguments; an empty list if no such arguments are present, or if
     * the option was not detected
     * @see #valuesOf(String)
     */
    public List argumentsOf( String option ) {
        return valuesOf( option );
    }

    /**
     * <p>Gives the argument associated with the given option.  If the argument was
     * given a type, it will take on that type; otherwise, use {@link #argumentOf(String)
     * argumentOf} to get the argument as a {@link String}.</p>
     *
     * @since 2.0
     * @param option the option to search for
     * @return the argument of the given option; <code>null</code> if no argument is
     * present, or that option was not detected
     * @throws OptionException if more than one argument was detected for the option
     */
    public Object valueOf( String option ) {
        List values = valuesOf( option );

        switch ( values.size() ) {
            case 0:
                return null;
            case 1:
                return values.get( 0 );
            default:
                throw new MultipleArgumentsForOptionException( option );
        }
    }

    /**
     * <p>Gives any arguments associated with the given option.</p>
     *
     * @since 2.0
     * @param option the option to search for
     * @return the arguments associated with the option, as a list of objects of the
     * type given to the arguments; an empty list if no such arguments are present, or if
     * the option was not detected
     */
    public List valuesOf( String option ) {
        List values = (List) detectedOptions.get( option );
        return values == null
            ? Collections.EMPTY_LIST
            : Collections.unmodifiableList( values );
    }

    /**
     * <p>Gives the detected non-option arguments.</p>
     *
     * @since 2.1
     * @return the detected non-option arguments as a list of {@link String}s.
     */
    public List nonOptionArguments() {
        return Collections.unmodifiableList( nonOptionArguments );
    }

    /**
     * {@inheritDoc}
     */
    public boolean equals( Object that ) {
        if ( this == that )
            return true;

        if ( that == null || !getClass().equals( that.getClass() ) )
            return false;

        OptionSet other = (OptionSet) that;
        return detectedOptions.equals( other.detectedOptions )
            && nonOptionArguments.equals( other.nonOptionArguments() );
    }

    /**
     * {@inheritDoc}
     */
    public int hashCode() {
        return detectedOptions.hashCode() ^ nonOptionArguments.hashCode();
    }

    void add( String option ) {
        addWithArgument( option, null );
    }

    void addWithArgument( String option, Object argument ) {
        List optionArguments = (List) detectedOptions.get( option );

        if ( optionArguments == null ) {
            optionArguments = new ArrayList();
            detectedOptions.put( option, optionArguments );
        }

        if ( argument != null )
            optionArguments.add( argument );
    }

    void addNonOptionArgument( String argument ) {
        nonOptionArguments.add( argument );
    }

    void addAll( List options ) {
        for ( Iterator iter = options.iterator(); iter.hasNext(); )
            add( (String) iter.next() );
    }

    void addAllWithArgument( List options, Object argument ) {
        for ( Iterator iter = options.iterator(); iter.hasNext(); )
            addWithArgument( (String) iter.next(), argument );
    }
}
