/*
 * (C) Copyright Keith Visco 1998, 1999  All rights reserved.
 *
 * The contents of this file are released under an Open Source 
 * Definition (OSD) compliant license; you may not use this file 
 * execpt in compliance with the license. Please see license.txt, 
 * distributed with this file. You may also obtain a copy of the
 * license at http://www.clc-marketing.com/xslp/license.txt
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * The Copyright owner will not be liable for any damages suffered by
 * you as a result of using the Program. In no event will the Copyright
 * owner be liable for any special, indirect or consequential damages or
 * lost profits even if the Copyright owner has been advised of the
 * possibility of their occurrence.
 *
 * $Id: CommandLineOptions.java 8146 2009-03-30 07:29:12Z wguttmn $
 */

 
package org.exolab.castor.util;

import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

import org.castor.core.util.Messages;

/**
 * A utility class for generating command line options.
 *
 * @author <a href="mailto:kvisco@ziplink.net">Keith Visco</a>
 * @version $Revision: 8146 $ $Date: 2006-04-10 16:39:24 -0600 (Mon, 10 Apr 2006) $
 */
public class CommandLineOptions {
    private Vector _flags = null;
    private Hashtable _optionInfo = null;
    private PrintWriter _errorWriter = null;
    
    public CommandLineOptions() {
        _flags = new Vector();
        _optionInfo = new Hashtable();
        _errorWriter = new PrintWriter(System.out);
    }
    
    /**
     * Adds the flag to list of available command line options.
     * 
     * @param flag the flag to add as an available command line option.
     */
    public void addFlag(final String flag) {
        addFlag(flag, null, null);
    }
    
    /**
     * Adds the flag to list of available command line options.
     * 
     * @param flag the flag to add as an available command line option.
     * @param comment a comment for the flag.
     */
    public void addFlag(final String flag, final String comment) {
        addFlag(flag, null, comment, false);
    }
    
    /**
     * Adds the flag to list of available command line options.
     * 
     * @param flag the flag to add as an available command line option.
     * @param comment a comment for the flag.
     * @param usageText the text that appears after the flag in the
     *        usage string.
     */
    public void addFlag(final String flag, final String usageText, final String comment) {
        addFlag(flag, usageText, comment, false);
    } //-- addFlag

    /**
     * Adds the flag to list of available command line options.
     * 
     * @param flag The flag to add as an available command line option.
     * @param comment A comment for the flag.
     * @param usageText The text that appears after the flag in the usage string.
     * @param optional When true, indicates that this flag is optional.
     */
    public void addFlag(final String flag, final String usageText, final String comment,
            final boolean optional) {
        if (flag == null) { return; }
        _flags.addElement(flag);
        
        CmdLineOption opt = new CmdLineOption(flag);
        opt.setComment(comment);
        opt.setUsageText(usageText);
        opt.setOptional(optional);
        _optionInfo.put(flag, opt);
    }
    
    /**
     * Parses the arguments into a hashtable with the proper flag as the key.
     */
    public Properties getOptions(final String[] args) {
        Properties options = new Properties();
        String flag = null;
        for (int i = 0; i < args.length; i++) {
            
            if (args[i].startsWith("-")) {
                    
                // clean up previous flag
                if (flag != null) {
                    options.put(flag, args[i]);
                    options.put(new Integer(i), args[i]);
                }
                // get next flag
                flag = args[i].substring(1);
                
                //-- check full flag, otherwise try to find
                //-- flag within string
                if (!_flags.contains(flag)) {
                    int idx = 1;
                    while (idx <= flag.length()) {
                        if (_flags.contains(flag.substring(0, idx))) {
                            if (idx < flag.length()) {
                                options.put(flag.substring(0, idx),
                                    flag.substring(idx));
                                break;
                            }
                        }
                        else if (idx == flag.length()) {
                            _errorWriter.println(Messages.format("misc.invalidCLIOption",
                                    "-" + flag));
                            printUsage(_errorWriter);
                        }
                        ++idx;
                    }
                }
            } else {
                // Store both flag key and number key
                if (flag != null) { options.put(flag, args[i]); }
                options.put(new Integer(i), args[i]);
                flag = null;
            }
            
        }
        if (flag != null) { options.put(flag, "no value"); }
        return options;
    }

    /**
     * Sets a comment for the flag.
     * 
     * @param flag the flag to set the comment for.
     * @param comment the comment to use when printing help for the given flag.
     */
    public void setComment(final String flag, final String comment) {
        if (flag == null) { return; }
        CmdLineOption opt = (CmdLineOption) _optionInfo.get(flag);
        if (opt != null) { opt.setComment(comment); }
    }
    
    /**
     * Sets whether or not a given flag is optional.
     * 
     * @param flag the flag to set optionality for.
     * @param optional the boolean indicating the optionality for the given flag.
     */
    public void setOptional(final String flag, final boolean optional) {
        if (flag == null) { return; }
        CmdLineOption opt = (CmdLineOption) _optionInfo.get(flag);
        if (opt != null) { opt.setOptional(optional); }
    }
    
    /**
     * Sets the text to print after the flag when printing the usage line.
     * 
     * @param flag the flag to set the usage info for.
     * @param usage the usage text.
     */
    public void setUsageInfo(final String flag, final String usage) {
        if (flag == null) { return; }
        CmdLineOption opt = (CmdLineOption) _optionInfo.get(flag);
        if (opt != null) { opt.setUsageText(usage); }
    }

    public void printUsage(final PrintWriter pw) {
        pw.println();
        pw.print(Messages.message("misc.CLIUsage"));
        for (int i = 0; i < _flags.size(); i++) {
            String flag = (String) _flags.elementAt(i);
            CmdLineOption opt = (CmdLineOption) _optionInfo.get(flag);
            if (opt.getOptional()) {
                pw.print(" [-");
            } else {
                pw.print(" -");
            }
            pw.print(flag);
            String usage = opt.getUsageText();
            if (usage != null) {
                pw.print(' ');
                pw.print(usage);
            }
            if (opt.getOptional()) {
                pw.print(']');
            }
        }
        pw.println();
        pw.flush();
    }
    
    public void printHelp(final PrintWriter pw) {
        printUsage(pw);
        pw.println();
        
        if (_flags.size() > 0) {
            pw.println("Flag               Description");
            pw.println("----------------------------------------------");
        }
        for (int i = 0; i < _flags.size(); i++) {
            String flag = (String) _flags.elementAt(i);
            CmdLineOption opt = (CmdLineOption) _optionInfo.get(flag);
            
            pw.print('-');
            pw.print(flag);
            
            pw.print(' ');
            //-- adjust spacing
            int spaces = 17 - flag.length();
            while (spaces > 0) { 
                pw.print(' ');
                --spaces;
            }
            
            pw.print(opt.getComment());
            
            //String usage = opt.getUsageText();
            //if (usage != null) {
            //    pw.print(' ');
            //    pw.print(usage);
            //}
            //if (opt.getOptional()) pw.print(']');
            pw.println();
        }
        pw.println();
        pw.flush();
    }
}

class CmdLineOption {
    private boolean _optional = false;
    private String _usageText = null;
    private String _comment = null;
    private String _flag = null;
    
    /**
     * Creates a new CmdLineOption.
     * 
     * @param flag The flag associated with this command line option.
     */
    CmdLineOption(final String flag) {
        super();
        _flag = flag;
    }
    
    /**
     * Returns the flag associated with this command line option.
     * 
     * @return the flag associated with this command line option.
     */
    public String getFlag() {
        return _flag;
    }
    
    /**
     * Returns whether or not this CmdLineOption is optional or not.
     * 
     * @return true if this CmdLineOption is optional, otherwise false.
    */
    public boolean getOptional() {
        return _optional;
    }
    
    /**
     * Returns the comment for this option.
     * 
     * @return the comment for this command line option.
     */
    public String getComment() {
        return _comment;
    }
    
    /**
     * Returns the text to print after the flag when printing the usage line.
     * 
     * @return the text to print after the flag when printing the usage line.
     */
    public String getUsageText() {
        return _usageText;
    }
    
    /**
     * Sets whether or not this CmdLineOption is optional or not.
     * 
     * @param optional the flag indicating whether or not this CmdLineOption
     *        is optional.
     */
    public void setOptional(final boolean optional) {
        _optional = optional;
    }
    
    /**
     * Sets a comment for the flag.
     * 
     * @param comment the comment to use when printing help for the given flag.
     */
    public void setComment(final String comment) {
        _comment = comment;
    }
    
    /**
     * Sets the text to print after the flag when printing the usage line.
     * 
     * @param usageText the usage text.
     */
    public void setUsageText(final String usageText) {
        _usageText = usageText;
    }
}

