/*
 * CodeBuilder.java
 *
 * Created on 2001/08/05, 14:34
 */

package relaxngcc.builder;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import relaxngcc.NGCCGrammar;
import relaxngcc.Options;
import relaxngcc.automaton.Alphabet;
import relaxngcc.automaton.Head;
import relaxngcc.automaton.State;
import relaxngcc.automaton.Transition;
import relaxngcc.codedom.CDBlock;
import relaxngcc.codedom.CDCastExpression;
import relaxngcc.codedom.CDClass;
import relaxngcc.codedom.CDConstant;
import relaxngcc.codedom.CDExpression;
import relaxngcc.codedom.CDLanguageSpecificString;
import relaxngcc.codedom.CDMethod;
import relaxngcc.codedom.CDMethodInvokeExpression;
import relaxngcc.codedom.CDObjectCreateExpression;
import relaxngcc.codedom.CDOp;
import relaxngcc.codedom.CDStatement;
import relaxngcc.codedom.CDSwitchStatement;
import relaxngcc.codedom.CDType;
import relaxngcc.codedom.CDVariable;
import relaxngcc.datatype.NoDefinitionException;
import relaxngcc.grammar.NGCCDefineParam;
import relaxngcc.grammar.NameClass;
import relaxngcc.grammar.SimpleNameClass;

/**
 * generates Java code that parses XML data via NGCCHandler interface
 */
public class CodeBuilder
{
    //variables in generated code have this prefix
    private static final String GENERATED_VARIABLE_PREFIX = "$";
    
    
    private ScopeInfo _info;
    private NGCCGrammar _grammar;
    private Options _options;
    
    public CodeBuilder(NGCCGrammar grm, ScopeInfo sci, Options o) {
        _info = sci;
        _grammar = grm;
        _options = o;
    }
    
    
    /** Special transition that means "revert to the parent." */
    private static final Transition REVERT_TO_PARENT = new Transition(null,null,0);
    /** Special transition that means "join with other branches. */
    private static final Transition JOIN = new Transition(null,null,0);
    

    private CDClass _classdef;
    /** Reference to _ngcc_current_state. */
    private CDVariable _$state;
    /** Reference to runtime. */
    private CDVariable _$runtime;
    /** Reference to super.source */
    private final CDExpression _$source = CDConstant.SUPER.prop("_source");
    /** Reference to super.cookie */
    private final CDExpression _$cookie = CDConstant.SUPER.prop("_cookie");
    
    /** Reference to "super". */
    private static final CDExpression _$super = CDConstant.SUPER;
    
    /** Reference to "this". */
    private static final CDExpression _$this = CDConstant.THIS;
    
    private static final CDType interleaveFilterType = new CDType("NGCCInterleaveFilter");
    
    /** Member variables that store uri/localName/qname of the enter/leave element. */
    private CDVariable _$uri,_$localName,_$qname;

    /**
     * Builds the code.
     */
    private CDClass createClassCode(String globalimport) {
        StringBuffer buf = new StringBuffer();
        
        //notice
        println(buf, "/* this file is generated by RelaxNGCC */");
        //package
        if(_grammar.packageName.length()>0)
            println(buf, "package " + _grammar.packageName + ";");
        
        /*
        //imports
        if(_UsingBigInteger)
            output.println("import java.math.BigInteger;");
        if(_UsingCalendar)
            output.println("import java.util.GregorianCalendar;");
        */

        println(buf, "import org.xml.sax.SAXException;");
        println(buf, "import org.xml.sax.Attributes;");
        
        if(!_options.usePrivateRuntime)
            println(buf, "import relaxngcc.runtime.NGCCHandler;");
        if(!_grammar.getRuntimeTypeFullName().equals(_grammar.getRuntimeTypeShortName()))
            println(buf, "import "+_grammar.getRuntimeTypeFullName()+";");
        
        println(buf, globalimport);
        
        if(_info._scope.getImport()!=null)
            println(buf, _info._scope.getImport().trim());

        println(buf, _info.getHeaderSection());
        
        //class name
        NGCCDefineParam param = _info._scope.getParam();
        
        CDClass classdef = new CDClass(
            new CDLanguageSpecificString[]{ new CDLanguageSpecificString(buf.toString()) },
            new CDLanguageSpecificString(param.access),
            param.className,
            new CDLanguageSpecificString("extends NGCCHandler"));
        
        //NSURI constants
        for (Iterator itr = _info.iterateNSURIConstants(); itr.hasNext();) {
            Map.Entry e = (Map.Entry) itr.next();
            
            classdef.addMember(
                new CDLanguageSpecificString("public static final"),
                CDType.STRING,
                (String)e.getValue(),
                new CDConstant((String)e.getKey()));
        }
        
        // aliases
        for (Iterator itr = _info.iterateAliases(); itr.hasNext();) {
            Alias a = (Alias)((Map.Entry)itr.next()).getValue();
            
            // if the alias is already declared explicitly by the <java-body>,
            // don't write it again.
            if(_info.isUserDefinedField(a.name))
                continue;
            
            classdef.addMember(
                new CDLanguageSpecificString("private"), a.type, a.name);
        }

        {// runtime field and the getRuntime method.
            String runtimeBaseName = "relaxngcc.runtime.NGCCRuntime";
            if(_options.usePrivateRuntime) runtimeBaseName = "NGCCRuntime";
    
            _$runtime = classdef.addMember(
                new CDLanguageSpecificString("protected final"),
                new CDType(_grammar.getRuntimeTypeShortName()), GENERATED_VARIABLE_PREFIX+"runtime" );
            
            CDMethod getRuntime = new CDMethod(
                new CDLanguageSpecificString("public final"),
                new CDType(runtimeBaseName),
                "getRuntime", null );
            classdef.addMethod(getRuntime);
            
            getRuntime.body()._return(_$runtime);
        }


        // create references to variables
        _$state = classdef.addMember(
            new CDLanguageSpecificString("private"),
            CDType.INTEGER,
            GENERATED_VARIABLE_PREFIX+"_ngcc_current_state");
        
        // uri, localName, qname variables
        _$uri = classdef.addMember(
            new CDLanguageSpecificString("protected"),
            CDType.STRING,
            GENERATED_VARIABLE_PREFIX+"uri");
        _$localName = classdef.addMember(
            new CDLanguageSpecificString("protected"),
            CDType.STRING,
            GENERATED_VARIABLE_PREFIX+"localName");
        _$qname = classdef.addMember(
            new CDLanguageSpecificString("protected"),
            CDType.STRING,
            GENERATED_VARIABLE_PREFIX+"qname");
        
        
        Alias[] constructorParams = _info.getConstructorParams();
        
        {// internal constructor
            CDMethod cotr1 = new CDMethod(
                new CDLanguageSpecificString("public"),
                null, param.className, null );
            classdef.addMethod(cotr1);
            
            // add parameters (parent,source,runtime,cookie) and call the super class initializer.
            CDVariable $parent = cotr1.param( new CDType("NGCCHandler"), "parent" );
            CDVariable $source = cotr1.param( new CDType("NGCCEventSource"), "source" );
            CDVariable $runtime = cotr1.param( new CDType(_grammar.getRuntimeTypeShortName()), "runtime" );
            CDVariable $cookie = cotr1.param( CDType.INTEGER, "cookie" );
            cotr1.body().invoke("super").arg($source).arg($parent).arg($cookie);
            cotr1.body().assign(_$runtime, $runtime);
            
            // append additional constructor arguments
            for( int i=0; i<constructorParams.length; i++ ) {
                CDVariable v = cotr1.param(
                    constructorParams[i].type,
                    '_'+constructorParams[i].name);
                cotr1.body().assign( _$this.prop(constructorParams[i].name),
                    v );
            }
            
            // move to the initial state
            cotr1.body().assign( _$state,
                new CDConstant(_info.getInitialState().getIndex()) );
        }        
        
        {// external constructor
            CDMethod cotr2 = new CDMethod(
                    new CDLanguageSpecificString("public"),
                    null, param.className, null );
            classdef.addMethod(cotr2);

            CDVariable $runtime = cotr2.param( new CDType(_grammar.getRuntimeTypeShortName()), "runtime" );
            
            // call the primary constructor
            CDMethodInvokeExpression callThis = cotr2.body().invoke("this")
                .arg( CDConstant.NULL )
                .arg( $runtime )
                .arg( $runtime )
                .arg( new CDConstant(-1) );
            
            // append additional constructor arguments
            for( int i=0; i<constructorParams.length; i++ ) {
                CDVariable v = cotr2.param(
                    constructorParams[i].type,
                    '_'+constructorParams[i].name);
                callThis.arg(v);
            }
        }


                
        // action functions
        for (Iterator itr = _info.iterateActions(); itr.hasNext();) {
            ScopeInfo.Action a = (ScopeInfo.Action) itr.next();
            a.generate(classdef);
        }
        
        //simple entry point.
        if(_info.isRoot() && _info._scope.getParam().params==null) {
            String rt = _grammar.packageName;
            if(rt.length()!=0)  rt+='.';
            rt+="NGCCRuntime";

            
            if(_grammar.getRuntimeTypeFullName().equals(rt)) {
                StringBuffer main = new StringBuffer();
                main.append("    public static void main( String[] args ) throws Exception {");                              main.append(_options.newline);
                main.append("        javax.xml.parsers.SAXParserFactory factory = javax.xml.parsers.SAXParserFactory.newInstance();");                           main.append(_options.newline);
                main.append("        factory.setNamespaceAware(true);");                                                     main.append(_options.newline);
                main.append("        org.xml.sax.XMLReader reader = factory.newSAXParser().getXMLReader();");                            main.append(_options.newline);
                main.append("        NGCCRuntime runtime = new NGCCRuntime();");                                             main.append(_options.newline);
                main.append("        reader.setContentHandler(runtime);");                                                   main.append(_options.newline);
                main.append("        for( int i=0; i<args.length; i++ ) {");                                                 main.append(_options.newline);
                main.append("            runtime.setRootHandler(new "+_info.getClassName()+"(runtime));");                   main.append(_options.newline);
                main.append("            reader.parse(new org.xml.sax.InputSource(new java.io.FileInputStream(args[i])));"); main.append(_options.newline);
                main.append("            runtime.reset();");
                main.append("        }");
                main.append("    }");
                classdef.addLanguageSpecificString(new CDLanguageSpecificString(main.toString()));
            }
        }
        
        return classdef;
    }
    
    /**
     * Adds code that are necessary to implement NGCCHandler.
     */
    private CDClass createInterleaveBranchClassCode( State initial ) {
        
        final String className = "Branch"+initial.getIndex();
        
        CDClass classdef = new CDClass(
            new CDLanguageSpecificString[0],
            null,
            className,
            new CDLanguageSpecificString("extends NGCCHandler"));

        {// runtime field and the getRuntime method.
            String runtimeBaseName = "relaxngcc.runtime.NGCCRuntime";
            if(_options.usePrivateRuntime) runtimeBaseName = "NGCCRuntime";
    
            CDMethod getRuntime = new CDMethod(
                new CDLanguageSpecificString("public final"),
                new CDType(runtimeBaseName),
                "getRuntime", null );
            classdef.addMethod(getRuntime);
            
            getRuntime.body()._return(_$runtime);
        }


        // create references to variables
        _$state = classdef.addMember(
            new CDLanguageSpecificString("private"),
            CDType.INTEGER,
            GENERATED_VARIABLE_PREFIX+"_ngcc_current_state");

        {// internal constructor
            CDMethod cotr1 = new CDMethod( null, null, className, null );
            classdef.addMethod(cotr1);
            
            CDVariable $source = cotr1.param(new CDType("NGCCInterleaveFilter"),"source");
            
            // add three parameters (parent,runtime,cookie) and call the super class initializer.
            cotr1.body().invoke("super").arg($source).arg(CDConstant.NULL).arg(new CDConstant(-1));
            
            // move to the initial state
            cotr1.body().assign( _$state,
                new CDConstant(initial.getIndex()) );
        }        
        
        return classdef;
    }
    
    
    public CDClass output() throws NoDefinitionException, IOException {
        // set of Fork attributes that have already been processed.
        Set processedForks = new HashSet();
        // map from initial state to CDClass that needs to be processed
        Map jobQueue = new HashMap();
        
        final CDClass outerClass = createClassCode(_grammar.globalImportDecls);
        jobQueue.put( _info.getInitialState(), outerClass );
        
        
        while( !jobQueue.isEmpty() ) {
            Map.Entry job = (Map.Entry)jobQueue.entrySet().iterator().next();
            
            State initial = (State)job.getKey();
            _classdef = (CDClass)job.getValue();
            
            jobQueue.remove(initial);
            
            // process this sub-automata
        
            // build transition table map< state, map<non-ref alphabet,transition> >
            TransitionTable table = new TransitionTable();
            
            State[] states = initial.getReachableStates();
            for( int i=0; i<states.length; i++ ) {
                State s = states[i];
                
                if(s.isAcceptable()) {
                    if( outerClass==_classdef )
                        table.addEverythingElse( s, REVERT_TO_PARENT );
                    else
                        table.addEverythingElse( s, JOIN );
                }
                
                Iterator jtr = s.iterateTransitions();
                while(jtr.hasNext()) {
                    Transition t = (Transition)jtr.next();
                    
                    if(t.getAlphabet().isForAction())
                        table.addEverythingElse(s, t);
                    else {
                        Set head = t.head(true);
                        if(head.contains(Head.EVERYTHING_ELSE)) {
                            // TODO: check ambiguity
                            table.addEverythingElse( s, t );
                            head.remove(Head.EVERYTHING_ELSE);
                        }
                        for (Iterator ktr = head.iterator(); ktr.hasNext();)
                            table.add(s,(Alphabet)ktr.next(),t);
                    
                    
                        if(t.getAlphabet().isFork()) {
                            // if a fork is found, put it into the queue
                            Alphabet.Fork fork = t.getAlphabet().asFork();
                            if( processedForks.add(fork) ) {
                                // generate InterleaveFilter impl.
                                outerClass.addInnerClass(createInterleaveFilterImpl(fork));
                                
                                for( int j=0; j<fork._subAutomata.length; j++ ) {
                                    State subInit = fork._subAutomata[j];
                                    
                                    // we found a new sub-automaton that needs to be processed.
                                    CDClass childClass = createInterleaveBranchClassCode(subInit);
                                    outerClass.addInnerClass(childClass);
                                    jobQueue.put(subInit,childClass);
                                }
                            }
                        }
                    }
                }
            }
            
            _classdef.addMethod(writeEventHandler(table,Alphabet.ENTER_ELEMENT));
            _classdef.addMethod(writeEventHandler(table,Alphabet.LEAVE_ELEMENT));
            _classdef.addMethod(writeEventHandler(table,Alphabet.ENTER_ATTRIBUTE));
            _classdef.addMethod(writeEventHandler(table,Alphabet.LEAVE_ATTRIBUTE));
        
            _classdef.addMethod(writeTextHandler(table));
            _classdef.addMethod(writeChildCompletedHandler());
            _classdef.addMethod(createAcceptedMethod());
        }


        if(_info._scope.getBody()!=null)
            outerClass.addLanguageSpecificString(new CDLanguageSpecificString(_info._scope.getBody()));
        outerClass.addLanguageSpecificString(new CDLanguageSpecificString(_grammar.globalBody));
        
        return outerClass;
    }
    
    
    /**
     * Generate {@link InterleaveFilter} implementation for
     * the given fork attribute.
     * 
     * @return
     *      Returns a reference to the generated class.
     */
    private CDClass createInterleaveFilterImpl( Alphabet.Fork fork ) {
        String className = fork.getClassName();
        
        CDClass classdef = new CDClass(null,null,className,
                           new CDLanguageSpecificString(" extends NGCCInterleaveFilter"));
        
        {// constructor
            // [RESULT]
            // InterleaveFilterImpl( source, parent, cookie )
            CDMethod ctr = new CDMethod(null,null,className,null);
            classdef.addMethod(ctr);
            
            // add parameters (source,parent,cookie) and call the super class initializer.
            CDVariable $parent = ctr.param( new CDType(_info.getClassName()), "parent" );
            CDVariable $cookie = ctr.param( CDType.INTEGER, "cookie" );
            
            // [RESULT]
            //  super(parent,cookie);
            ctr.body().invoke("super").arg($parent).arg($cookie);
            
            // [RESULT] new NGCCEventReceiver[]{handler1,handler2,...};
            CDObjectCreateExpression $handlers =
                new CDType("NGCCEventReceiver").array()._new();
            for( int i=0; i<fork._subAutomata.length; i++ ) {
                // TODO: ideally these sub-automata classes should be created first.
                // and references shall be used
                $handlers.arg(
                    new CDType("Branch"+fork._subAutomata[i].getIndex())._new()
                        .arg(_$this) );
            }

            // [RESULT]
            // setHandlers(handlers);
            ctr.body().invoke("setHandlers").arg($handlers);
        }        
        
        classdef.addMethod(createFindReceiverMethod(
            "findReceiverOfElement", fork._elementNameClasses ));
        classdef.addMethod(createFindReceiverMethod(
            "findReceiverOfAttribute", fork._attributeNameClasses ));
        
        {// [RESULT] protected NGCCEventReceiver findReceiverOfText();
            CDMethod method = new CDMethod(
                new CDLanguageSpecificString("protected "),
                CDType.INTEGER, "findReceiverOfText", null );
            classdef.addMethod(method);
            
            int i;
            for( i=0; i<fork._canConsumeText.length; i++ )
                if( fork._canConsumeText[i] ) {
                    method.body()._return(new CDConstant(i));
                    break;
                }
            if(i==fork._canConsumeText.length)
                method.body()._return(new CDConstant(-1));
        }
        
        return classdef;
    }
    
    private CDMethod createFindReceiverMethod( String name, NameClass[] nameClasses ) {
        CDMethod method = new CDMethod(
            new CDLanguageSpecificString("protected "),
            CDType.INTEGER, name, null );
        CDVariable $uri = method.param(CDType.STRING,GENERATED_VARIABLE_PREFIX+"uri");
        CDVariable $local = method.param(CDType.STRING,GENERATED_VARIABLE_PREFIX+"local");
        
        CDBlock body = method.body();
        
        for( int i=0; i<nameClasses.length; i++ )
            if( nameClasses[i]!=null)
                body._if(NameTestBuilder.build(nameClasses[i],$uri,$local))
                    ._then()._return(new CDConstant(i));
        
        body._return(new CDConstant(-1));
        
        return method;
    }

    
    private CDMethod createAcceptedMethod()
    {
        Iterator states = _info.iterateAcceptableStates();
        CDExpression statecheckexpression = null;
        while(states.hasNext())
        {
            State s = (State)states.next();
            CDExpression temp = CDOp.EQ( _$state, new CDConstant(s.getIndex()) );
            
            statecheckexpression = (statecheckexpression==null)? temp : CDOp.OR(temp, statecheckexpression);
        }
        
        if(statecheckexpression==null) statecheckexpression = new CDConstant(false);
        
        CDMethod m = new CDMethod(new CDLanguageSpecificString("public"), CDType.BOOLEAN, "accepted",null);
        m.body()._return(statecheckexpression);
        return m;
    }

    
    //variable holder
    private static class EventHandlerParameters {
        public CDVariable $uri;
        public CDVariable $localName;
        public CDVariable $qname;
        public CDVariable $attrs;
        public CDVariable $value;
        private CDVariable $ai;
        
        private final CDMethod handlerMethod;
        
        EventHandlerParameters( CDMethod handlerMethod ) {
            this.handlerMethod = handlerMethod;
        }
        
        public CDVariable get$ai() {
            if( $ai==null ) {
                // only declare this variable when it's necessary
                $ai = handlerMethod.body().insertDecl(CDType.INTEGER, GENERATED_VARIABLE_PREFIX+"ai");
            }
            return $ai;
        }
        
        public CDVariable[] toVariableArray(int type) {
            CDVariable[] arr = new CDVariable[type==Alphabet.VALUE_TEXT? 1 : type==Alphabet.ENTER_ELEMENT? 4 : 3];
            if(type==Alphabet.VALUE_TEXT)
                arr[0] = $value;
            else {
                arr[0] = $uri;
                arr[1] = $localName;
                arr[2] = $qname;
                if(type==Alphabet.ENTER_ELEMENT) arr[3] = $attrs;
            }
            
            //debug
            for(int i=0; i<arr.length; i++)
                if(arr[i]==null) throw new NullPointerException("#"+i+" is null!");
            
            return arr;
        }
    }
    
    
    /**
     * Writes event handlers for (enter|leave)(Attribute|Element) methods.
     */
    private CDMethod writeEventHandler( TransitionTable table, int type ) throws NoDefinitionException, IOException {

        String eventName = eventName(type);
        CDMethod method = new CDMethod(
            new CDLanguageSpecificString("public"),
            CDType.VOID, eventName,
            new CDLanguageSpecificString(" throws SAXException") );
        
        EventHandlerParameters $params = new EventHandlerParameters(method);
        $params.$uri = method.param( CDType.STRING, GENERATED_VARIABLE_PREFIX+"__uri" );
        $params.$localName = method.param( CDType.STRING, GENERATED_VARIABLE_PREFIX+"__local" );
        $params.$qname = method.param( CDType.STRING, GENERATED_VARIABLE_PREFIX+"__qname" );
        if(type==Alphabet.ENTER_ELEMENT)
            $params.$attrs = method.param(new CDType("Attributes"), GENERATED_VARIABLE_PREFIX+"attrs");
        
        CDBlock sv = method.body();
        //printSection(eventName);
            
        // QUICK HACK
        // copy them to the instance variables so that they can be 
        // accessed from action functions.
        // we should better not keep them at Runtime, because
        // this makes it impossible to emulate events.
        sv.assign(_$uri,         $params.$uri);
        sv.assign(_$localName,   $params.$localName);
        sv.assign(_$qname,       $params.$qname);
            
        if(_options.debug) {
            sv.invoke( _$runtime, "traceln")
                    .arg(new CDLanguageSpecificString("\""+_info.getClassName()+" : "+eventName + " \"+"+$params.$qname.getName()+"+\" #\" + "+_$state.getName()));
        }
        
        SwitchBlockInfo bi = new SwitchBlockInfo(type);
        
        Iterator states = _info.iterateAllStates();
        while(states.hasNext()) {
            State st = (State)states.next();
            
            // list all the transition table entry
            TransitionTable.Entry[] entries = table.list(st);
            for(int i=0; i<entries.length; i++) {
                TransitionTable.Entry e = entries[i];
                
                Transition tr = e.transition;// action to perform
                Iterator as = e.alphabets.iterator();
                CDExpression transition_condition = null;
                while(as.hasNext()) {
	                CDExpression condition = null;
	                Alphabet a = (Alphabet)as.next();      // alphabet
	                
	                if(a.isEnterAttribute() && (type==Alphabet.ENTER_ELEMENT || type==Alphabet.LEAVE_ELEMENT)) {
	                    Set t = tr.nextState().AFollow();
	                    Iterator it = t.iterator();
	                    boolean consume_attr = false;
	                    while(it.hasNext()) {
	                        Object o = it.next();
	                        consume_attr = true;
	                        if(o!=Head.EVERYTHING_ELSE) {
	                            Alphabet af = (Alphabet)o;
	                             if(af.getType()==type) {
	                                CDExpression expr = NameTestBuilder.build(af.asMarkup().getNameClass() , $params.$uri, $params.$localName);
	                                condition = condition==null? expr : CDOp.OR(condition, expr);
	                            }
	                        }
	                    }
	                    
	                    if(consume_attr) {
	                        NameClass nc = a.asMarkup().getNameClass();
	                        if(!(nc instanceof SimpleNameClass))
	                            throw new UnsupportedOperationException("attribute with a complex name class is not supported yet  name class:"+nc.toString());
	                        SimpleNameClass snc = (SimpleNameClass)nc;
	
	                        CDExpression expr = new CDLanguageSpecificString(MessageFormat.format(
	                            "("+$params.get$ai().getName()+" = "+_$runtime.getName()+".getAttributeIndex(\"{0}\",\"{1}\"))>=0",
	                            new Object[]{snc.nsUri, snc.localName}));
	                        condition = condition==null? expr : CDOp.AND(expr, condition);
	                    }                    
	                }
	                else {
	                    if(a.getType()==type) {
	                        condition = (CDExpression)a.asMarkup().getNameClass().apply(new NameTestBuilder($params.$uri, $params.$localName));
	                    }
	                }
	                
	                if(condition!=null)
	                    transition_condition = transition_condition==null? condition : CDOp.OR(condition, transition_condition);

                }
                                
                if(transition_condition==null)
                    continue;   // we are not interested in this attribute now.
                
                bi.addConditionalCode(st,
                    transition_condition,
                    buildTransitionCode(st,tr,type,$params));
            }

            // if there is EVERYTHING_ELSE transition, add an else clause.
            Transition tr = table.getEverythingElse(st);
            if(tr!=null) {
                if(tr.getAlphabet()!=null && tr.getAlphabet().isForAction()) 
                    bi.addElseCode(st, buildRepeatAlphabetCode(tr, type, $params));
                else
                    bi.addElseCode(st, buildTransitionCode(st,tr,type,$params));
            }
        }
        
        CDStatement eh = new CDMethodInvokeExpression("unexpected"+capitalize(eventName))
                .arg($params.$qname).asStatement();
        sv.add(bi.output(_$state, eh));

        
        return method;
    }

    //outputs text consumption handler. this handler branches by output method
    private CDMethod writeTextHandler(TransitionTable table) throws NoDefinitionException, IOException {

        CDMethod method = new CDMethod(
            new CDLanguageSpecificString("public"),
            CDType.VOID,
            "text",
            new CDLanguageSpecificString(" throws SAXException"));
        
        EventHandlerParameters $params = new EventHandlerParameters(method);
        
        CDBlock sv = method.body();
        $params.$value = method.param( CDType.STRING, GENERATED_VARIABLE_PREFIX+"value" );
        
        if(_options.debug) {
            sv.invoke( _$runtime, "traceln" )
                .arg(new CDLanguageSpecificString("\"text '\"+"+$params.$value.getName()+".trim()+\"' #\" + "+_$state.getName()));
        }

        SwitchBlockInfo bi = new SwitchBlockInfo(Alphabet.VALUE_TEXT);
        
        Iterator states = _info.iterateAllStates();
        while(states.hasNext()) {
            State st = (State)states.next();
            // if a transition by <data> is present, then
            // we cannot execute "everything_else" action.
            boolean dataPresent = false;
            
            // list all the transition table entry
            TransitionTable.Entry[] entries = table.list(st);
            for(int i=0; i<entries.length; i++) {
                TransitionTable.Entry e = entries[i];
                
                Transition tr = e.transition;// action to perform
                Iterator as = e.alphabets.iterator();
                while(as.hasNext()) {
	                Alphabet a = (Alphabet)as.next();      // alphabet
	                
	                CDExpression condition = null;
	                if(a.getType()==Alphabet.ENTER_ATTRIBUTE) {
	                    NameClass nc = a.asMarkup().getNameClass();
	                    if(!(nc instanceof SimpleNameClass))
	                        throw new UnsupportedOperationException("attribute with a complex name class is not supported yet  name class:"+nc.toString());
	                    SimpleNameClass snc = (SimpleNameClass)nc;
	
	                    condition = new CDLanguageSpecificString(MessageFormat.format(
	                        "("+$params.get$ai().getName()+" = "+_$runtime.getName()+".getAttributeIndex(\"{0}\",\"{1}\"))>=0",
	                        new Object[]{snc.nsUri, snc.localName}));
	                    CDBlock code = buildTransitionCode(st,tr,Alphabet.VALUE_TEXT,$params);
	                    bi.addConditionalCode(st, condition, code);
	                }
	                else if(a.isValueText()) {
	                    CDBlock code = buildTransitionCode(st,tr,Alphabet.VALUE_TEXT,$params);
	                       condition = CDOp.STREQ( $params.$value, new CDConstant(a.asValueText().getValue()));
	                    bi.addConditionalCode(st, condition, code);
	                }
	                else if(a.isDataText()) {
	                    CDBlock code = buildTransitionCode(st,tr,Alphabet.VALUE_TEXT,$params);
	                    dataPresent = true;
	                    bi.addElseCode(st, code);
	                }
                }
            }

            // if there is EVERYTHING_ELSE transition, add an else clause.
            Transition tr = table.getEverythingElse(st);
            if(tr!=null && !dataPresent) {
                if(tr.getAlphabet()!=null && tr.getAlphabet().isForAction()) 
                    bi.addElseCode(st, buildRepeatAlphabetCode(tr, Alphabet.VALUE_TEXT, $params));
                else
                    bi.addElseCode(st, buildTransitionCode(st,tr,Alphabet.VALUE_TEXT,$params));
            }
        }
        
        CDStatement errorHandler = null;
        if(_options.debug)
            errorHandler = _$runtime.invoke("traceln")
                    .arg(new CDConstant("ignored")).asStatement();
        sv.add(bi.output(_$state, errorHandler));
        
        return method;
    }
    
    /**
     * Gets a code fragment that corresponds to a strate transition.
     * This includes house-keeping, any associated actions, changing
     * the current state, etc.
     * 
     * @param params
     *      Additional parameters that need to be passed to
     *      the revertToParentFromXXX method or the
     *      spawnChildFromXXX method.
     */
    private CDBlock buildTransitionCode( State current, Transition tr, int type, EventHandlerParameters $params) throws NoDefinitionException, IOException {
        String eventName = eventName(type);
        if(tr==REVERT_TO_PARENT) {
            
            CDType retType  = _info._scope.getParam().returnType;
            String boxType = getJavaBoxType(retType);
            
            CDExpression r = _info._scope.getParam().returnValue;
            if(boxType!=null)
                r = new CDType(boxType)._new().arg(r);
            
            CDBlock sv = new CDBlock();
            current.outputActionsOnExit(sv);
            sv.invoke("revertToParentFrom"+capitalize(eventName))
                    .arg(r)
                    .arg(_$cookie)
                    .args($params.toVariableArray(type));
            return sv;
        }
        else if(tr==JOIN) {
            CDBlock sv = new CDBlock();
            current.outputActionsOnExit(sv);
            sv.invoke( _$source.castTo(interleaveFilterType), "joinBy"+capitalize(eventName))
                    .arg(CDConstant.THIS)
                    .args($params.toVariableArray(type));
            return sv;
        }
        else if(tr.getAlphabet().isEnterElement()) {
            CDBlock sv = new CDBlock();
            sv.invoke( _$runtime, "onEnterElementConsumed")
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname)
                    .arg($params.$attrs);
            sv.add(buildMoveToStateCode(tr));
            
            return sv;
        }
        else if(tr.getAlphabet().isLeaveElement()) {
            CDBlock sv = new CDBlock();
            sv.invoke( _$runtime, "onLeaveElementConsumed")
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname);
            sv.add(buildMoveToStateCode(tr));
            
            return sv;
        }
        else if(tr.getAlphabet().isEnterAttribute()) {
            CDBlock sv = new CDBlock();
            if(type==Alphabet.ENTER_ELEMENT ) {
                sv.invoke(_$runtime, "consumeAttribute").arg($params.get$ai());
                sv.invoke(_$runtime, "sendEnterElement")
                    .arg(_$cookie)
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname)
                    .arg($params.$attrs);
            }
            else if(type==Alphabet.LEAVE_ELEMENT) {
                sv.invoke(_$runtime, "consumeAttribute").arg($params.get$ai());
                sv.invoke(_$runtime, "sendLeaveElement")
                    .arg(_$cookie)
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname);
            }
            else if(type==Alphabet.DATA_TEXT || type==Alphabet.VALUE_TEXT) {
                sv.invoke(_$runtime, "consumeAttribute").arg($params.get$ai());
                sv.invoke(_$runtime, "sendText")
                    .arg(_$cookie)
                    .arg($params.$value);
            }
            else
                sv.add(buildMoveToStateCode(tr));
            return sv;
        }
        else if(tr.getAlphabet().isText()) {
            CDBlock sv = new CDBlock();
            Alphabet.Text ta = tr.getAlphabet().asText();
            String alias = ta.getAlias();
            if(alias!=null)
                sv.assign(
                    new CDLanguageSpecificString(alias),
                    ta.getDatatype().generate( _grammar, $params.$value ) );
            sv.add(buildMoveToStateCode(tr));
            
            return sv;
        }
        else if(tr.getAlphabet().isRef())
            return buildCodeToSpawnChild(type,tr,$params);
        else if(tr.getAlphabet().isFork())
            return buildCodeToForkChildren(type,tr,$params);
        else
            return buildMoveToStateCode(tr);
    }
    
    /**
     * returns a CDStatement that performs:
     *  1. execution of [act]
     *  2. state transition to [st]
     *  3. process of the same alphabet again 
     */
    private CDBlock buildRepeatAlphabetCode(Transition tr, int type, EventHandlerParameters $params) {
        CDBlock sv = tr.invokeEpilogueActions();
        
        appendStateTransition(sv, tr.nextState());
        
        if(_options.debug) {
            sv.invoke( _$runtime, "traceln" )
                .arg( new CDConstant("repeating alphabet.."));
        }
        
        switch(type) {
            case Alphabet.ENTER_ELEMENT:
                sv.invoke( _$runtime, "sendEnterElement")
                    .arg(_$cookie)
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname)
                    .arg($params.$attrs);
                break;
            case Alphabet.LEAVE_ELEMENT:
                sv.invoke( _$runtime, "sendLeaveElement")
                    .arg(_$cookie)
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname);
                break;
            case Alphabet.ENTER_ATTRIBUTE:
                sv.invoke( _$runtime, "sendEnterAttribute")
                    .arg(_$cookie)
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname);
                break;
            case Alphabet.LEAVE_ATTRIBUTE:
                sv.invoke( _$runtime, "sendLeaveAttribute")
                    .arg(_$cookie)
                    .arg($params.$uri)
                    .arg($params.$localName)
                    .arg($params.$qname);
                break;
            case Alphabet.DATA_TEXT:
            case Alphabet.VALUE_TEXT:
                sv.invoke(_$runtime, "sendText")
                    .arg(_$cookie)
                    .arg($params.$value);
                break;
        }
                   
        return sv;
    }
    
    /**
     * Generates a code fragment that creates a new child object
     * and switches to it.
     * 
     * @param type
     *      The alphabet type for which we are writing an event handler.
     * @param ref_tr
     *      The transition with REF_BLOCK type alphabet.
     * @param eventParams
     *      Additional parameters that will be passed to the
     *      spawnChildFromXXX method. Usually the parameters
     *      given to the event handler.
     * @return
     *      code fragment.
     */
    private CDBlock buildCodeToSpawnChild(int type,Transition ref_tr, EventHandlerParameters $params) {
        
        String eventName = eventName(type);
        CDBlock sv = new CDBlock();
        Alphabet.Ref alpha = ref_tr.getAlphabet().asRef();
        ScopeInfo ref_block = alpha.getTargetScope();
        
        sv.add(ref_tr.invokePrologueActions());
        
        /* Caution
         *  alpha.getParams() may return more than one argument concatinated by ','.
         *  But I give it away because separating the string into CDExpression[] is hard.
         */
        String extraarg = alpha.getParams();
        
        CDObjectCreateExpression oe = 
            new CDType(ref_block.getClassName())._new()
                .arg(_$this)
                .arg(_$source)
                .arg(_$runtime)
                .arg(new CDConstant(ref_tr.getUniqueId()));
        if(extraarg.length()>0)
            oe.arg(new CDLanguageSpecificString(extraarg.substring(1)));
            
        CDExpression $h = sv.decl(new CDType("NGCCHandler"), "h", oe );
        
        if(_options.debug) {
            CDExpression msg = new CDConstant(MessageFormat.format(
                "Change Handler to {0} (will back to:#{1})",
                new Object[]{
                    ref_block.getClassName(),
                    new Integer(ref_tr.nextState().getIndex())
                }));
                
            sv.invoke(_$runtime, "traceln").arg(msg);
        }
        
        sv.invoke("spawnChildFrom"+capitalize(eventName))
            .arg($h).args($params.toVariableArray(type));
                
        return sv;
    }

    
    /**
     * Generates a code fragment that forks a new InterleaveFilter.
     * 
     * @param type
     *      The alphabet type for which we are writing an event handler.
     * @param forkTr
     *      The transition with FORK type alphabet.
     * @param eventParams
     *      Additional parameters that will be passed to the
     *      spawnChildFromXXX method. Usually the parameters
     *      given to the event handler.
     * @return
     *      code fragment.
     */
    private CDBlock buildCodeToForkChildren(int type,Transition forkTr, EventHandlerParameters $params) {
        
        String eventName = eventName(type);
        CDBlock sv = new CDBlock();
        Alphabet.Fork alpha = forkTr.getAlphabet().asFork();
        
        // [RESULT]
        // spawnChildFromXXX( new InterleaveFilter<id>(cookie), ... );
        sv.invoke("spawnChildFrom"+capitalize(eventName))
            .arg( new CDType(alpha.getClassName())._new()
                    .arg(_$this)
                    .arg(new CDConstant(forkTr.getUniqueId()))
            ).args($params.toVariableArray(type));
                
        return sv;
    }
    
    

    
    /**
     * Creates code that changes the current state to the nextState
     * of the transition.
     */
    private CDBlock buildMoveToStateCode(Transition tr)
    {
        CDBlock sv = new CDBlock();
        
        sv.add(tr.invokePrologueActions());
        State nextstate = tr.nextState();
        
        appendStateTransition(sv, nextstate);
        sv.add(tr.invokeEpilogueActions());
        
        return sv;
    }
    
    /** Capitalizes the first character. */
    private String capitalize( String name ) {
        return Character.toUpperCase(name.charAt(0))+name.substring(1);
    }
    
    
    
    private static final String[] boxTypes = {
            "boolean","Boolean",
            "char","Character",
            "byte","Byte",
            "short","Short",
            "int","Integer",
            "long","Long",
            "float","Float",
            "double","Double"};
    
    private String getJavaBoxType( CDType type ) {
        for( int i=0; i<boxTypes.length; i+=2 )
            if( boxTypes[i].equals(type.getName()) )
                return boxTypes[i+1];
        return null;
    }
    
    private CDMethod writeChildCompletedHandler() {
        //printSection("child completed");
        CDMethod method = new CDMethod(
            new CDLanguageSpecificString("public"),
            CDType.VOID,
            "onChildCompleted",
            new CDLanguageSpecificString("throws SAXException") );

        CDVariable $result = method.param( new CDType("Object"), "$__result__" );
        CDVariable $cookie = method.param( CDType.INTEGER, "$__cookie__" );
        CDVariable $attCheck = method.param( CDType.BOOLEAN, "$__needAttCheck__" );
        
        CDBlock sv = method.body();
        
        if(_options.debug) {
            sv.invoke( _$runtime, "traceln" )
                .arg( new CDLanguageSpecificString("\"onChildCompleted(\"+cookie+\") back to "+_info.getClassName()+"\""));
        }
        
        CDSwitchStatement switchstatement = new CDSwitchStatement($cookie);
        
        Set processedTransitions = new HashSet();
        
        Iterator states = _info.iterateAllStates();
        while(states.hasNext()) {
            State st = (State)states.next();
            
            Iterator trans =st.iterateTransitions(Alphabet.REF_BLOCK|Alphabet.FORK);
            while(trans.hasNext()) {
                Transition tr = (Transition)trans.next();
                
                if(processedTransitions.add(tr)) {
                    
                    CDBlock block = new CDBlock();
                    // if there is an alias, assign to that variable
                    String alias = null;
                    
                    if( tr.getAlphabet().isRef() )
                        alias = tr.getAlphabet().asRef().getAlias();
                    
                    if(alias!=null) {
                        Alphabet.Ref a = tr.getAlphabet().asRef();
                        
                        ScopeInfo childBlock = a.getTargetScope();
                        CDType returnType = childBlock._scope.getParam().returnType;
                        
                        String boxType = getJavaBoxType(returnType);
                        CDExpression rhs;
                        if(boxType==null)
                            rhs = new CDCastExpression( returnType, $result);
                        else
                            rhs = new CDCastExpression( new CDType(boxType),
                                $result).invoke(returnType.getName()+"Value");
                            
//                        block.assign( _$this.prop(alias), rhs );
                        // when used in an interleave branch, we can't qualify
                        // aliases with "this."
                        block.assign( new CDLanguageSpecificString(alias), rhs );
                    }
                    
                    block.add(tr.invokeEpilogueActions());

                    appendStateTransition(block, tr.nextState(), $attCheck);
                        
                    switchstatement.addCase(new CDConstant(tr.getUniqueId()), block);
                }
            }
        }
        sv.add(switchstatement);
        
        // TODO: assertion failed
        //_output.println("default:");
        //_output.println("    throw new InternalError();");
        
        //_output.println("public void onChildCompleted(Object result, int cookie,boolean needAttCheck) throws SAXException {");
        return method;
    }
    
    
    
    private State appendStateTransition(CDBlock sv, State deststate ) {
        return appendStateTransition(sv,deststate,null);
    }
    
    /**
     * @param flagVarName
     *      If this parameter is non-null, the processAttribute method
     *      should be called if and only if this variable is true.
     */
    // What's the difference of this method and "buildMoveToStateCode"? - Kohsuke
    private State appendStateTransition(CDBlock sv, State deststate, CDVariable flagVar)
    {
        
        CDExpression statevariable = _$state;
        
        sv.assign(statevariable, new CDConstant(deststate.getIndex()));
        
        if(_options.debug) {
            sv.invoke( _$runtime, "traceln" )
                .arg( new CDConstant("-> #" + deststate.getIndex()));
        }

        return deststate;
    }


    private void println(StringBuffer buf, String data) {
        buf.append(data);
        buf.append(_options.newline);
    }
    
    private static String eventName(int type) {
        switch(type) {
            case Alphabet.VALUE_TEXT:
                return "text";
            case Alphabet.ENTER_ELEMENT:
                return "enterElement";
            case Alphabet.LEAVE_ELEMENT:
                return "leaveElement";
            case Alphabet.ENTER_ATTRIBUTE:
                return "enterAttribute";
            case Alphabet.LEAVE_ATTRIBUTE:
                return "leaveAttribute";
            default:
                throw new IllegalArgumentException(Integer.toString(type));
        }
    }
}
