//----------------------------BEGIN LICENSE----------------------------
/* 
 * Willow : the Open Source WorkFlow Project
 * Distributable under GNU LGPL license by gun.org
 *
 * Copyright (C) 2004-2010 huihoo.org 
 * Copyright (C) 2004-2010  ZosaTapo <dertyang@hotmail.com> 
 * 
 * ====================================================================
 * Project Homepage : http://www.huihoo.org/willow
 * Source Forge     : http://sourceforge.net/projects/huihoo
 * Mailing list     : willow@lists.sourceforge.net 
 */
//----------------------------END  LICENSE-----------------------------
package org.huihoo.workflow.script.compiler;
import java.io.CharArrayWriter;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.huihoo.workflow.WorkflowException;
import org.huihoo.workflow.script.Constants;
import org.huihoo.workflow.util.StringManager;

public class Parser
{
	private static StringManager sm = StringManager.getManager(Constants.RESOURCE_BUNDLE_BASE);
	/**
	 * The input source we read from...
	 */
	private ScriptReader reader;
	/**
	 * The backend that is notified of constructs recognized in the input... 
	 */
	private ParseEventListener listener;
	/*
	 * Char buffer for HTML data
	 */
	CharArrayWriter caw;
	/*
	 * Marker for start and end of the tempate data.
	 */
	Mark tmplStart;
	Mark tmplStop;
	String currentFile;
	public interface Action
	{
		void execute(Mark start, Mark stop) throws WorkflowException;
	}
	public Parser(ScriptReader reader, final ParseEventListener lnr)
	
	{
		this.reader = reader;
		this.listener = new DelegatingListener(lnr, new Action()
		{
			public void execute(Mark start, Mark stop) throws WorkflowException
			{
				Parser.this.flushCharData(start, stop);
			}
		});
		this.caw = new CharArrayWriter();
		this.currentFile = reader.mark().getFile();
	}
	static final Vector coreElements = new Vector();
	/*
			* Godj directives
			*/
	static final class Directive implements CoreElement
	{
		private static final String OPEN_DIRECTIVE = "<%@";
		private static final String CLOSE_DIRECTIVE = "%>";
		static final String[] directives = { "script", };
		private static final ScriptUtilities.ValidAttribute[] scriptDvalidAttrs =
			{ new ScriptUtilities.ValidAttribute("import"), new ScriptUtilities.ValidAttribute("charset")};
		public boolean accept(ParseEventListener listener, ScriptReader reader, Parser parser) throws WorkflowException
		{
			String close;
			String open;
			if (reader.matches(OPEN_DIRECTIVE))
			{
				open = OPEN_DIRECTIVE;
				close = CLOSE_DIRECTIVE;
			}
			else
				return false;
			Mark start = reader.mark();
			reader.advance(open.length());
			reader.skipSpaces();
			// Check which directive it is.
			String match = null;
			for (int i = 0; i < directives.length; i++)
				if (reader.matches(directives[i]))
				{
					match = directives[i];
					break;
				}
			if (match == null)
				throw new WorkflowException(reader.mark() + " " + sm.getString("script.error.invalid.directive"));
			reader.advance(match.length());
			// Parse the attr-val pairs.
			Hashtable attrs = reader.parseTagAttributes();
			if (match.equals("script"))
				ScriptUtilities.checkAttributes("Script directive", attrs, scriptDvalidAttrs, start);
			// Match close.
			reader.skipSpaces();
			if (!reader.matches(close))
				throw new WorkflowException(
					reader.mark() + " " + sm.getString("script.error.unterminated", new Object[] { open }));
			else
				reader.advance(close.length());
			Mark stop = reader.mark();
			listener.handleDirective(match, start, stop, attrs);
			return true;
		}
	}
	static {
		coreElements.addElement(new Directive());
	}
	//scriptlets
	static final class Scriptlet implements CoreElement
	{
		private static final String OPEN_SCRIPTLET = "<%";
		private static final String CLOSE_SCRIPTLET = "%>";
		private static final ScriptUtilities.ValidAttribute[] validAttributes = {
		};
		public boolean accept(ParseEventListener listener, ScriptReader reader, Parser parser) throws WorkflowException
		{
			String close, open, end_open = null;
			Hashtable attrs = null;
			Mark start;
			if (reader.matches(OPEN_SCRIPTLET))
			{
				open = OPEN_SCRIPTLET;
				close = CLOSE_SCRIPTLET;
			}
			else
				return false;
			reader.advance(open.length());
			start = reader.mark();
			if (end_open != null)
			{
				attrs = reader.parseTagAttributes();
				reader.skipSpaces();
				if (!reader.matches(end_open))
					throw new WorkflowException(reader.mark() + " " + sm.getString("script.error.unterminated"));
				reader.advance(end_open.length());
				reader.skipSpaces();
				ScriptUtilities.checkAttributes("Scriptlet", attrs, validAttributes, start);
			}
			Mark stop = reader.skipUntil(close);
			if (stop == null)
				throw new WorkflowException(
					reader.mark() + " " + sm.getString("script.error.unterminated", new Object[] { open }));
			listener.handleScriptlet(start, stop, attrs);
			return true;
		}
	}
	static {
		coreElements.addElement(new Scriptlet());
	}
	void flushCharData(Mark start, Mark stop) throws WorkflowException
	{
		char[] array = caw.toCharArray();
		if (array.length != 0) // Avoid unnecessary out.write("") statements...
		{
			listener.handleCharData(start, stop, caw.toCharArray());
		}
		caw = new CharArrayWriter();

	}
	public void parse() throws WorkflowException
	{
		parse(null);
	}
	public void parse(String until) throws WorkflowException
	{
		parse(until, null);
	}
	public void parse(String until, Class[] accept) throws WorkflowException
	{
		boolean noscriptElement = false;
		while (reader.hasMoreInput())
		{
			if (until != null && reader.matches(until))
				return;

			Enumeration e = coreElements.elements();
			if (accept != null)
			{
				Vector v = new Vector();
				while (e.hasMoreElements())
				{
					CoreElement c = (CoreElement) e.nextElement();
					for (int i = 0; i < accept.length; i++)
						if (c.getClass().equals(accept[i]))
							v.addElement(c);
				}
				e = v.elements();
			}

			boolean accepted = false;
			while (e.hasMoreElements())
			{
				CoreElement c = (CoreElement) e.nextElement();
				Mark m = reader.mark();
				if (c.accept(listener, reader, this))
				{
					accepted = true;
					noscriptElement = false;
					break;
				}
			}
			if (!accepted)
			{
				// This is a hack. "reader.nextContent()" will just return 
				// after it sees "<" -- not necessarily a script element. Using
				// a boolean we will ensure that tmplStart changes only when
				// strictly necessary.
				if (noscriptElement == false)
				{
					tmplStart = reader.mark();
					noscriptElement = true;
				}
				String s = reader.nextContent();
				tmplStop = reader.mark();
				caw.write(s, 0, s.length());
			}
		}
		flushCharData(tmplStart, tmplStop);
	}
}
