package org.incava.jagol;

import java.io.*;
import java.util.*;
import org.incava.log.Log;


/**
 * A group of options.
 */
public class OptionSet
{
    private List options = new ArrayList();

    private List rcFiles = new ArrayList();

    private String appName;
    
    private String description;
    
    public OptionSet(String appName, String description)
    {
        this.appName = appName;
        this.description = description;
    }
    
    /**
     * Returns the application name.
     */
    public String getAppName()
    {
        return appName;
    }

    /**
     * Returns the description.
     */
    public String getDescription()
    {
        return description;
    }

    /**
     * Adds an options to this set.
     */
    public void add(Option opt)
    {
        options.add(opt);
    }

    /**
     * Adds a run control file to be processed.
     */
    public void addRunControlFile(String name)
    {
        Log.log("adding rc file: " + name);
        rcFiles.add(name);
    }

    /**
     * Processes the run control files and command line arguments. Returns the
     * arguments that were not consumed by option processing.
     */
    public String[] process(String[] args)
    {
        Log.log("args: " + args);

        processRunControlFiles();

        return processCommandLine(args);
    }

    /**
     * Processes the run control files, if any.
     */
    protected void processRunControlFiles()
    {
        Log.log("");
        Iterator it = rcFiles.iterator();
        while (it.hasNext()) {
            String rcFileName = (String)it.next();
            Log.log("processing: " + rcFileName);
            try {
                Properties props = new Properties();

                int tildePos = rcFileName.indexOf('~');
                if (tildePos != -1) {
                    rcFileName = rcFileName.substring(0, tildePos) + System.getProperty("user.home") + rcFileName.substring(tildePos + 1);
                }

                props.load(new FileInputStream(rcFileName));

                Log.log("properties: " + props);
                Iterator pit = props.keySet().iterator();
                while (pit.hasNext()) {
                    String key   = (String)pit.next();
                    String value = (String)props.get(key);
                    Log.log(key + " => " + value);
                    Iterator oit = options.iterator();
                    boolean processed = false;
                    while (!processed && oit.hasNext()) {
                        Option opt = (Option)oit.next();
                        Log.log("option: " + opt.getLongName());
                        if (opt.getLongName().equals(key)) {
                            Log.log("option matches: " + opt);
                            processed = true;
                            try {
                                opt.setValue(value);
                            }
                            catch (OptionException oe) {
                                Log.log("option exception: " + oe);
                                System.err.println("error: " + oe.getMessage());
                            }
                        }
                    }
                }
            }
            catch (IOException ioe) {
                Log.log("exception: " + ioe);
                // ioe.printStackTrace();
            }
        }
    }

    /**
     * Processes the command line arguments. Returns the arguments that were not
     * consumed by option processing.
     */
    protected String[] processCommandLine(String[] args)
    {
        Log.log("args: " + args);
        
        List argList = new ArrayList();
        for (int i = 0; i < args.length; ++i) {
            argList.add(args[i]);
        }

        Log.log("arg list: " + argList);

        while (argList.size() > 0) {
            String arg = (String)argList.get(0);
            
            Log.log("arg: " + arg);
            
            if (arg.equals("--")) {
                argList.remove(0);
                break;
            }
            else if (arg.charAt(0) == '-') {
                Log.log("got leading dash");
                argList.remove(0);
                
                Iterator oit = options.iterator();
                boolean processed = false;
                while (!processed && oit.hasNext()) {
                    Option opt = (Option)oit.next();
                    Log.log("option: " + opt);
                    try {
                        processed = opt.set(arg, argList);
                        Log.log("processed: " + processed);
                    }
                    catch (OptionException oe) {
                        Log.log("option exception: " + oe);
                        System.err.println("error: " + oe.getMessage());
                    }
                }

                if (!processed) {
                    Log.log("argument not processed: '" + arg + "'");
                    if (arg.equals("--help") || arg.equals("-h")) {
                        showUsage();
                    }
                    else if (rcFiles.size() > 0 && arg.equals("--help-config")) {
                        showConfig();
                    }
                    else {
                        System.err.println("invalid option: " + arg + " (-h will show valid options)");
                    }
                    
                    break;
                }
            }
            else {
                break;
            }
        }

        Log.log("unprocessed arguments: " + argList);
        String[] unprocessed = (String[])argList.toArray(new String[0]);
        
        Log.log("args");
        for (int i = 0; i < args.length; ++i) {
            Log.log("arg[" + i + "]: " + args[i]);
        }

        Log.logColor(Log.BLUE, "unprocessed: " + unprocessed);
        for (int i = 0; i < unprocessed.length; ++i) {
            Log.logColor(Log.BLUE, "unprocessed[" + i + "]: " + unprocessed[i]);
        }

        return unprocessed;
    }

    protected void showUsage()
    {
        Log.log("generating help");

        System.out.println("Usage: " + appName + " [options] file...");
        System.out.println(description);
        System.out.println();
        System.out.println("Options:");

        Log.log("options: " + options);

        List tags = new ArrayList();

        Iterator it = options.iterator();
        while (it.hasNext()) {
            Option opt = (Option)it.next();
            Log.log("opt: " + opt);
            StringBuffer buf = new StringBuffer("  ");

            if (opt.getShortName() == 0) {
                buf.append("    ");
            }
            else {
                buf.append("-" + opt.getShortName() + ", ");
            }
                            
            buf.append("--" + opt.getLongName());
                            
            tags.add(buf.toString());
        }
                        
        int widest = -1;
        Iterator tit = tags.iterator();
        while (tit.hasNext()) {
            String tag = (String)tit.next();
            widest = Math.max(tag.length(), widest);
        }

        it = options.iterator();
        tit = tags.iterator();
        while (it.hasNext()) {
            Option opt = (Option)it.next();
            String tag = (String)tit.next();
            Log.log("opt: " + opt);

            System.out.print(tag);
            for (int i = tag.length(); i < widest + 2; ++i) {
                System.out.print(" ");
            }

            System.out.println(opt.getDescription());
        }

        if (rcFiles.size() > 0) {
            System.out.println("For an example configure file, run --help-config");
            System.out.println();
            System.out.println("Configuration File" + (rcFiles.size() > 1 ? "s" : "") + ":");
            Iterator rit = rcFiles.iterator();
            while (rit.hasNext()) {
                String rcFileName = (String)rit.next();
                System.out.println("    " + rcFileName);
            }
        }
    }

    protected void showConfig()
    {
        Log.log("generating config");

        Iterator it = options.iterator();
        while (it.hasNext()) {
            Option opt = (Option)it.next();
            System.out.println("# " + opt.getDescription());
            System.out.println(opt.getLongName() + " = " + opt.toString());
            System.out.println();
        }
    }
}
