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

package joptsimple;

import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import joptsimple.internal.Classes;
import joptsimple.internal.ColumnarData;
import joptsimple.internal.Strings;

/**
 * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
 * @version $Id: OptionParserHelpFormatter.java,v 1.7 2008/05/14 22:56:25 pholser Exp $
 */
class OptionParserHelpFormatter implements OptionSpecVisitor {
    private final ColumnarData grid;

    OptionParserHelpFormatter() {
        grid = new ColumnarData( new String[] { "Option", "Description" } );
    }

    String format( Map options ) {
        if ( options.isEmpty() )
            return "No options specified";

        grid.clear();

        SortedSet sorted = new TreeSet( new OptionSpecComparator() );
        sorted.addAll( options.values() );

        for ( Iterator iter = sorted.iterator(); iter.hasNext(); )
            ( (OptionSpec) iter.next() ).accept( this );

        return grid.format();
    }

    void addHelpLineFor( OptionSpec spec, String additionalInfo ) {
        grid.addRow( new Object[] {
            createOptionDisplay( spec ) + additionalInfo,
            spec.description()
        } );
    }

    public void visit( NoArgumentOptionSpec spec ) {
        addHelpLineFor( spec, "" );
    }

    public void visit( RequiredArgumentOptionSpec spec ) {
        visit( spec, '<', '>' );
    }

    public void visit( OptionalArgumentOptionSpec spec ) {
        visit( spec, '[', ']' );
    }

    private void visit( ArgumentAcceptingOptionSpec spec, char begin, char end ) {
        String argDescription = spec.argumentDescription();
        String argType = nameOfArgumentType( spec );
        StringBuffer collector = new StringBuffer();

        if ( argType.length() > 0 ) {
            collector.append( argType );

            if ( argDescription.length() > 0 )
                collector.append( ": " ).append( argDescription );
        }
        else if ( argDescription.length() > 0 )
            collector.append( argDescription );

        String helpLine = collector.length() == 0
            ? ""
            : ' ' + Strings.surround( collector.toString(), begin, end );
        addHelpLineFor( spec, helpLine );
    }

    public void visit( AlternativeLongOptionSpec spec ) {
        String argDescription = spec.argumentDescription();
        addHelpLineFor( spec, ' ' + Strings.surround( argDescription, '<', '>' ) );
    }

    private String createOptionDisplay( OptionSpec spec ) {
        StringBuffer buffer = new StringBuffer();

        for ( Iterator iter = spec.options().iterator(); iter.hasNext(); ) {
            String option = (String) iter.next();
            buffer.append( option.length() > 1
                ? ParserRules.DOUBLE_HYPHEN
                : ParserRules.HYPHEN );
            buffer.append( option );

            if ( iter.hasNext() )
                buffer.append( ", " );
        }

        return buffer.toString();
    }

    private static String nameOfArgumentType( ArgumentAcceptingOptionSpec spec ) {
        Class argType = spec.argumentType();
        return String.class.equals( argType ) ? "" : Classes.shortNameOf( argType );
    }

    private static class OptionSpecComparator implements Comparator {
        public int compare( Object first, Object second ) {
            OptionSpec firstSpec = (OptionSpec) first;
            OptionSpec secondSpec = (OptionSpec) second;

            if ( firstSpec.equals( secondSpec ) )
                return 0;

            List firstOptions = firstSpec.options();
            List secondOptions = secondSpec.options();

            return ( (String) firstOptions.get( 0 ) )
                .compareTo( (String) secondOptions.get( 0 ) );
        }
    }
}
