/*
 * @(#)$Id: ClassSerializer.java 1087 2001-08-18 01:37:50Z Bear $
 *
 * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * This software is the proprietary information of Sun Microsystems, Inc.  
 * Use is subject to license terms.
 * 
 */
package com.sun.tahiti.compiler.java;

import com.sun.msv.grammar.*;
import com.sun.tahiti.compiler.Symbolizer;
import com.sun.tahiti.compiler.Controller;
import com.sun.tahiti.compiler.XMLWriter;
import com.sun.tahiti.compiler.sm.MarshallerGenerator;
import com.sun.tahiti.grammar.*;
import com.sun.tahiti.grammar.util.Multiplicity;
import com.sun.tahiti.reader.NameUtil;
import com.sun.tahiti.util.text.Formatter;
import com.sun.tahiti.util.xml.XSLTUtil;
import com.sun.tahiti.util.xml.DOMVisitor;
import com.sun.tahiti.util.xml.DOMBuilder;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.text.MessageFormat;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

/**
 * produces Java source codes of the object model.
 * 
 * @author
 *	<a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
 */
public class ClassSerializer
{
	public ClassSerializer( AnnotatedGrammar grammar, Symbolizer symbolizer, Controller controller ) {
		this.grammar = grammar;
		this.symbolizer = symbolizer;
		this.controller = controller;
		this.grammarClassName = grammar.grammarName;
		
		int idx = grammarClassName.lastIndexOf('.');
		if(idx<0)	grammarShortClassName = grammarClassName;
		else		grammarShortClassName = grammarClassName.substring(idx+1);
	}
	
	private final AnnotatedGrammar grammar;
	private final Symbolizer symbolizer;
	private final Controller controller;
	
	/** fully-qualified name of the Gramamr class. */
	protected final String grammarClassName;
	/**
	 * bare class name of the Grammar class.
	 * Since the Grammar class is imported in generated classes,
	 * this bare name can be used to refer to the Grammar class.
	 */
	protected final String grammarShortClassName;
		
	public void generate() throws IOException {
		// collect all ClassItems.
		
		ClassItem[] types = grammar.getClasses();
		for( int i=0; i<types.length; i++ ) {
			out = new PrintWriter(controller.getOutput(types[i]));
			writeClass( types[i] );
		}
		
		InterfaceItem[] itfs = grammar.getInterfaces();
		for( int i=0; i<itfs.length; i++ ) {
			out = new PrintWriter(controller.getOutput(itfs[i]));
			writeClass( itfs[i] );
		}
	}
	
	
	private static String format( String fmt, Object[] args ) {
		return MessageFormat.format(fmt,args);
	}
	private static String format( String fmt, Object arg1 ) {
		return MessageFormat.format(fmt,new Object[]{arg1});
	}
	private static String format( String fmt, Object arg1, Object arg2 ) {
		return MessageFormat.format(fmt,new Object[]{arg1,arg2});
	}
	private static String format( String fmt, Object arg1, Object arg2, Object arg3 ) {
		return MessageFormat.format(fmt,new Object[]{arg1,arg2,arg3});
	}
	private static String format( String fmt, Object arg1, Object arg2, Object arg3, Object arg4 ) {
		return MessageFormat.format(fmt,new Object[]{arg1,arg2,arg3,arg4});
	}
	
	/**
	 * computes the type name that will can be placed into the source code.
	 * 
	 * If the given type obejct is in the current package, then only the local type
	 * name is returned. Otherwise fully qualified name is returned.
	 */
	protected String toPrintName( Type type ) {
		if( packageName==type.getPackageName()
		|| 	(packageName!=null && packageName.equals( type.getPackageName() ))
		||  "java.lang".equals( type.getPackageName() ) )
			return type.getBareName();
		else
			return type.getTypeName();
	}
	
	/**
	 * the name of the package into which this class is placed.
	 * This field is set to null to indicate the root package.
	 */
	private String packageName;
	
	/**
	 * output object which receives the produced Java source code.
	 * Set in the generate method.
	 */
	private PrintWriter out;
	
	/**
	 * writes the body of a ClassItem.
	 */
	private void writeClass( TypeItem type ) {

		// one 
		ClassItem citm = null;
		InterfaceItem iitm = null;
		
		if( type instanceof ClassItem )	citm = (ClassItem)type;
		else							iitm = (InterfaceItem)type;
	
		
		packageName = type.getPackageName();
		if( packageName!=null )
			out.println(format("package {0};\n",packageName));
		
		out.println("import com.sun.tahiti.runtime.ll.NamedSymbol;");
		out.println("import com.sun.tahiti.runtime.sm.Marshaller;");
		// if the grammar class is placed under some package
		if(!grammarClassName.equals(grammarShortClassName))
			out.println(format("import {0};",grammarClassName));
		out.println();
		
		
		produceHeaderComment(type);

		
	// class name and the base class definitions
	//-------------------------------------------
		
		out.print(format("public {0} {1}",
			citm!=null?"class":"interface",	type.getBareName() ));
		
		if( citm!=null && citm.getSuperType()!=null )
			out.print(format(" extends {0}",
				toPrintName( type.getSuperType() ) ));
		
		Type[] itfs = type.getInterfaces();
		if(itfs.length!=0) {
			out.print(format(" {0} {1}",
				citm!=null?"implements":"extends",
				toPrintName(itfs[0])));
			
			for( int i=1;i<itfs.length;i++ )
				out.print(format(", {0}",toPrintName(itfs[i])));
		}
		
		out.println(" {");
		out.println();

		
		
	// prepare field serializers
	//----------------------------------------
		FieldUse[] fields;
		{
			Vector f = new Vector();
			TypeItem t = type;
			while(true) {
				f.addAll( t.fields.values() );
				if( t instanceof ClassItem ) {
					ClassItem c = (ClassItem)t;
					if(c.superClass!=null) {
						t = c.superClass.definition;
						continue;
					}
				}
				break;
			}
			fields = (FieldUse[])f.toArray(new FieldUse[0]);
		}
		FieldSerializer[] fieldSerializers = new FieldSerializer[fields.length];

		// a map from field name to FieldSerializer.
		final Map fsMap = new java.util.HashMap();
		
		for( int i=0; i<fields.length; i++ ) {
			fieldSerializers[i] = FieldSerializer.get(this,fields[i]);
			fsMap.put( fields[i].name, fieldSerializers[i] );
		}
			
		
	// generate fields
	//----------------------------------------
		for( int i=0; i<fields.length; i++ ) {
			out.println(
				"\n"+
				"//\n"+
				"// "+fields[i].name+"\n"+
				"//\n");
			fieldSerializers[i].writeFieldDef(out);
		}
		
	// generate the setField method
	//------------------------------------------
		if( citm!=null ) {
			out.println("\n\n");
			out.println(
				"\t/**\n"+
				"\t * unmarshalling handler.\n"+
				"\t * This method is called to unmarshall objects from XML.\n"+
				"\t */");
			out.println("\tpublic void setField( NamedSymbol name, Object item ) throws Exception {");
			for( int i=0; i<fields.length; i++ ) {
				
				FieldUse fu = fields[i];
				FieldSerializer fs = fieldSerializers[i];
				
				out.print("\t\tif( ");
				FieldItem[] fi = fu.getItems();
				for( int j=0; j<fi.length; j++ ) {
					if(j!=0)
						out.print(" || ");
					out.print(format("name=={0}.{1}",
						grammarShortClassName,
						symbolizer.getId(fi[j])));
				}
				out.println(" ) {");
				out.println("\t\t\t"+fs.setField("item"));
				out.println("\t\t\treturn;");
				out.println("\t\t}");
			}
			if( type.getSuperType()!=null )
				out.println("\t\tsuper.setField(name,item);");
			else
				out.println("\t\tthrow new Error();//assertion failed.this is not possible");
			out.println("\t}");
		}

	// generate the marshaller
	//------------------------------------------
		if( citm!=null ) {
			try {
				// TODO: ideally, a marshaller should be produced as a separate class,
				// to minimize dependency between the marshaller and the unmarshaller.
				// however, since we have only one marshaller at this moment,
				// we will create it here.
				
/*				TransformerHandler xsltEngine = XSLTUtil.getTransformer(
					JavaGenerator.class.getResourceAsStream("marshaller2java.xsl"));
				xsltEngine.setResult( new StreamResult(out) );
				XMLWriter writer = XMLWriter.fromContentHandler(xsltEngine);
		
				// call MarshallerGenerator to have it produce marshaller.
				// generated marshaller will be then transformed into Java source code
				// by using XSLT.
				xsltEngine.startDocument();
				MarshallerGenerator.write( citm, writer, controller );
				xsltEngine.endDocument();
*/
				// get DOM representation of the marshaller
				DOMBuilder builder = new DOMBuilder();
				XMLWriter writer = XMLWriter.fromContentHandler(builder);
				writer.handler.startDocument();
				MarshallerGenerator.write( symbolizer, citm, writer, controller );
				writer.handler.endDocument();
				
				// produce a source code fragment from DOM.
				MarshallerSerializer.write( fsMap, out, builder.getDocument() );
				
			} catch( MarshallerGenerator.Abort a ) {
				// generation of the marshaller is aborted.
				out.println(
					"\t// Tahiti fails to produce a marshaller for this class.\n"+
					"\t// You have to implement one by yourself if you need it.\n"+
					"\tpublic void marshall( Marshaller out ) {\n"+
					"\t\tthrow new UnsupportedOperationException();\n"+
					"\t}\n");
			} catch( javax.xml.parsers.ParserConfigurationException e ) {
				controller.error( null, e.getMessage(), e );
				// TODO: what shall I do if an error happens.
			} catch( SAXException e ) {
				controller.error( null, e.getMessage(), e );
				// TODO: what shall I do if an error happens.
			}
		} else {
			// in case of an interface, produce a signature.
			out.println("\tvoid marshall( Marshaller out );\n");
		}
		
		
		
		out.println("}");
		out.flush();
		out.close();
	}
	
	/**
	 * produces a javadoc comment for this type.
	 */
	protected void produceHeaderComment( TypeItem type ) {
		out.println("/**");
		out.println(" * "+type.getBareName()+".");
		
		// lists derived classes.
		boolean hasDerivedType = false;
		out.println(" * <h2>list of derived types</h2>");
		hasDerivedType |= listDerivedTypes(type,grammar.iterateClasses());
		hasDerivedType |= listDerivedTypes(type,grammar.iterateInterfaces());
		if(!hasDerivedType)
			out.println(" *  no derived type");
		out.println(" */");
	}
	
	protected boolean listDerivedTypes( TypeItem current, Iterator itr ) {
		boolean hasDerivedType = false;
		while( itr.hasNext() ) {
			TypeItem type = (TypeItem)itr.next();
			
			boolean isDerived = false;
			
			if(type.getSuperType()==current)
				isDerived = true;
			
			Type[] itfs = type.getInterfaces();
			for( int i=0; i<itfs.length; i++ )
				if( itfs[i]==current )
					isDerived = true;
			
			if(isDerived)
				out.println(" *   {@link "+type.getTypeName()+"}");
			hasDerivedType |= isDerived;
		}
		return hasDerivedType;
	}
}
