/*
 * @(#)$Id: FieldItemAnnotation.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.reader.annotator;

import com.sun.msv.datatype.xsd.StringType;
import com.sun.msv.grammar.*;
import com.sun.msv.grammar.util.ExpressionWalker;
import com.sun.msv.grammar.util.ExpressionPrinter;
import com.sun.msv.grammar.trex.ElementPattern;
import com.sun.msv.grammar.xmlschema.SimpleTypeExp;
import com.sun.tahiti.grammar.*;
import com.sun.tahiti.grammar.util.Multiplicity;
import com.sun.tahiti.grammar.util.MultiplicityCounter;
import com.sun.tahiti.reader.NameUtil;
import java.util.Set;
import java.util.Map;
import java.util.Stack;
import java.util.Iterator;

/**
 * adds missing FieldItems to the grammar.
 * 
 * This algorithm expands C-C, C-I, and C-P relationships to
 * C-F/F-C, C-F/F-I, and C-F/F-P relationships.
 * 
 * @author
 *	<a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
 */
class FieldItemAnnotation
{
	private static java.io.PrintStream debug = System.out;
	
	public static void annotate( AnnotatedGrammar g ) {
		
		FieldItemAnnotation ann = new FieldItemAnnotation();
		
		// process all class items.
		ClassItem[] classes = g.getClasses();
		for( int i=0; i<classes.length; i++ ) {
			if(debug!=null)
				debug.println(" adding field item for "+ classes[i].getTypeName());
			classes[i].exp = classes[i].exp.visit(ann.new Annotator(g,classes[i]));
		}
	}
	
	private FieldItemAnnotation() {}
	
	/**
	 * this map will keep the annotated ReferenceExps. ReferenceExp is the key
	 * and annotated result is the value. This map is very important to minimize
	 * the number of generated classes/interfaces.
	 */
	private final Map annotatedRefs = new java.util.HashMap();
	
	/**
	 * Annotate the body of one ClassItem.
	 */
	private class Annotator extends ExpressionCloner {
		
		private Annotator( AnnotatedGrammar g, ClassItem owner ) {
			super(g.getPool());
			this.owner = owner;
			this.grammar = g;
			names.push("content");	// if no other name is available, "content" is used.
		}
		
		/**
		 * the top of this stack is the name of the nearest enclosing named item.
		 * 
		 * This information is used to determine the name of newly added FieldItem.
		 */
		private final Stack names = new Stack();
		
		/** the grammar object to which we are adding annotation. */
		private final AnnotatedGrammar grammar;
		
		/** the current ClassItem object. Its body is what we are dealing with now. */
		private final ClassItem owner;
		
		public Expression onRef( ReferenceExp exp ) {
			
			// If this ReferenceExp has already visited and annotated. reuse it.
			// This will prevent annotating the same ChoiceExp, etc again and again.
			Expression r = (Expression)annotatedRefs.get(exp);
			if(r!=null)			return r;

			// store the name information
			boolean pushed = false;
			if(exp.name!=null ) {
				if(!(exp instanceof SimpleTypeExp)) {
					// typically we don't want to use the type name 
					// as the field name.
					names.push(exp.name);
					pushed = true;
				}
			}
			r = exp.exp.visit(this);
			
			if( pushed )
				// store the annotated result.
				// if we haven't push the name,
				// the top of the names stack is the name from ancestors.
				// in that case, visiting this node next time will
				// produce a different result.
				// So we cannot memorize the result.
				annotatedRefs.put(exp,r);
			
//	debug: assertion check
// since it is now properly annotated,
// every ClassItem, PrimitiveItem or InterfaceItem must be wrapped by FieldItem.
			r.visit( new ExpressionWalker(){
				public void onOther( OtherExp exp ) {
					if( exp instanceof FieldItem )
						return;
					if( exp instanceof IgnoreItem )
						return;
					if( exp instanceof JavaItem ) {
						System.err.println(exp);
						throw new Error();
					}
				}
			});
			
			
			if(pushed)	names.pop();
			return r;
		}
		
		public Expression onOther( OtherExp exp ) {
			// expands C-C,C-P,C-I relationship.
			if( exp instanceof PrimitiveItem
			||  exp instanceof InterfaceItem
			||  exp instanceof ClassItem )
				return  new FieldItem( decideName(exp.exp), exp );
			
			if( exp instanceof IgnoreItem
			||  exp instanceof SuperClassItem
			||	exp instanceof FieldItem )
				return exp;
			
			// unknown OtherExps.
			assert(!(exp instanceof JavaItem));
			return exp.exp.visit(this);
		}

		public Expression onAttribute( AttributeExp exp ) {
			Expression body = visitXMLItemContent(exp);
			if( body==exp.exp )	return exp;
			else	return pool.createAttribute( exp.nameClass, body );
		}
	
		public Expression onElement( ElementExp exp ) {
			/*
			although we will lose any additional information
			added to this ElementExp,
			we have to create a copy of ElementExp. Otherwise
			we cannot correctly process things like:
			
			<define name="X">
				<ref name="Z" t:role="class"/>
			</define>
			<define name="Y">
				<ref name="Z" t:role="field"/>
			</define>
			<define name="Z">
				<element name="foo">
					<data type="string" t:role="primitive"/>
				</element>
			</define>
			
			When processing X->Z, we want to add extra FieldItem.
			When processing Y->Z, we don't want that.
			*/
			
			Expression body = visitXMLItemContent(exp);
			if(body==exp.contentModel)	return exp;
			else	return new ElementPattern( exp.getNameClass(), body );
		}
		
		private Expression visitXMLItemContent( NameClassAndExpression exp ) {
			// if this element or attribute has a simple name, then
			// push it to the name stack.
			String name=null;
			NameClass nc = exp.getNameClass();
			if( nc instanceof SimpleNameClass )
				name = ((SimpleNameClass)nc).localName;
			
			// if this is the direct child of the owner, do not push it to
			// the name stack.
			/*
			Consider the following case. Typically, a ClassItem is created on <element>.
			In this case, ClassItem's exp field is an ElementExp.
			
			<tahiti:classItem name="Foo">
			  <element name="foo">
			    <group>
			      <tahiti:classItem name="First">
			        <element name="FirstName">
			           ....
			
			In cases like this, it's silly to use "foo" as the name of children.
			So don't push it to the name stack.
			*/
			if( exp==owner.exp )
				name = null;
			
			if(name!=null)	names.push(name);
			Expression body = exp.getContentModel().visit(this);
			if(name!=null)	names.pop();
			
			return body;
		}

		/**
		 * <p>
		 * For other live but not complex branches, if a branch doesn't contain
		 * FieldItems, then it is wrapped by a FieldItem. Since FieldItem cannot be
		 * wrapped by a FieldItem, we cannot wrap a branch by a FieldItem if
		 * it contains FieldItem. This case happens only when a user explicitly
		 * annotate a part of the grammar like this:
		 * 
		 * <PRE><XMP>
		 * <choice>
		 *   <group>
		 *     <element name="A"/>
		 *     <element name="B"/>
		 *   </group>
		 *   <group>
		 *     &lt;!-- explicit annotation -->
		 *     <ref name="X" t:role="field"/>
		 *	   ...
		 *   </group>
		 * </choice>
		 * </XMP></PRE>
		 * 
		 * In this case, we recursively process that branch (since that branch may
		 * contains other bare ClassItems.)
		 * 
		 * <p>
		 * All 
		 */		
		public Expression onChoice( ChoiceExp exp ) {
			
			// check whether there is only one meaningul branch, or more than one of them.
			Expression[] b = exp.getChildren();
			boolean[] fieldlessBranch = new boolean[b.length];
			int numLiveBranch = 0;
			
			boolean bBranchWithField = false;
			final boolean[] bBranchWithPrimitive = new boolean[1];

			if(debug!=null) {
				debug.println( "Processing Choice: " + ExpressionPrinter.printContentModel(exp) );
				debug.println("checking each branch");
			}
			
			for( int i=0; i<b.length; i++ ) {
				final boolean[] hasChildFieldItem = new boolean[1];

				// compute the multiplicity of the all child JavaItems and 
				// also compute whether this branch has FieldItem in it.
				Multiplicity m = Multiplicity.calc( b[i],
					new MultiplicityCounter(){
						protected Multiplicity isChild( Expression exp ) {
							if(exp instanceof FieldItem)	hasChildFieldItem[0] = true;
							if(exp instanceof PrimitiveItem)	bBranchWithPrimitive[0] = true;
							
							if(exp instanceof IgnoreItem)	return Multiplicity.zero;
							if(exp instanceof JavaItem)		return Multiplicity.one;
							else						return null;
						}
					});

				if(debug!=null) {
					debug.println( "  Branch: " + ExpressionPrinter.printContentModel(b[i]) );
					debug.println( "    multiplicity:"+m+"  hasChildFieldItem:"+hasChildFieldItem[0] );
				}
				
				if(m.isZero())
					continue;		// do nothing for this branch.
				
				numLiveBranch++;
				
				if( !hasChildFieldItem[0] ) {
					// memorize that this branch is fieldless.
					fieldlessBranch[i] = true;
					continue;
				}
				
				bBranchWithField = true;
				
				// this branch has a FieldItem. perform recursion.
				b[i] = b[i].visit(this);
			}
			
			if( numLiveBranch<=1 ) {
				// there is only one meaningful branch.
				// this happens for patterns like <optional>.
				
				// visit all unvisited branch
				for( int i=0; i<b.length; i++ )
					if( fieldlessBranch[i] )
						b[i] = b[i].visit(this);
				
				Expression r = Expression.nullSet;
				for( int i=0; i<b.length; i++ )
					r = pool.createChoice( r, b[i] );
			
				return r;
				
			} else {
				
				/*
				if we don't have any branch with FieldItem, then we just need
				one FieldItem to cover the entire branches.
				
				TODO:(?) actually this would be done better. Even if there are 
				branches with FieldItems, one created FieldItem can cover all
				FieldItem-less branches, and then that FieldItem and other
				branches can be combined. But is it an improvement?
				*/
				
				final String fieldName = decideName(exp);
				
				Expression r = Expression.nullSet;
				for( int i=0; i<b.length; i++ ) {
					if( bBranchWithField && fieldlessBranch[i] )
						b[i] = new FieldItem( fieldName, b[i] );
					
					r = pool.createChoice( r, b[i] );
				}
				
				if( !bBranchWithField )
					// there was no branch with FieldItem.
					// add a FieldItem in here.
					r = new FieldItem( fieldName, r );
				
				return r;
			}
		}
		
		
		
		
		
		
		/**
		 * decides a name to be used as the field name.
		 * 
		 * Use the name at the stack top. As a side effect,
		 * this method modifies the stack top so that fields that
		 * are added later will have different names.
		 */
		private String decideName( Expression hint ) {
			String name=null;
			
			// use hint if available.
			if( hint!=null && hint instanceof NameClassAndExpression ) {
				NameClass nc = ((NameClassAndExpression)hint).getNameClass();
				if( nc instanceof SimpleNameClass )
					return NameUtil.xmlNameToJavaName("field",((SimpleNameClass)nc).localName);
			}
			
			if( name==null )
				name = (String)names.pop();
			
			// the value of name must be the form of "xxxxxNNN" where 
			// NNN is digits.
			int idx = name.length()-1;
			while( Character.isDigit(name.charAt(idx)) && idx>=0 )	idx--;
			idx++;
			
			if(idx==name.length())	// the name doesn't have any number in its suffix.
				names.push( name+"1" );
			else // increment the number
				names.push( name.substring(0,idx)+(Integer.parseInt(name.substring(idx))+1) );
			
			return NameUtil.xmlNameToJavaName("field",name);
		}
	}


//	/**
//	 * generates a field name suitable to hold a reference for the specified class.
//	 */
//	private static String typeNameToFieldName( String bareName ) {
//		return Character.toLowerCase(bareName.charAt(0))+bareName.substring(1);
//	}
		
	private static void assert( boolean b ) {
		if(!b)	throw new Error();
	}
}
