

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Level;

//-----------------------------------------------------------------------//
// Main class:
//-----------------------------------------------------------------------//

/**
 * Provides a <code>driver</code> to compose a sequence of Bali
 * source files into a single Bali output file.
 *
 * @layer<composer>
 */
    
public class Main {

    /**
     * Handles argument parsing, source parsing, parse-tree collections,
     * and code generation.
     *
     * @return {@link $TEqn.Collector}
     * with collected data from parse trees.
     *
     * @layer<composer>
     */
    public Object driver( String[] args ) throws Throwable {

        setVersion( "v2003.02.17" ) ;

        try {
            parseArguments( Arrays.asList( args ) ) ;
        }
        catch ( Exception exception ) {
            usage( exception.getMessage() ) ;
            throw exception ;
        }

        Collector collector = collectSources( inpFiles ) ;
        generateObject( collector, writer ) ;
        writer.close() ;

        Main.DEBUG.exiting( "composer.Main", "driver", "collector" ) ;
        return collector ;
    }

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    /**
     * Given an {@link Object}, returns a {@link String} containing the
     * normalized {@link Class} name for the object.
     *
     * @layer<composer>
     */
    private static String className( Object object ) {
        return
                ( object != null )
		? object.getClass().getName()
		: "null" ;
    }

    /**
     * Processes a {@link List} of input {@link File} objects, each with
     * Bali source code, by parsing each into a parse tree, then running
     * a {@link $TEqn.Collector} over the trees to gather all necessary
     * data for later code generation.
     *
     * @return {@link $TEqn.Collector} with collected data from parse trees.
     *
     * @layer<composer>
     */
    private  Collector collectSources( List inpFiles )
    throws IOException {
        Main.DEBUG.entering( "composer.Main", "collectSources", inpFiles ) ;

        Collector collector = new  Collector() ;

        for ( Iterator p = inpFiles.iterator() ; p.hasNext() ; ) {

            File inputFile = ( File ) p.next() ;

            Reader reader =
                            new BufferedReader( new InputStreamReader( new FileInputStream( inputFile ),
                                        "ISO-8859-1" ) ) ;

            BaliParse tree = null ;
            try {
                Parser parser =  Parser.getInstance( reader ) ;
                tree = (BaliParse) parser.parseAll () ;

            }
            catch ( ParseException thrown ) {
                IllegalStateException except = new IllegalStateException( "parse error in file " + inputFile ) ;
                except.initCause( thrown ) ;
                throw except ;

            }
            finally {
                reader.close() ;
            }

            collector.dispatch( tree ) ;
        }

        Main.DEBUG.exiting( "composer.Main", "collectSources", collector ) ;
        return collector ;
    }

    /**
     * Generates a single Bali file representing the data collected in the
     * {@link $TEqn.Collector} argument.
     *
     * @layer<composer>
     */
    public void generateObject( Collector collector, Writer writer )
    throws IOException {
        Main.DEBUG.entering( "composer.Main", "generateObject", writer ) ;

        writer.write( "// Automatically generated Bali code.  Edit at your own risk!"
                + Main.LINE_SEPARATOR
                + "// Generated by \"balicomposer\" "
                + Main.getVersion()
                + '.'
                + Main.LINE_SEPARATOR
                + Main.LINE_SEPARATOR
                + collector
                + Main.LINE_SEPARATOR ) ;
        writer.flush() ;

        Main.DEBUG.exiting( "composer.Main", "generateObject" ) ;
    }

    /**
     * Processes a {@link List} of {@link String} arguments that specify
     * the input files and output file.
     *
     * @see #usage()
     *
     * @layer<composer>
     */
    private void parseArguments( List args ) throws IOException {

        Level debugLevel = Level.OFF ;
        inpFiles = new ArrayList() ;

        for ( ListIterator p = args.listIterator() ; p.hasNext() ; ) {

            String arg = ( String ) p.next() ;
            if ( arg.equals( "-debug" ) ) {
                debugLevel = Level.INFO ;
                if ( p.hasNext() ) {
                    String peek = ( String ) p.next() ;
                    if ( peek.charAt( 0 ) != '-' )
                        debugLevel = Level.parse( peek.toUpperCase() ) ;
                    else
                        p.previous() ;
                }
                continue ;
            }

            if ( arg.equals( "-output" ) && p.hasNext() )
                writer = parseWriter( ( String ) p.next() ) ;
            else
                if ( arg.equals( "-o" ) && p.hasNext() )
                    writer = parseWriter( ( String ) p.next() ) ;
                else
                    if ( arg.charAt( 0 ) == '-' )
                        throw new IllegalArgumentException( "invalid: " + arg ) ;
                    else
                        inpFiles.add( parseInputFile( arg ) ) ;
        }

        Main.DEBUG.setLevel( debugLevel ) ;

        if ( inpFiles.size() < 1 )
            throw new IllegalArgumentException( "no input files" ) ;

        if ( writer == null )
            writer = new PrintWriter( System.out, true ) ;

        return ;
    }

    private File parseInputFile( String fileName ) {
        File file = new File( fileName ) ;

        if ( ! file.exists() )
            throw new IllegalArgumentException( "file doesn't exist: "
                            + fileName ) ;

        if ( ! file.canRead() )
            throw new IllegalArgumentException( "file can't be read: "
                            + fileName ) ;

        return file ;
    }

    private BufferedWriter parseWriter( String fileName )
    throws IOException {

        return new BufferedWriter( new OutputStreamWriter( new FileOutputStream( fileName ),
                    "ISO-8859-1" ) ) ;
    }

    private void usage( String message ) {

        String program = className( this ) ;

        if ( message != null )
            System.err.println( program + ": " + message ) ;

        System.err.println( "Usage: java "
                + program
                + " <Bali-source-file> ..."
                + " -output <output-file>" ) ;
    }

    private List inpFiles = null ;
    private Writer writer = null ;

}
