/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.tools.guiframework.util;

import com.iplanet.jato.RequestContext;
import com.iplanet.jato.RequestManager;
import com.iplanet.jato.view.DisplayField;
import com.iplanet.jato.view.View;
import com.iplanet.jato.view.ViewBean;
import com.iplanet.jato.view.ContainerView;

import com.sun.enterprise.tools.guiframework.exception.FrameworkException;
import com.sun.enterprise.tools.guiframework.view.DescriptorViewManager;
import com.sun.enterprise.tools.guiframework.view.descriptors.ViewDescriptor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;


public class Util {
    
    /**
     *	<P>This method can be used to substitute Request attributes into a
     *	String, or return the value of the attribute if the substitution is the
     *	whole String.  This method looks for the LAST occurance of startToken
     *	in the given String.  It then searches from that location (if found) to
     *	the first occurance of endToken.  The value inbetween is used as the
     *	name of the Request attribute to retrieve.  The value of the Request
     *	attribute (w/ a toString()) is substituted into the string beginning
     *	where the startToken was found and ending at the endToken (replacing
     *	the start and end tokens too of course).  This process is repeated
     *	until no more occurances of startToken are found within the String.</P>
     *	
     *	<P>This algorithm will accomodate nested variables (e.g. "$(A($x))").
     *	It also allows that attribute itself to contain variables.  Care should
     *	be taken to ensure that the attribute included does not directly or
     *	indirectly refer to itself -- this will cause an infinite loop.</P>
     *
     *	<P>There is one special case where the string to be evaluated begins
     *	with the startToken and ends with the endToken.  In this case, string
     *	substitution is NOT performed.  Instead the value of the request
     *	attribute is returned.  The request attribute value is not examined or
     *	processed any further in this case.</P>
     *
     *	<P>NOTE: This method has been enhanced to substitute other types as
     *	well.  The syntax is now $attribute(..) or $session(...), etc.  More
     *	documentation to come later...</P>
     *
     *
     *	@param	ctx		The RequestContext
     *	@param	vd		The closest ViewDescriptor to this string
     *	@param	string		The string to be evaluated.
     *	@param	startToken	Marks the beginning			"$"
     *	@param	typeDelim	Marks separation of type/variable	"("
     *	@param	endToken	Marks the end of the variable		")"
     *
     *	@return	The new string with substitutions, or the specified request
     *		attribute value.
     */
    public static Object replaceVariableWithAttribute(RequestContext ctx, 
            ViewDescriptor vd, String string, String startToken, 
            String typeDelim, String endToken) {

	int stringLen = string.length();
	int delimIndex;
	int endIndex, matchingIndex;
	int parenSemi;
	int startTokenLen = startToken.length();
	int delimLen = typeDelim.length();
	int endTokenLen = endToken.length();
	boolean expressionIsWholeString = false;
	char firstEndChar = SUB_END.charAt(0);
	char firstDelimChar = SUB_TYPE_DELIM.charAt(0);
	char currChar;
	String type;
	Object variable;

	for (int startIndex = string.lastIndexOf(startToken); startIndex != -1;
		 startIndex = string.lastIndexOf(startToken, startIndex-1)) {

	    // Find first typeDelim
	    delimIndex = string.indexOf(typeDelim, startIndex+startTokenLen);
	    if (delimIndex == -1) {
		continue;
	    }

	    // Next find the end token
	    parenSemi = 0;
	    endIndex = -1;
	    // Iterate through the string looking for the matching end
	    for (int curr = delimIndex+delimLen; curr<stringLen; ) {
		// Get the next char...
		currChar = string.charAt(curr);
		if ((currChar == firstDelimChar) && typeDelim.equals(string.substring(curr, curr+delimLen))) {
		    // Found the start of another... inc the semi
		    parenSemi++;
		    curr += delimLen;
		    continue;
		}
		if ((currChar == firstEndChar) && endToken.equals(string.substring(curr, curr+endTokenLen))) {
		    parenSemi--;
		    if (parenSemi < 0) {
			// Found the right one!
			endIndex = curr;
			break;
		    }
		    // Found one, but this isn't the right one
		    curr += endTokenLen;
		    continue;
		}
		curr++;
	    }
	    if (endIndex == -1) {
		// We didn't find a matching end...
		continue;
	    }

/*
	    // Next find end token
	    endIndex = string.indexOf(endToken, delimIndex+delimLen);
	    matchingIndex = string.lastIndexOf(typeDelim, endIndex);
	    while ((endIndex != -1) && (matchingIndex != delimIndex)) {
		// We found a endToken, but not the matching one...keep looking
		endIndex = string.indexOf(endToken, endIndex+endTokenLen);
		matchingIndex = string.lastIndexOf(typeDelim, matchingIndex-delimLen);
	    }
	    if ((endIndex == -1) || (matchingIndex == -1)) {
		continue;
	    }
*/

	    // Handle special case where string starts with startToken and ends
	    // with endToken (and no replacements inbetween).  This is special
	    // because we don't want to convert the attribute to a string, we
	    // want to return it (this allows Object types).
	    if ((startIndex == 0) && (endIndex == string.lastIndexOf(endToken)) && (string.endsWith(endToken))) {
		// This is the special case...
		expressionIsWholeString = true;
	    }

	    // Pull off the type...
	    type = string.substring(startIndex+startTokenLen, delimIndex);
	    DataSource ds = (DataSource)_dataSourceMap.get(type);
	    if (ds == null) {
		throw new FrameworkException("Invalid type '"+type+
		    "' in attribute value: '"+string+"'.");
	    }

	    // Pull off the variable...
	    variable = string.substring(delimIndex+delimLen, endIndex);

	    // Get the value...
	    variable = ds.getValue(ctx, vd, (String)variable);
	    if (expressionIsWholeString) {
		return variable;
	    }

	    // Make new string
	    string = string.substring(0, startIndex) +	// Before replacement
	    	     ((variable == null) ? "" : variable.toString()) +
		     string.substring(endIndex+endTokenLen); // After
            stringLen = string.length();
	}

	// Return the string 
	return string;
    }


    /**
     *	This method replaces the $(..) variables with their attribute values.
     *	It will only do this for Strings and List's that contain Strings.
     */
    public static Object replaceVariablesWithAttributes(Object value, ViewDescriptor vd) {
	if (value == null) {
	    return null;
	}
	return replaceVariablesWithAttributes(
	    RequestManager.getRequestContext(), vd, value);
    }


    /**
     *	This method replaces the $(..) variables with their attribute values.
     *	It will only do this for Strings and List's that contain Strings.
     */
    public static Object replaceVariablesWithAttributes(RequestContext ctx, ViewDescriptor vd, Object value) {
	if (value == null) {
	    return null;
	}
	if (value instanceof String) {
	    value = Util.replaceVariableWithAttribute(
		ctx,
		vd,
		(String)value,
		SUB_START,
		SUB_TYPE_DELIM,
		SUB_END);
	} else if (value instanceof List) {
	    // Create a new List b/c invalid to change shared List
	    List list = ((List)value);
	    int size = list.size();
	    List newList = new ArrayList(size);
	    Iterator it = list.iterator();
	    while (it.hasNext()) {
		newList.add(replaceVariablesWithAttributes(ctx, vd, it.next()));
	    }
	    return newList;
	}
	return value;
    }


    /**
     *	This method returns true of the given String is non-null and has a
     *	length greater than 0.
     *
     *	@param	str	The String to check
     *
     *	@return true if non-null String with length greater than 0
     */
    public static boolean hasValue(String str) {
        if (str == null) {
            return false;
	}
        return str.length() > 0;
    }

    
    public static String replace(String src, String toReplace, String replaceWith) {
        String str = src;
        while (true) {
            int startIndex = str.indexOf(toReplace);
            if (startIndex < 0)
                break;
            int length = str.length();
            int endIndex = startIndex + toReplace.length();
            str = str.substring(0, startIndex) + replaceWith + str.substring(endIndex, length);
        }
        return str;
    }


    /**
     *	This is a helper method to get a View's "Top-level" ViewBean.
     *
     *	@param	view	The View which needs to find its ViewBean.
     */
    public static ViewBean getParentViewBean(View view) {
	while (view != null) {
	    if (view instanceof ViewBean) {
	    	// Make sure that this "ViewBean" is the top ViewBean
		if (view.getParent() == null) {
		    return (ViewBean)view;
		}
	    }
	    view = view.getParent();
	}
	return null;
    }


    /**
     *	<P> This interface defines a String substitution data source.  This
     *	    is used to retrieve values when a $&lt;type&gt;(&lt;data&gt;) is
     *	    encountered within a parameter value.</P>
     *
     *	<P> Implementations of this interface may register themselves
     *	    statically to extend the capabilities of the $() substitution
     *	    mechanism.</P>
     */
    public interface DataSource {
	Object getValue(RequestContext ctx, ViewDescriptor vd, String key);
    }


    /**
     *	<P> This DataSource provides access to HttpRequest attributes.  It
     *	    uses the data portion of the substitution String as a key to the
     *	    HttpRequest attribute Map.</P>
     */
    public static class AttributeDataSource implements DataSource {
	public AttributeDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
	    return ctx.getRequest().getAttribute(key);
	}
    }


    /**
     *	<P> This DataSource provides access to PageSession attributes.  It
     *	    uses the data portion of the substitution String as a key to the
     *	    PageSession attribute Map.</P>
     */
    public static class PageSessionDataSource implements DataSource {
	public PageSessionDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
	    while (vd.getParent() != null) {
		vd = vd.getParent();
	    }
	    return ((ViewBean)vd.getView(ctx)).getPageSessionAttribute(key);
	}
    }
    
    /**
     *	<P> This DataSource provides access to HttpRequest Parameters.  It
     *	    uses the data portion of the substitution String as a key to the
     *	    HttpRequest Parameter Map.</P>
     */
    public static class RequestParameterDataSource implements DataSource {
	public RequestParameterDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
            return ctx.getRequest().getParameter(key);
        }
    }


    /**
     *	<P> This DataSource provides access to View XML parameters.  It
     *	    uses the data portion of the substitution String as a key to the
     *	    ViewDescriptor's parameter Map.  Until a value is found, it will
     *	    continue to look at the parent's ViewDescriptor parameter Map.
     *	    If no parameter matching the name is found, then null is
     *	    returned.</P>
     */
    public static class ParameterDataSource implements DataSource {
	public ParameterDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
	    Object value = null;
	    for (; (value == null) && (vd != null); vd = vd.getParent()) {
		value = vd.getParameter(key);
	    }
	    return value;
	}
    }


    /**
     *	<P> This DataSource simply returns the key that it is given.  This is
     *	    useful for supplying $()'s around the string you wish to mark as a
     *	    string.  If not used, characters such as '=' will be interpretted
     *	    as a separator causing your string to be split -- which can be
     *	    very undesirable. Mostly useful in "if" statements.</P>
     */
    public static class EscapeDataSource implements DataSource {
	public EscapeDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
	    return key;
	}
    }


    /**
     *	<P> This DataSource provides access to HttpSession attributes.  It
     *	    uses the data portion of the substitution String as a key to the
     *	    HttpSession Map.</P>
     */
    public static class SessionDataSource implements DataSource {
	public SessionDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
	    return ctx.getRequest().getSession().getAttribute(key);
	}
    }


    /**
     *	<P> This DataSource provides access to DisplayField values.  It
     *	    uses the data portion of the substitution String as the
     *	    DisplayField name to find.  This is a non-qualified DisplayField
     *	    name.  It will walk up the View tree starting at the View object	     *	    cooresponding to the ViewDescriptor which contained this
     *	    expression.  At each ContainerView, it will look for a child with
     *	    a matching name.</P>
     */
    public static class DisplayFieldDataSource implements DataSource {
	public DisplayFieldDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
            while (vd != null) {
                View view = vd.getView(ctx);
                if (view instanceof ContainerView) {
                    DisplayField child = null;
                    try { 
                        child = (DisplayField) ((ContainerView) view).getChild(key);
                    } catch (Exception ex) {
			// Ignore, we will continue to look for the value
                    }
                    if (child != null) {
                        return child.getValue();
                    }
                }
                vd = vd.getParent();
            }
            return null;
	}
    }

    /**
     *	<P> This class provides an implementation for the syntax $this(xyz)
     *	    where xyz can be any of the following.</P>
     *
     *	<UL><LI>View -- Returns the current View</LI>
     *	    <LI>ViewDescriptor -- Returns the current ViewDescriptor</LI>
     *	    <LI>ParentViewDescriptor -- The parent ViewDescriptor</LI>
     *	    <LI>TopViewDescriptor -- The "top" ViewDescriptor</LI>
     *	    <LI>ParentView -- Returns the parent View (may be null)</LI>
     *	    <LI>ViewBean -- Returns the ViewBean</LI>
     *	    <LI>ViewBeanName -- Returns the ViewBean name</LI>
     *	    <LI>DefaultModel -- Returns the DefaultModel of the closest
     *				ContainerView</LI></UL>
     */
    public static class ThisDataSource implements DataSource {
	public ThisDataSource() {
	}

	public Object getValue(RequestContext ctx, ViewDescriptor vd, String key) {
	    Object value = null;

	    if ((key.equalsIgnoreCase(THIS_VIEW)) || (key.length() == 0)) {
		value = vd.getView(ctx);
	    } else if (key.equalsIgnoreCase(THIS_VIEW_DESCRIPTOR)) {
		value = vd;
	    } else if (key.equalsIgnoreCase(THIS_PARENT_VIEW_DESCRIPTOR)) {
		value = vd.getParent();
	    } else if (key.equalsIgnoreCase(THIS_TOP_VIEW_DESCRIPTOR)) {
		while (vd.getParent() != null) {
		    vd = vd.getParent();
		}
                value = vd;
	    } else if (key.equalsIgnoreCase(THIS_PARENT_VIEW)) {
		vd = vd.getParent();
		value = (vd == null) ? null : vd.getView(ctx);
	    } else if (key.equalsIgnoreCase(THIS_VIEWBEAN)) {
	    	// Get the top ViewDescriptor (may not be a ViewBean)
		while (vd.getParent() != null) {
		    vd = vd.getParent();
		}

		// Get corresponding View
		value = vd.getView(ctx);

		// Make sure its the top View (ViewBean)
		while (((View)value).getParent() != null) {
		    value = ((View)value).getParent();
		}
	    } else if (key.equalsIgnoreCase(THIS_VIEW_BEAN_NAME)) {
		while (vd.getParent() != null) {
		    vd = vd.getParent();
		}
                value=vd.getName();
                
                /*commenting out because this could cause infinte loop in beforeCreate */
                /*
		// Get corresponding View
		value = vd.getView(ctx);

		// Make sure its the top View (ViewBean)
		while (((View)value).getParent() != null) {
		    value = ((View)value).getParent();
		}
                value = ((View)value).getName();
                 */ 
	    } else if (key.equalsIgnoreCase(THIS_DEFAULT_MODEL)) {
		// This will not work in a beforeCreate! (infinite loop)
		View view = vd.getView(ctx);
		while ((view != null) && !(view instanceof ContainerView)) {
		    view = view.getParent();
		}
		value = ((ContainerView)view).getDefaultModel();
	    } else {
		throw new FrameworkException("'"+key+"' is not valid in $this("
		    +key+").  This was found on '"+vd.getName()+"'.");
	    }
	
	    return value;
	}

	/**
	 *  <P> Defines "view" in $this(view).  Returns the View object.</P>
	 */
	public static final String THIS_VIEW		= "View";


	/**
	 *  <P> Defines "ParentView" in $this(ParentView).  Returns the
	 *	parent View object.</P>
	 */
	public static final String THIS_PARENT_VIEW  = "ParentView";


	/**
	 *  <P> Defines "ViewBean" in $this(ViewBean).  Returns the ViewBean
	 *	object.</P>
	 */
	public static final String THIS_VIEWBEAN	= "ViewBean";


	/**
	 *  <P> Defines "ViewDescriptor" in $this(ViewDescriptor).  Returns
	 *	the ViewDescriptor.</P>
	 */
	public static final String THIS_VIEW_DESCRIPTOR	= "ViewDescriptor";


	/**
	 *  <P> Defines "ParentViewDescriptor" in $this(ParentViewDescriptor).
	 *	Returns the parent ViewDescriptor object.</P>
	 */
	public static final String THIS_PARENT_VIEW_DESCRIPTOR= "ParentViewDescriptor";


	/**
	 *  <P> Defines "TopViewDescriptor" in $this(TopViewDescriptor).
	 *	Returns the top ViewDescriptor object -- most likely a
	 *	ViewBean ViewDescriptor.</P>
	 */
	public static final String THIS_TOP_VIEW_DESCRIPTOR= "TopViewDescriptor";


	/**
	 *  <P> Defines "ViewBeanName" in $this(ViewBeanName).  Returns the
	 *	ViewBeanName.</P>
	 */
	public static final String THIS_VIEW_BEAN_NAME	= "ViewBeanName";


	/**
	 *  <P> Defines "DefaultModel" in $this(DefaultModel).  Returns the
	 *	DefaultModel associated with the nearest ContainerView.</P>
	 */
	public static final String THIS_DEFAULT_MODEL	= "DefaultModel";
    }

    public static void main(String args[]) {
	System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escape(ViewDescriptor))", "$", "(", ")"));
	System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escape(EEPersistenceManager))", "$", "(", ")"));

	System.out.println(""+replaceVariableWithAttribute(null, null, "$es$cape$escape(EEPersistenceManager))", "$", "(", ")"));
	System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escapeEEP$ersistenceManager))", "$", "(", ")"));

	System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escape(EEPersistenceManager)))", "$", "(", ")"));
	System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escape(EEPersistenceManager())", "$", "(", ")"));
	System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escape($escape(EEPersistenceManager()))==$escape(EEPersistenceManager()))", "$", "(", ")"));
	System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escape($escape(EEPersistenceManager()))==$escape(EEPersistenceManager()))", "$", "(", ")"));
	for (int x=0; x<100000; x++) {
	    System.out.println(""+replaceVariableWithAttribute(null, null, "$escape($escape(EEPers"+x+"istenceManager()))==$escape(EEPersistenceManager())", "$", "(", ")"));
	}
    }

    /**
     *	Contains the data sources for $type(variable) syntax.
     */
    public static Map _dataSourceMap			= new HashMap();


    /**
     *	<P> Defines "attribute" in $attribute(...).  This allows you to
     *	    retrieve an HttpRequest attribute.</P>
     */
    public static final String	    ATTRIBUTE		= "attribute";


    /**
     *	<P> Defines "pageSession" in $pageSession(...).  This allows you to
     *	    retrieve a PageSession attribute.</P>
     */
    public static final String	    PAGE_SESSION	= "pageSession";


    /**
     *	<P> Defines "parameter" in $parameter(...).  This allows you to
     *	    retrieve a parameter from the view XML file.</P>
     */
    public static final String	    PARAMETER		= "parameter";


    /**
     *	<P> Defines "session" in $session(...).  This allows you to retrieve
     *	    an HttpSession attribute.
     */
    public static final String	    SESSION		= "session";


    /**
     *	<P> Defines "requestParameter" in $requestParameter(...).  This allows
     *	    you to retrieve a HttpRequest parameter (QUERY_STRING
     *	    parameter).</P>
     */
    public static final String	    REQUEST_PARAMETER	= "requestParameter";


    /**
     *	<P> Defines "display" in $display(...).  This allows you to retrive
     *	    a DisplayField value.</P>
     */
    public static final String	    DISPLAY             = "display";


    /**
     *	<P> Defines "this" in $this(...).  This allows you to retrieve a
     *	    number of different objects related to the relative placement of
     *	    this expression.</P>
     *
     *	@see ThisDataSource
     */
    public static final String	    THIS		= "this";


    /**
     *	<P> Defines "escape" in $escape(...).  This allows some reserved
     *	    characters to be escaped in "if" attributes.  Such as '=' or
     *	    '|'.</P>
     */
    public static final String	    ESCAPE		= "escape";


    static {
	AttributeDataSource attrDS = new AttributeDataSource();
	_dataSourceMap.put(ATTRIBUTE, attrDS);
	_dataSourceMap.put("", attrDS);
	_dataSourceMap.put(PAGE_SESSION, new PageSessionDataSource());
	_dataSourceMap.put(PARAMETER, new ParameterDataSource());
	_dataSourceMap.put(SESSION, new SessionDataSource());
	_dataSourceMap.put(REQUEST_PARAMETER, new RequestParameterDataSource());
	_dataSourceMap.put(DISPLAY, new DisplayFieldDataSource());
	_dataSourceMap.put(THIS, new ThisDataSource());
	_dataSourceMap.put(ESCAPE, new EscapeDataSource());
    }


    /**
     *	The '$' character marks the beginning of a substituion in a String.
     */
    public static final String SUB_START	= "$";


    /**
     *	The '(' character marks the beginning of the data content of a String
     *	substitution.
     */
    public static final String SUB_TYPE_DELIM	= "(";


    /**
     *	The ')' character marks the end of the data content for a String
     *	substitution.
     */
    public static final String SUB_END		= ")";
}
