

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;

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

/**
 * Provides a <code>driver</code> to translate a Bali source file into a
 * set of Jak source files.
 *
 * @layer<bali2jak>
 */
    
public class Main {

    final private static String PROPERTIES = "bali2jak.properties" ;

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

        setVersion( "v2002.09.03" ) ;

        Files.setProgram( "bali2jak" ) ;
        Files.setVersion( getVersion() ) ;

        loadProperties( PROPERTIES ) ;

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

        Main.DEBUG.info( "-layer " + String.valueOf( argLayer ) ) ;
        Main.DEBUG.info( "-directory " + String.valueOf( argDirectory ) ) ;
        Main.DEBUG.info( "source " + String.valueOf( argSourceFile ) ) ;

        Collector collector = collectSource( argSourceFile ) ;
        generateObject( collector ) ;

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

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

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

    /**
     * Processes an input {@link File} (with Bali source code) into a parse
     * tree, then running a {@link $TEqn.Collector} over the tree to gather
     * all necessary data for later code generation.
     *
     * @return {@link $TEqn.Collector} with collected data from parse tree.
     *
     * @layer<bali2jak>
     */
    private  Collector collectSource( File inpFile )
    throws IOException, ParseException {
        Main.DEBUG.entering( "bali2jak.Main", "collectSource", inpFile ) ;

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

        Parser parser =  Parser.getInstance( reader ) ;
        BaliParse tree = ( BaliParse ) parser.parseAll () ;
        reader.close() ;

        Collector collector = new  Collector() ;
        collector.dispatch( tree ) ;

        Main.DEBUG.exiting( "bali2jak.Main", "collectSource", collector ) ;
        return collector ;
    }

    /**
     * Generates the object code {@link File} objects (Jak class files for
     * parse tree nodes) from a {@link $TEqn.Collector} argument.
     *
     * @layer<bali2jak>
     */
    public void generateObject( Collector collector )
    throws IOException {
        Main.DEBUG.entering( "bali2jak.Main", "generateObject" ) ;

        collector.setLayer( argLayer ) ;

        // Generate syntax tree node classes for Bali grammar rules:
        //
        collector.generateNonterminals( argDirectory ) ;

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

    public void loadProperties( String source ) {
        Main.DEBUG.entering( "bali2jak.Main", "loadProperties", source ) ;

        // Allow users to rename resource via system properties:
        //
        String resource = System.getProperty( source, source ) ;

        // Load properties from properties resource:
        //
        Properties properties = new Properties() ;
        try {
            properties.load( ClassLoader.getSystemResourceAsStream( resource ) ) ;
            Main.DEBUG.info( "properties loaded from resource " + resource ) ;
        }
        catch ( NullPointerException thrown ) {
            return ; // Resource not found is ok.
        }
        catch ( IOException thrown ) {
            IllegalStateException exception = 
                            new IllegalStateException( "resource error " + resource ) ;
            exception.initCause( thrown ) ;
            throw exception ;
        }

        // Create a new system Properties object containing the current
        // system properties, but using the application Properties as
        // defaults.  This way, properties defined on the command line
        // (which show up as system properties) override application
        // properties defined via relatively static sources such as
        // resources and files.
        //
        Properties newSystem = new Properties( properties ) ;
        newSystem.putAll( System.getProperties() ) ;
        System.setProperties( newSystem ) ;

        Main.DEBUG.exiting( "bali2jak.Main", "loadProperties" ) ;
    }

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

        // Set default values:
        //
        argLayer = null ; // Generated layer name.
        argDebug = Level.OFF ; // How much debugging output.
        argDirectory = null ; // Output directory.
        argSourceFile = null ; // Bali source file.

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

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

            if ( arg.equals( "-layer" ) && p.hasNext() )
                argLayer = parseLayer( ( String ) p.next() ) ;
            else
                if ( arg.equals( "-a" ) && p.hasNext() )
                    argLayer = parseLayer( ( String ) p.next() ) ;
                else
                    if ( arg.equals( "-directory" ) && p.hasNext() )
                        argDirectory = parseDirectory( ( String ) p.next() ) ;
                    else
                        if ( arg.equals( "-d" ) && p.hasNext() )
                            argDirectory = parseDirectory( ( String ) p.next() ) ;
                        else
                            if ( arg.charAt( 0 ) == '-' )
                                throw new IllegalArgumentException( "invalid: " + arg ) ;
                            else
                                argSourceFile = parseSourceFile( arg ) ;
        }

        Main.DEBUG.setLevel( argDebug ) ;

        if ( argSourceFile == null ) {
            String message = "no Bali source file specified" ;
            throw new IllegalArgumentException( message ) ;
        }

        if ( argDirectory == null )
            argDirectory = parseDirectory( "." ) ;

        if ( argLayer == null ) {

            File directory ;
            try {
                directory = argDirectory.getCanonicalFile() ;
            }
            catch ( IOException exception ) {
                directory = argDirectory.getAbsoluteFile() ;
            }

            argLayer = parseLayer( directory.getName() ) ;
        }

        return ;
    }

    private String parseLayer( String layerName ) {

        if ( argLayer != null )
            throw new IllegalArgumentException( "option \"-layer\" appears more than once" ) ;

        if ( ! layerName.matches( "^[\\p{Alpha}_$][\\p{Alnum}_$]*$" ) )
            throw new IllegalArgumentException( "option \"-layer\" doesn't specify an identifier" ) ;

        return layerName ;
    }

    private File parseDirectory( String directoryName ) {

        if ( argDirectory != null )
            throw new IllegalArgumentException( "option \"-directory\" appears more than once" ) ;

        File directory = new File( directoryName ) ;

        if ( ! directory.exists() )
            directory.mkdirs() ;

        if ( ! directory.isDirectory() )
            throw new IllegalArgumentException( "file \""
                            + directory
                            + "\" is not a directory" ) ;

        return directory ;
    }

    private File parseSourceFile( String fileName ) {

        if ( argSourceFile != null )
            throw new IllegalArgumentException( "more than one Bali source file specified" ) ;

        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 void usage( String message ) {

        String program = className( this ) ;

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

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

    private Level argDebug = Level.OFF ; // How many debug messages.
    private String argLayer = null ; // Generated layer name.
    private File argDirectory = null ; // Output directory.
    private File argSourceFile = null ; // Bali source file.

}
