

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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.logging.Level;

//-----------------------------------------------------------------------//
// Composer-specific collections from the parse tree:
// (adds composer-specific {@link Object#toString()} methods)
//-----------------------------------------------------------------------//

public class BaliRulesData {

    /**
     * Generates JavaCC productions for each Bali rule:
     *
     * @layer<bali2javacc>
     */
    public String toString() {

        List keys = new ArrayList( keySet() ) ;

        StringBuffer buffer = new StringBuffer() ;
        buffer.append( rule2string( getStartName() ) ) ;
        keys.remove( getStartName() ) ;
            
        Collections.sort( keys ) ;
        for ( Iterator p = keys.iterator() ; p.hasNext() ; ) {
            buffer.append( Main.LINE_SEPARATOR ) ;
            buffer.append( Main.LINE_SEPARATOR ) ;
            buffer.append( rule2string( ( String ) p.next() ) ) ;
        }

        return buffer.toString() ;
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //

    private String assignmentCode( CodeBuffer code, Primitive prim, Variables variables ) {

        if ( prim instanceof BaliTokenNode ) {
            String var = variables.getFree( "Token" ) ;
            code.append( var + "=<" + prim.tok[0].tokenName() + '>' ) ;
            return "t2at(" + var + ')' ;
        }

        if ( prim instanceof IdentifierNode ) {
            String name = prim.tok[0].tokenName() ;
            String type = getType( name ) ;
            String var = variables.getFree( type ) ;
            code.append( var + '=' + name + "()" ) ;
            return var ;
        }

        if ( prim instanceof OptionalNode ) {

            code.append( '[' ) ;

            if ( prim.arg[0].arg [0] != null ) {
                LookaheadNode look = ( LookaheadNode ) prim.arg[0].arg [0] ;
                code.append( "LOOKAHEAD(" + look.arg [0] + ") " ) ;
            }

            String var = assignmentCode( code, ( Primitive ) prim.arg [1], variables ) ;

            code.append( ']' ) ;
            return "opt(" + var + ')' ;
        }

        if ( prim instanceof StringNode ) {
            String var = variables.getFree( "Token" ) ;
            code.append( var + '=' + prim.tok[0].tokenName() ) ;
            return "t2at(" + var + ')' ;
        }

        throw new IllegalStateException( "unexpected Primitive: " + prim ) ;
    }

    private String compact( String text ) {
        return text.trim().replaceAll( "\\s+", " " ) ;
    }

    private String complexList( String rule, Primitive item, ComplexListNode rest, Variables vars ) {

        CodeBuffer code = new CodeBuffer() ;
        String result = vars.declare( rule, "list", "new " + rule + " ()" );

        String var = assignmentCode( code, item, vars ) ;
        code.endLine().append( "{" + result + ".add (new " ) ;
        code.append( rule + "Elem().setParms (" + var + ")) ;}" ) ;

        code.endLine().append( '(' ) ;

        // Handle LOOKAHEAD:
        //
        code.endLine().indent() ;
        if ( rest.arg[0].arg [0] != null ) {
            LookaheadNode node = ( LookaheadNode ) rest.arg[0].arg [0] ;
            code.append( "LOOKAHEAD(" + node.arg [0] + ") " ) ;
            code.endLine() ;
        }

        vars.reset() ;

        Primitive one = ( Primitive ) rest.arg [1] ;
        String varOne = assignmentCode( code, one, vars ) ;
        code.endLine() ;

        Primitive two = ( Primitive ) rest.arg [2] ;
        String varTwo = assignmentCode( code, two, vars ) ;

        code.endLine().append( "{" + result + ".add (new " ) ;
        code.append( rule + "Elem().setParms (" + varOne + ", " + varTwo + ")) ;}" ) ;

        code.endLine().outdent().append( ")*" ) ;

        code.endLine().append( "{return " + result + " ;}" ) ;
        return code.toString() ;
    }

    private String merger2string( Merger merger, Variables variables ) {

        ProductionNode namedNode = merger.getNamed() ;
        IdentifierNode nonTerminal = merger.getNonterminal() ;
        String rule = merger.getRule() ;
        ProductionNode unnamedNode = merger.getUnnamed() ;

        CodeBuffer code = new CodeBuffer() ;

        code.append( "// Merged productions from rule " + rule ) ;
        code.endLine() ;
        code.append( "// (*) " + compact( unnamedNode.toString() ) ) ;
        code.endLine() ;
        code.append( "// (*) " + compact( namedNode.toString() ) ) ;
        code.endLine() ;
        code.append( "// " ) ;
        code.endLine() ;

        List args = new ArrayList() ;
        args.add( assignmentCode( code, nonTerminal, variables ) ) ;

        code.endLine().append( '[' ).endLine().indent() ;
        code.append( "LOOKAHEAD(2)" ) ;

        // Refactor with similar code from "namedRule" method?
        //
        Pattern pat = ( Pattern ) namedNode.arg[1].arg[1].arg[0].arg [0] ;
        List list = ( pat != null ) ? pat.toList() : Collections.EMPTY_LIST ;
        for ( Iterator p = list.iterator() ; p.hasNext() ; ) {
            Primitive prim = ( Primitive ) p.next() ;
            code.endLine() ;
            args.add( assignmentCode( code, prim, variables ) ) ;
        }

        StringBuffer arg = new StringBuffer() ;
        Iterator p = args.iterator() ;
        if ( p.hasNext() )
            arg.append( p.next().toString() ) ;
        while ( p.hasNext() ) {
            arg.append( ", " ) ;
            arg.append( p.next().toString() ) ;
        }

        String node =
                namedNode.arg[1].arg[1].arg[1].arg[0].tok[1].tokenName() ;

        code.endLine().append( "{return new " + node + "().setParms" ) ;
        code.append( "(" + arg + ") ;}" ) ;
        code.endLine().outdent().append( ']' ) ;

        String var = ( String ) args.get( 0 ) ;
        code.endLine().append( "{return (" + rule + ") " + var + " ;}" ) ;
        return code.toString() ;
    }

    private String namedRule( String rule,Primitive item,Pattern pat,String node,Variables vars ) {

        CodeBuffer code = new CodeBuffer() ;
        List args = new ArrayList() ;
        args.add( assignmentCode( code, item, vars ) ) ;

        List patList =
                ( pat != null ) ? pat.toList() : Collections.EMPTY_LIST ;

        for ( Iterator p = patList.iterator() ; p.hasNext() ; ) {
            Primitive prim = ( Primitive ) p.next() ;
            code.endLine() ;
            args.add( assignmentCode( code, prim, vars ) ) ;
        }

        StringBuffer arg = new StringBuffer() ;
        Iterator p = args.iterator() ;
        if ( p.hasNext() )
            arg.append( p.next().toString() ) ;
        while ( p.hasNext() ) {
            arg.append( ", " ) ;
            arg.append( p.next().toString() ) ;
        }

        code.endLine().append( "{return new " + node + "().setParms" ) ;
        code.append( "(" + arg + ") ;}" ) ;
        return code.toString() ;
    }

    private String production2string( String rule, ProductionNode production, Variables variables ) {

        if ( production instanceof Merger )
            return merger2string( ( Merger ) production, variables ) ;

        CodeBuffer code = new CodeBuffer() ;

        // Generate leading LOOKAHEAD, if any:
        //
        AstNode node = production.arg[0].arg [0] ;
        if ( node != null )
            code.append( "LOOKAHEAD(" + node.arg [0] + ") " ) ;

        // Separate the production types and generate code for each:
        //
        Rewrite rewrite = ( Rewrite ) production.arg [1] ;

        // Handle a "(item)+" list:
        //
        if ( rewrite instanceof SimpleListNode ) {
            SimpleListNode list = ( SimpleListNode ) rewrite ;
            code.append( simpleList( rule, list, variables ) ) ;
            return code.toString() ;
        }

        // Handle a "item1 (item2)*" list:
        //
        Primitive item1 = ( Primitive ) rewrite.arg [0] ;
        PrimitiveRewrite rest = ( PrimitiveRewrite ) rewrite.arg [1] ;
        if ( rest instanceof ComplexListNode ) {
            ComplexListNode list = ( ComplexListNode ) rest ;
            code.append( complexList( rule, item1, list, variables ) ) ;
            return code.toString() ;
        }

        // Handle a named rule (i.e., a ":: NodeName" at production's end):
        //
        node = rest.arg[0].arg [0] ;
        Pattern pattern = ( node != null ) ? ( Pattern ) node : null ;
        if ( rest.arg[1].arg [0] != null ) {
            String name = rest.arg[1].arg[0].tok[1].tokenName() ;
            code.append( namedRule( rule,item1,pattern,name,variables ) ) ;
            return code.toString() ;
        }

        // Currently, unnamed rules with right-hand sequences are errors:
        //
        if ( pattern != null )
            throw new IllegalStateException( "unnamed rule with right-hand sequence: " + rule ) ;

        // What remains are productions with singleton right-hand sides.
        // If the field is a rule name, it's an unnamed rule:
        //
        if ( item1 instanceof IdentifierNode ) {
            code.append( unnamedRule( rule, ( IdentifierNode ) item1, variables ) ) ;
            return code.toString() ;
        }

        throw new IllegalStateException( "singleton production without a name: " + rule ) ;
    }

    /**
     * Examines a {@link List} of productions for special cases where two
     * or more productions should be combined, such as left-recursion via
     * an "unnamed rule", and modifies the {@link List} to add the combined
     * productions while removing their source productions.
     *
     * @layer<bali2javacc>
     */
    private List productionsMerge( String rule ) {

        List productions = ( List ) get( rule ) ;
        if ( productions.size() < 2 )
            return productions ;

        // Extract productions to merge:
        //
        Map mergers = new HashMap() ;
        for ( Iterator p = productions.iterator() ; p.hasNext() ; ) {

            ProductionNode pn = ( ProductionNode ) p.next() ;
            if ( pn.arg[0].arg [0] != null ) // Disallow LOOKAHEAD.
                continue ;

            Rewrite rewrite = ( Rewrite ) pn.arg [1] ;
            if ( ! ( rewrite instanceof PrimitiveRewriteNode ) )
                continue ;
            if ( ! ( rewrite.arg [0] instanceof IdentifierNode ) )
                continue ;

            PrimitiveRewrite pr = ( PrimitiveRewrite ) rewrite.arg [1] ;
            if ( ! ( pr instanceof PatternNode ) )
                continue ;
            if ( pr.arg[0].arg [0] != null && pr.arg[1].arg [0] == null )
                continue ;

            IdentifierNode identifier = ( IdentifierNode ) rewrite.arg [0] ;
            String nonTerminal = identifier.tok[0].tokenName() ;
            Merger merger = ( Merger ) mergers.get( nonTerminal ) ;
            if ( merger == null ) {
                merger = new Merger( rule, identifier ) ;
                mergers.put( nonTerminal, merger ) ;
            }

            if ( pr.arg[1].arg [0] != null )
                merger.setNamed( pn ) ;
            else
                if ( pr.arg[0].arg [0] == null )
                    merger.setUnnamed( pn ) ;
        }

        if ( mergers.size() < 1 )
            return productions ;

        // For complete mergers, modify the {@link List} of productions:
        //
        List newProductions = new ArrayList( productions ) ;
        for ( Iterator p = mergers.keySet().iterator() ; p.hasNext() ; ) {
            String nonTerminal = ( String ) p.next() ;
            Merger merger = ( Merger ) mergers.get( nonTerminal ) ;
            if ( merger.isComplete() ) {
                ProductionNode named = merger.getNamed() ;
                ProductionNode unnamed = merger.getUnnamed() ;
                newProductions.remove( unnamed ) ;
                int namedIndex = newProductions.indexOf( named ) ;
                newProductions.set( namedIndex, merger ) ;
            }
        }

        return newProductions ;
    }

    private String rule2string( String rule ) {

        CodeBuffer code = new CodeBuffer() ;
        Variables variables = new Variables() ;

        Iterator p = productionsMerge( rule ).iterator() ;
        if ( ! p.hasNext() )
            throw new IllegalStateException( "no productions: " + rule ) ;

        ProductionNode node = ( ProductionNode ) p.next() ;
        code.append( production2string( rule, node, variables ) ) ;
        while ( p.hasNext() ) {
            variables.reset() ;
            node = ( ProductionNode ) p.next() ;
            code.endLine().append( '|' ).endLine() ;
            code.append( production2string( rule,node,variables ).trim() ) ;
        }

        String clauses = code.toString().trim() ;
        code.clear().append( rule + ' ' + rule + " () : {" ) ;
        code.endLine().indent() ;

        String declarations = variables.toString() ;
        if ( declarations.length() > 0 )
            code.appendLines( declarations ).endLine() ;

        code.outdent().append( "} {" ) ;
        code.endLine().indent().appendLines( clauses ) ;
        code.endLine().outdent().append( '}' ) ;

        return code.toString() ;
    }

    private String simpleList( String rule, SimpleListNode list, Variables vars ) {

        CodeBuffer code = new CodeBuffer() ;
        String result = vars.declare( rule, "list", "new " + rule + " ()" );

        // Handle LOOKAHEAD:
        //
        code.append( '(' ).endLine().indent() ;
        if ( list.arg[0].arg [0] != null ) {
            LookaheadNode node = ( LookaheadNode ) list.arg[0].arg [0] ;
            code.append( "LOOKAHEAD(" + node.arg [0] + ") " ) ;
            code.endLine() ;
        }

        String var =
                assignmentCode( code, ( Primitive ) list.arg[1], vars ) ;

        code.endLine().append( "{" + result + ".add (new " ) ;
        code.append( rule + "Elem().setParms (" + var + ")) ;}" ) ;
        code.endLine().outdent().append( ")+" ) ;

        code.endLine().append( "{return " + result + " ;}" ) ;
        return code.toString() ;
    }

    private String unnamedRule( String rule, IdentifierNode item, Variables variables ) {

        CodeBuffer code = new CodeBuffer() ;
        String var = assignmentCode( code, item, variables ) ;
        code.endLine().append( "{return (" + rule + ") " + var + " ;}" ) ;

        return code.toString() ;
    }

}
