/*
 * @(#)$Id: RuleSerializer.java 1256 2001-10-23 18:27:03Z 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.ll;

import com.sun.msv.datatype.xsd.XSDatatype;
import com.sun.msv.grammar.*;
import com.sun.msv.grammar.util.ExpressionWalker;
import com.sun.tahiti.compiler.XMLWriter;
import com.sun.tahiti.compiler.Symbolizer;
import com.sun.tahiti.grammar.*;
import com.sun.tahiti.reader.TypeUtil;
import com.sun.tahiti.reader.NameUtil;
import org.relaxng.datatype.Datatype;
import org.xml.sax.DocumentHandler;
import org.xml.sax.SAXException;
import java.io.PrintStream;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.text.MessageFormat;

/**
 * serializes a LL grammar into the XML representation.
 * 
 * @author
 *	<a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
 */
public class RuleSerializer implements Symbolizer {
	
	private RuleSerializer() {}
	
	/**
	 * 
	 * @param grammar
	 *		Grammar object to be generated as a Java source code.
	 * @param rules
	 *		production rules of the grammar. Actual type must be
	 *		non-terminal -> Rule[].
	 * @param grammarClassName
	 *		the fully-qualified name of the class that is going to be generated.
	 * @param outHandler
	 *		Generated source code will be sent to this handler.
	 * 
	 * @return
	 *		return a symbolizer that is necessary to serialize class definitions.
	 */
	public static Symbolizer serialize( AnnotatedGrammar grammar, Rules rules, DocumentHandler outHandler ) throws SAXException {
		RuleSerializer gen = new RuleSerializer();
		gen._serialize( grammar, rules, outHandler );
		return gen;
	}
	
	
	/**
	 * this map serves as a dictionary to lookup the name of the given symbol.
	 */
	final Map allNames = new java.util.HashMap();
	public String getId( Object symbol ) {
		if(symbol==null)	return "null";
		String s = (String)allNames.get(symbol);
		
		if(s==null) {
			System.out.println(symbol);
			assert(false);
		}
		return s;
	}
	
	private void _serialize( AnnotatedGrammar grammar, Rules rules, DocumentHandler outHandler ) throws SAXException {
		
		// add pre-defined special symbols.
		allNames.put( Expression.epsilon, "epsilon" );
		
		try {
			final XMLWriter out = new XMLWriter(outHandler);
			outHandler.setDocumentLocator(new org.xml.sax.helpers.LocatorImpl());
			outHandler.startDocument();
			outHandler.processingInstruction("xml-stylesheet","type='text/xsl' href='../grammarDebug.xslt'");
			out.start("grammar");
			
			{
				int idx = grammar.grammarName.lastIndexOf('.');
				if(idx<0) {
					out.element("name", grammar.grammarName);
				} else {
					out.element("package", grammar.grammarName.substring(0,idx));
					out.element("name", grammar.grammarName.substring(idx+1));
				}
			}
			
		// collect various primitives (a map to its name)
		//====================================================================
			final Map elements = new java.util.HashMap();	// ElementExps
			final Map attributes = new java.util.HashMap();	// AttributeExps
			final Map datatypes = new java.util.HashMap();	// Datatypes
			final Map classes = new java.util.HashMap();	// ClassItems
			final Map fields = new java.util.HashMap();		// FieldItems
			final Map primitives = new java.util.HashMap();	// PrimitiveItems
			final Map ignores = new java.util.HashMap();	// IgnoreItems
			
			grammar.getTopLevel().visit( new ExpressionWalker(){
				public void onElement( ElementExp exp ) {
					if(!elements.containsKey(exp)) {
						elements.put(exp,computeName(exp.getNameClass(),elements));
						super.onElement(exp);
					}
				}
				public void onAttribute( AttributeExp exp ) {
					if(!attributes.containsKey(exp)) {
						attributes.put(exp,computeName(exp.nameClass,attributes));
						super.onAttribute(exp);
					}
				}
				public void onData( DataExp exp ) {
					if(!datatypes.containsKey(exp)) {
						datatypes.put(exp,computeName(exp.dt,datatypes));
						super.onData(exp);
					}
				}
				public void onValue( ValueExp exp ) {
					if(!datatypes.containsKey(exp)) {
						datatypes.put(exp,computeName(exp.dt,datatypes));
						super.onValue(exp);
					}
				}
				public void onOther( OtherExp exp ) {
					if(exp instanceof ClassItem) {
						if(!classes.containsKey(exp)) {
							classes.put(exp,computeName((ClassItem)exp,classes));
							super.onOther(exp);
						}
						return;
					}
					if(exp instanceof PrimitiveItem) {
						if(!primitives.containsKey(exp)) {
							primitives.put(exp,computeName((PrimitiveItem)exp,primitives));
							super.onOther(exp);
						}
						return;
					}
					if(exp instanceof FieldItem) {
						if(!fields.containsKey(exp)) {
							fields.put(exp,computeName((FieldItem)exp,fields));
							super.onOther(exp);
						}
						return;
					}
					if(exp instanceof IgnoreItem) {
						if(!ignores.containsKey(exp)) {
							ignores.put(exp,computeName((IgnoreItem)exp,ignores));
							super.onOther(exp);
						}
						return;
					}
					
					super.onOther(exp);
				}
			});
			
		// assign names to intermediate non-terminals.
		//====================================================================
			
			
			copyAll( elements, "E", allNames );
			copyAll( attributes, "A", allNames );
			copyAll( datatypes, "D", allNames );
			copyAll( classes, "C", allNames );
			copyAll( fields, "N", allNames );
			copyAll( primitives, "P", allNames );
			copyAll( ignores, "Ignore", allNames );
			
			
			final ElementExp[] elms = (ElementExp[])elements.keySet().toArray(new ElementExp[0]);
			final AttributeExp[] atts = (AttributeExp[])attributes.keySet().toArray(new AttributeExp[0]);
			final DataOrValueExp[] dts = (DataOrValueExp[])datatypes.keySet().toArray(new DataOrValueExp[0]);
			final ClassItem[] cis = (ClassItem[])classes.keySet().toArray(new ClassItem[0]);
			final FieldItem[] fis = (FieldItem[])fields.keySet().toArray(new FieldItem[0]);
			final PrimitiveItem[] pis = (PrimitiveItem[])primitives.keySet().toArray(new PrimitiveItem[0]);
			final IgnoreItem[] iis = (IgnoreItem[])ignores.keySet().toArray(new IgnoreItem[0]);
			
			for( int i=0; i<dts.length; i++ ) {
				// TODO: serious implementation
				out.start( "dataSymbol", new String[]{
					"id",(String)allNames.get(dts[i])});
				
				out.element("library", dts[i].getName().namespaceURI );
				out.element("name", dts[i].getName().localName );
				
				out.end("dataSymbol");
				
//					"type", ((XSDatatype)dts[i].dt).getConcreteType().getName()
//					} );
			}
			
			for( int i=0; i<cis.length; i++ ) {
				out.element( "classSymbol", new String[]{
					"id",(String)allNames.get(cis[i]),
					"type",(String)cis[i].getTypeName()
				} );
			}
			
			for( int i=0; i<pis.length; i++ )
				out.element( "primitiveSymbol", new String[]{"id",(String)allNames.get(pis[i])} );
			
			for( int i=0; i<fis.length; i++ )
				out.element( "namedSymbol", new String[]{"id",(String)allNames.get(fis[i])} );
			
			for( int i=0; i<iis.length; i++ )
				out.element( "ignoreSymbol", new String[]{"id",(String)allNames.get(iis[i])} );
			
			{// generate intermediate symbols.
				int cnt=1;
				for( Iterator itr = rules.iterateKeys(); itr.hasNext(); ) {
					Expression symbol = (Expression)itr.next();
					if(!allNames.containsKey(symbol)) {
						out.element( "intermediateSymbol", new String[]{"id","T"+cnt});
						allNames.put( symbol, "T"+cnt );
						cnt++;
					}
				}
			}
			
			{// write all rules
				int rcounter = 0;
				Iterator itr = rules.iterateKeys();
				while(itr.hasNext()) {
					Expression nonTerminal = (Expression)itr.next();
					Rule[] rs = rules.getAll(nonTerminal);
					
					for( int j=0; j<rs.length; j++ ) {
						if(allNames.get(rs[j])==null) {
							// name this rule.
							allNames.put( rs[j], Integer.toString(rcounter++) );
							// write this rule
							rs[j].write(out,this);
						}
					}
				}
			}
			
			{// write non-term -> rule relationship
				out.start("rulesList");
				Iterator itr = rules.iterateKeys();
				while(itr.hasNext()) {
					Expression symbol = (Expression)itr.next();
					out.start("nonTerminal",
						new String[]{"id",getId(symbol)});
					Rule[] rs = rules.getAll(symbol);
					for( int i=0; i<rs.length; i++ )
						out.element("rule",new String[]{"no",getId(rs[i])});
					
					out.end("nonTerminal");
				}
				out.end("rulesList");
			}
	
		// generate a source code that constructs the grammar.
		//==============================================================
			
			ExpressionSerializer eser = new ExpressionSerializer(this,out);
			
			/*
			visit all elements and attributes to compute the dependency between expressions.
			*/
			for( int i=0; i<atts.length; i++ ) {
				atts[i].exp.visit(eser.sequencer);
				eser.assignId( atts[i] );
				// attributes are serialized just like other particles.
			}
			for( int i=0; i<elms.length; i++ )
				elms[i].contentModel.visit(eser.sequencer);
			
			// ... and don't forget to visit top level expression.
			grammar.getTopLevel().visit(eser.sequencer);
			
			// then obtain the serialization order by creating a map from id to expr.
			java.util.TreeMap id2expr = new java.util.TreeMap();
			for( Iterator itr=eser.sharedExps.iterator(); itr.hasNext(); ) {
				Expression exp = (Expression)itr.next();
				id2expr.put( eser.expr2id.get(exp), exp );
			}
			
			// then serialize shared expressions
			for( Iterator itr=id2expr.keySet().iterator(); itr.hasNext(); ) {
				Integer n = (Integer)itr.next();
				
				Expression exp = (Expression)id2expr.get(n);
				
				if( exp instanceof AttributeExp ) {
					AttributeExp aexp = (AttributeExp)exp;
					out.start( "attributeSymbol", new String[]{"id",(String)allNames.get(aexp)} );
				
					ExpressionSerializer.serializeNameClass(aexp.getNameClass(),out);
					out.start("content");
					aexp.visit(eser.serializer);
					out.end("content");
					
					LLTableCalculator.calc( aexp, rules, grammar.getPool(),this ).write(out,this);
					
					out.end("attributeSymbol");
				} else {
					// other normal particles.
					out.start("particle",new String[]{"id","o"+n});
					exp.visit(eser.serializer);
					out.end("particle");
				}
			}
			
			// elements are serialized at last.
			for( int i=0; i<elms.length; i++ ) {
				out.start("elementSymbol", new String[]{"id",(String)allNames.get(elms[i])} );
			
				ExpressionSerializer.serializeNameClass(elms[i].getNameClass(),out);
				out.start("content");
				elms[i].visit(eser.serializer);
				out.end("content");
				
				LLTableCalculator.calc( elms[i], rules, grammar.getPool(),this ).write(out,this);
				
				out.end("elementSymbol");
			}
			
			if( elements.containsKey(grammar.getTopLevel()) ) {
				// if the top-level expression is element symbol,
				// then we don't need the root grammar.
				out.start("topLevel");
				out.start("content");
				eser.serialize(grammar.getTopLevel());
				out.end("content");
				out.end("topLevel");
			} else {
				// serialize top-level expression
				out.start("topLevel",new String[]{"id",getId(grammar.getTopLevel())});
				out.start("content");
				grammar.getTopLevel().visit(eser.serializer);
				out.end("content");
					LLTableCalculator.calc( grammar.getTopLevel(), rules, grammar.getPool(),this ).write(out,this);
				out.end("topLevel");
			}
			
			
			{// compute the base type of possible top-level classes
				final Set rootClasses = new java.util.HashSet();
				grammar.getTopLevel().visit( new ExpressionWalker(){
					private Set visitedExps = new java.util.HashSet();
					public void onOther( OtherExp exp ) {
						if( exp instanceof TypeItem )
							rootClasses.add(exp);
						// we don't need to parse inside a JavaItem.
					}
					public void onElement( ElementExp exp ) {
						if( visitedExps.add(exp) )
							exp.contentModel.visit(this);
					}
				});
				
				Type rootType = TypeUtil.getCommonBaseType(rootClasses);
				out.element("rootType",
					new String[]{"name", rootType.getTypeName()});
			}
			
			out.end("grammar");
			outHandler.endDocument();
		} catch( XMLWriter.SAXWrapper w ) {
			throw w.e;
		};
	}
	
	/**
	 * copy the source map to the destination map, while adding 
	 * the specified prefix to values.
	 */
	private static void copyAll( Map src, String prefix, Map dst ) {
		for( Iterator itr=src.keySet().iterator(); itr.hasNext(); ) {
			Object symbol = itr.next();
			dst.put( symbol, prefix+src.get(symbol) );
		}
	}
	
	/**
	 * computes a unique name that is used as a field name for
	 * the ElementExp/AttributeExp.
	 * 
	 * @param nc
	 *		name class of that ElementExp/AttributeExp.
	 * @param m
	 *		values of this map are the names of other ElementExp/AttributeExps.
	 *		This method has to return the unique name.
	 */
	private static String computeName( NameClass nc, Map m ) {
		if( nc instanceof SimpleNameClass ) {
			SimpleNameClass snc = (SimpleNameClass)nc;
			String name = NameUtil.toIdentifier(snc.localName);
			if(!m.containsValue(name))
				return name;
			
			return getNumberedName( snc.localName, 2, m );
		} else {
			return getNumberedName( "", 1, m );
		}
	}
	
	/**
	 * generate an unique name by concatenating a number to its tail.
	 */
	private static String getNumberedName( String prefix, int count, Map m ) {
		
		if( NameUtil.toIdentifier(prefix).length()==0 )
			prefix = "_" + prefix;
		
		String name;
		do {
			name = NameUtil.toIdentifier( prefix+Integer.toString(count++) );
		} while( m.containsValue(name) );

		return name;
	}

	/**
	 * computes an unique name that is used as a field name for
	 * the Datatype.
	 * 
	 * @param dt
	 *		Datatype object.
	 * @param m
	 *		values of this map are the names of other ElementExp/AttributeExps.
	 *		This method has to return the unique name.
	 */
	private static String computeName( Datatype dt, Map m ) {
		if( dt instanceof XSDatatype ) {
			XSDatatype xsdt = (XSDatatype)dt;
			if( xsdt.getName()!=null ) {
				String name = NameUtil.toIdentifier( xsdt.getName() );
				if(!m.containsValue(name) )	return name;
				else
					getNumberedName( name, 2, m );
			} else 
				return getNumberedName( xsdt.displayName(), 2, m );
		}
		
		return getNumberedName( "", 0, m );
	}
	
	private static String computeName( ClassItem cls, Map m ) {
		String name = cls.getBareName();
		
		if(!m.containsValue(name))		return name;
		return getNumberedName( name, 2, m );
	}

	private static String computeName( PrimitiveItem pitm, Map m ) {
		String name = pitm.type.getBareName();
		
		if(!m.containsValue(name))		return name;
		return getNumberedName( name, 2, m );
	}

	private static String computeName( FieldItem field, Map m ) {
		if(!m.containsValue(field.name))		return field.name;
		return getNumberedName( field.name, 2, m );
	}
	
	private static String computeName( IgnoreItem iim, Map m ) {
		return getNumberedName( "", 1, m );
	}

	
	/**
	 * gets the source code representation of the specified string.
	 */
	private static String javaStringEscape( String s ) {
//		throw new Error();
		return "\""+s+"\"";
	}
	
	
	private static void assert( boolean b ) {
		if(!b)	throw new Error();
	}
}
