

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * Provides a <em>builder</em> class to construct Jak source classes.
 * A <code>ClassBuilder</code> class can be used to build multiple classes
 * by either re-using state information for multiple classes or by invoking
 * {@link ClassBuilder#initialize()} between class constructions.
 *
 * @layer<codegen>
 */
    
public class ClassBuilder extends  CodeBuffer {

    /**
     * Clears the state information about the current class being
     * constructed so that a new class can be built.
     *
     * @layer<codegen>
     */
    public  CodeBuffer clear() {
        layerName = null ;
        className = null ;
        superName = null ;
	imports.clear () ;
        interfaces.clear() ;
        methods.clear() ;
        modifiers.clear() ;
        return this ;
    }

    /**
     * Adds a package/class to the imports list for this class.
     *
     * @layer<codegen>
     **/
    public ClassBuilder addImport (String name) {
	imports.add (name) ;
	return this ;
    }

    /**
     * Removes an import from the referenced imports for this class.
     *
     * @layer<codegen>
     */
    public  ClassBuilder removeImport( String name ) {
        imports.remove( name ) ;
        return this ;
    }

    /**
     * Adds an interface to the implemented interfaces for this class.
     *
     * @layer<codegen>
     */
    public  ClassBuilder addInterface( String name ) {
        interfaces.add( name ) ;
        return this ;
    }

    /**
     * Removes an interface from the implemented interfaces for this class.
     *
     * @layer<codegen>
     */
    public  ClassBuilder removeInterface( String name ) {
        interfaces.remove( name ) ;
        return this ;
    }

    /**
     * Adds a method to the class by extracting it from the contents of
     * a given {@link MethodBuilder} instance.
     *
     * @layer<codegen>
     */
    public  ClassBuilder addMethod( MethodBuilder method ) {
        addMethod( method.getSignature(), method.toString() ) ;
        return this ;
    }

    /**
     * Adds a method signature and body to be included in the generated
     * class.
     *
     * @layer<codegen>
     */
    public  ClassBuilder addMethod( String signature, String method ) {
        methods.put( signature, method ) ;
        return this ;
    }

    /**
     * Removes a method from the generated class.
     *
     * @layer<codegen>
     */
    public  ClassBuilder removeMethod( String signature ) {
        methods.remove( signature ) ;
        return this ;
    }

    /**
     * Adds a class modifier to the class to be generated.
     *
     * @layer<codegen>
     */
    public  ClassBuilder addModifier( Modifier modifier ) {

        if ( modifier == null )
            throw new IllegalArgumentException( "\"modifier\" is null" ) ;

        if ( modifier.equals( Modifier.REFINES ) && superName != null )
            throw new IllegalArgumentException( "invalid w/ super class" ) ;

        modifiers.add( modifier ) ;
        return this ;
    }

    /**
     * Removes a class modifier from the class.
     *
     * @layer<codegen>
     */
    public  ClassBuilder removeModifier( Modifier modifier ) {
        modifiers.remove( modifier ) ;
        return this ;
    }

    /**
     * Sets the layer name, if <code>name</code> is non-null, or clears
     * the layer name if <code>name==null</code>.
     *
     * @layer<codegen>
     */
    public  ClassBuilder setLayerName( String name ) {
        layerName = name ;
        return this ;
    }

    /**
     * Sets the class name, if <code>name</code> is non-null, or clears the
     * class name if <code>name==null</code>.
     *
     * @layer<codegen>
     */
    public  ClassBuilder setClassName( String name ) {
        className = name ;
        return this ;
    }

    /**
     * Sets the super class name, if <code>name</code> is non-null, or
     * clears the super class if <code>name==null</code>.  Note: assigning
     * a super class is incompatible with the {@link Modifier#REFINES}
     * modifier.
     *
     * @layer<codegen>
     */
    public  ClassBuilder setSuperName( String name ) {

        if ( modifiers.contains( Modifier.REFINES ) && name != null )
            throw new IllegalArgumentException( "invalid w/ REFINES" ) ;

        superName = name ;
        return this ;
    }

    /**
     * Returns a {@link String} containing the generated code for the
     * currently defined class, complete with line separators.
     *
     * @layer<codegen>
     */
    public String toString() {

        CodeBuffer buffer = new CodeBuffer() ;

        if ( layerName != null && layerName.length() > 0 ) {
            buffer.append( Keyword.LAYER ) ;
            buffer.append( layerName ) ;
            buffer.append( ';' ) ;
            buffer.endLine().endLine() ;
        }

	if (imports.size () > 0) {
	    for (Iterator p = imports.iterator () ; p.hasNext () ; ) {
		buffer.append (Keyword.IMPORT) ;
		buffer.append ((String) p.next ()) ;
		buffer.append( ';' ) ;
		buffer.endLine() ;
	    }
            buffer.endLine() ;
	}

        for ( Iterator p = modifiers.iterator() ; p.hasNext() ; )
            buffer.append( ( Modifier ) p.next() ) ;

        buffer.append( Keyword.CLASS ) ;
        buffer.append( className ) ;

        if ( superName != null ) {
            Object[] phrase = new Object[] {Keyword.EXTENDS, superName} ;
            buffer.append( Arrays.asList( phrase ) ) ;
        }

        if ( interfaces.size() > 0 ) {

            List phrase = new ArrayList( interfaces ) ;
            Collections.sort( phrase ) ;

            ListIterator p = phrase.listIterator( phrase.size() - 1 ) ;
            while ( p.hasPrevious() )
                p.set( ( ( String ) p.previous() ) + ',' ) ;

            phrase.add( 0, Keyword.IMPLEMENTS ) ;
            buffer.append( phrase ) ;
        }

        buffer.append( "{" ).endLine() ;
        buffer.indent() ;

        // Insert any lines built by direct calls to "append", etc.:
        //
        String header = super.toString().trim() ;
        if ( header != null && header.length() > 0 ) {
            buffer.endLine() ;
            buffer.appendLines( header ) ;
            buffer.endLine() ;
        }

        for ( Iterator p = methods.values().iterator() ; p.hasNext() ; ) {
            buffer.endLine() ;
            buffer.appendLines( p.next().toString() ) ;
            buffer.endLine() ;
        }

        buffer.outdent() ;
        buffer.endLine().append( "}" ) ;

        return buffer.toString() ;
    }

    //-------------------------------------------------------------------//
    // Private state information:
    //-------------------------------------------------------------------//

    private String layerName = null ;
    private String className = null ;
    private String superName = null ;

    final private Set imports = new TreeSet() ;
    final private Set interfaces = new TreeSet() ;
    final private Map methods = new TreeMap() ;
    final private Set modifiers = new TreeSet() ;

}
