/*
 * 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.admingui.util;

import com.iplanet.jato.*;
import com.iplanet.jato.view.*;
import com.iplanet.jato.view.event.*;
import com.iplanet.jato.view.html.*;
import com.iplanet.jato.model.*;
import com.sun.web.ui.common.CCI18N;

import com.sun.enterprise.tools.admingui.AdminGUIConstants;
import com.sun.enterprise.tools.admingui.tree.IndexTreeNode;
import com.sun.enterprise.tools.admingui.tree.IndexTreeModelImpl;
import com.sun.enterprise.tools.admingui.tree.MonitorTreeModelImpl;
import com.sun.enterprise.tools.admingui.tree.IndexTreeModel;
import com.sun.enterprise.tools.guiframework.exception.FrameworkException;

import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.StringWriter;
import java.io.FileReader;
import java.io.EOFException;

import java.util.Locale;
import java.util.List;
import java.util.Vector;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Date;

import java.net.URLEncoder;
import java.text.MessageFormat;
import java.text.DateFormat;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

// For now o.k. Ask backend support when we move to common console.
import com.sun.enterprise.util.SystemPropertyConstants;

public class Util {
    
    public static String getMessage(String key) {
       CCI18N i18n = null;
       RequestContext ctx = RequestManager.getRequestContext();
       i18n = (CCI18N)ctx.getRequest().getAttribute("__i18nObject");
       if (i18n == null) { 
           i18n = new CCI18N(ctx, "com.sun.enterprise.tools.admingui.resources.Resources");
           ctx.getRequest().setAttribute("__i18nObject", i18n);
       }
       return i18n.getMessage(key); 
    }
    
    public static String getMessage(String key, Object args[]) {
       CCI18N i18n = null;
       RequestContext ctx = RequestManager.getRequestContext();
       i18n = (CCI18N)ctx.getRequest().getAttribute("__i18nObject");
       if (i18n == null) {
           i18n = new CCI18N(ctx, "com.sun.enterprise.tools.admingui.resources.Resources");
           ctx.getRequest().setAttribute("__i18nObject", i18n);
            
        }
        return i18n.getMessage(key, args);
   }

    public static void setCurrentTreeModelType(RequestContext rc, String modelType) {
        rc.getRequest().getSession().setAttribute(TREE_MODEL_TYPE, modelType);
    }
    
    public static String getCurrentTreeModelType(RequestContext rc) {
        String modelType = (String) rc.getRequest().getSession().getAttribute(TREE_MODEL_TYPE);
        if (modelType == null)
            modelType = DEFAULT_TREE_MODEL_TYPE;
        return modelType;
    }
    
    public static IndexTreeModel getCurrentTreeModel() {
        RequestContext rc = RequestManager.getRequestContext();
        String modelType = getCurrentTreeModelType(rc);
        
        if (modelType.equals("index")) {
            return IndexTreeModelImpl.getIndexTreeModel(rc);
        } else if (modelType.equals("monitor")) {
            return MonitorTreeModelImpl.getIndexTreeModel(rc);
        }
        throw new RuntimeException("Invalid model type specified.");
    }
    
    // these methods set and get the right-side view for returning to when switching treees.
    public static void setCurrentViewURL(String viewName) {
        RequestContext rc = RequestManager.getRequestContext();
        rc.getRequest().getSession().setAttribute(
            getCurrentTreeModelType(rc), viewName);
    }
    
    public static String getCurrentViewURL(RequestContext rc) {
        if (rc == null)
            rc = RequestManager.getRequestContext();
        String view = (String) rc.getRequest().getSession().getAttribute(
            getCurrentTreeModelType(rc));
        return (view == null)?(""):(view);
    }
    
    public static final IndexTreeNode getSelectedNode() {
        RequestContext rc = RequestManager.getRequestContext();
	IndexTreeNode selectedNode = (IndexTreeNode)
            rc.getRequest().getSession().getAttribute(
                getCurrentTreeModelType(rc)+SELECTEDNODE);
        
        // if not in session, try the model.
        if (selectedNode == null) {
            IndexTreeModel model = getCurrentTreeModel();
            selectedNode = ((IndexTreeNode)model.getCurrentNode());
            if (selectedNode == null) {
                // avoid returning null, so return the root.
                selectedNode = model.getRoot();
            }
        }
        return selectedNode;
    }
    
    public static void setSelectedNode(IndexTreeNode selectedNode) {
        RequestContext rc = RequestManager.getRequestContext();
	rc.getRequest().getSession().setAttribute(
            getCurrentTreeModelType(rc)+SELECTEDNODE, selectedNode);
    }
    
    public static String URLencode(RequestContext ctx, String str) {
        String encoding = ctx.getResponse().getCharacterEncoding() != null ?
            ctx.getResponse().getCharacterEncoding() : CCI18N.ISO_ENCODING;
        try {
            str = URLEncoder.encode(str, encoding);
        } catch (java.io.UnsupportedEncodingException ex) {
        }
        return str;
    }
    
    /**
     * This method concatenates the delimiter char to the end of each string
     * in the array, and returns a single string with the concatenated string.
     */
    
    public static String concatDelimiter(String[] str,  String delimiter){
	StringBuffer retStr = new StringBuffer();
	
	if(str != null) {
	    for (int i=0; i < str.length; i++) {
		String element = str[i];
		
		if (element == null || element.length() == 0) {
		    throw new IllegalArgumentException();
		}
		retStr.append(element);
		
		if (i < str.length - 1) {
		    retStr.append(delimiter);
		}
	    }
	}
	
	return retStr.toString();
	
    }
    
    /** 
     *	This method converts a string into stringarray, uses the delimeter as the
     *	separator character. If the delimiter is null, uses space as default.
     */
    
    public static String[] stringToArray(String str, String delimiter) {
	String[] retString = new String[0];
	
	if (str != null) {
	    if(delimiter == null) {
		delimiter = " ";
	    }
	    StringTokenizer tokens = new StringTokenizer(str, delimiter);
	    retString = new String[tokens.countTokens()];
	    int i = 0;
	    while(tokens.hasMoreTokens()) {
		retString[i++] = tokens.nextToken();
	    }
	}
	return retString;
    }
    
    /** 
     *	This method converts String array into HashMap, uses the elements in the array as key.
     */
    
    public static HashMap stringArrayToHashMap(String[] str) {
	HashMap h = new HashMap();
	
	for (int i=0; str != null && i < str.length; i++) {
	    h.put(str[i], str[i]);
	}
	return h;
    }
    
    public static String getDefaultDisplayURL(String url) {
	if (isLoggableFINER()) {
	    logFINER("URL = "+AdminGUIConstants.DEFAULT_DISPLAY_URL_DIR+url);
	}
	return AdminGUIConstants.DEFAULT_DISPLAY_URL_DIR+url;
    }

	public static String getLocalizedURL(RequestContext rc, String fileName) {
		return getLocalizedURL(rc, fileName, null);
	}
    
    public static String getLocalizedURL(RequestContext rc, String fileName, Locale locale) {
	if(locale == null) {
		locale = getLocale();
	}
	if (locale.equals(Locale.ENGLISH) || locale.getLanguage().equals("en"))
	    return fileName;
	
	int lastdot = fileName.lastIndexOf(".");
	String newName = fileName.substring(0,lastdot)+"_"+locale.getLanguage() +
	fileName.substring(lastdot,fileName.length());
	
	try {
	    verifyClassExists(rc.getServletContext(), newName);
	} catch (ClassNotFoundException ex) {
	    // don't return a localized version, use the given version
	    return fileName;
	}
	return newName;
    }
    
    public static String getLocalizedString(String key) {
	return key;
    }
    
    private static void verifyClassExists(ServletContext sc, String fileName) throws ClassNotFoundException {
	String className = null;
	if (fileName.endsWith(".jsp")) {
	    className = fileName.substring(0,fileName.lastIndexOf(".jsp"))+"_jsp";
	    className = className.replaceAll("/", "._");
	    className = "_jasper"+className;
	    
	    try {
		Class clazz = Class.forName(className);
		return;
	    } catch (ClassNotFoundException ex) {
	    } catch (Exception ex) {
	    }
	}
	if (sc != null) {
	    // look for the file itself
	    String path = sc.getRealPath(fileName);
	    File f = new File(path);
	    if (f.exists() == false) {
		String msg = "";
		if (className != null) {
		    msg = "Class \""+className+"\" not found, and";
		}
		msg += "File \""+path+"\" not found!";
		throw new ClassNotFoundException(msg);
	    }
	} else {
	    throw new FrameworkException("sc null");
	}
    }


    //
    //	The following are helper methods for sending messages to the log file.
    //	Here is a description of how the variouis log levels should be used:
    //
    //	SEVERE	-- When an unrecoverable GUI error occurs.  This does NOT
    //		   include errors thrown from the backend.  This should only
    //		   be thrown if something unexpected in our GUI code occurs.
    //		   These errors should NOT be thrown if the AdminGUI itself
    //		   does not have a problem.  The administrator should react to
    //		   these messages to fix the problem or file a bug against the
    //		   AdminGUI.
    //
    //	WARNING	-- A less severe GUI error.  This does NOT include errors
    //		   thrown from the backend.  These errors may be generated
    //		   from edge cases that we don't support or may be bugs or
    //		   admin GUI configuration problems.  The administrator may
    //		   want to look into these, but most likely this error is
    //		   something that was preventable by the user.
    //
    //	INFO	-- This level is appropriate if a requested business action
    //		   cannot be completed due to an error.  If the backend fails,
    //		   expected business information does not exist, or invalid
    //		   data is passed in, this would be the appropriate Log level.
    //		   This level is visible by default, yet it does not indicate a
    //		   problem with the system (NOTE: if the backend fails for a
    //		   more severe reason, the backend is responsible for logging
    //		   the problem).  Business logic exceptions and messages should
    //		   mostly be logged at this level.
    //
    //	CONFIG	-- This log level should be used to display high-level success
    //		   or informational messages.  For example, when invoking an
    //		   MBean, you might want to log some information about the
    //		   result at this level.  This should be business-logic
    //		   oriented and not meant to be useful for tracing the flow of
    //		   the application.
    //
    //	FINE	-- This log level should be used to trace course information.
    //		   For example, a page or model has been loaded.
    //
    //	FINER	-- This log level should be used to trace more detailed
    //		   information such as a field-level tracing, or method level
    //		   tracing.
    //
    //	FINEST	-- This log level should be used for very detailed log
    //		   information, this level should be used whenever you log
    //		   specific information to assist debugging exactly what is
    //		   going on.  This information should be developer oriented and
    //		   is not intended to be useful to the end user.
    //

    // SEVERE helper log methods
    public static boolean isLoggableSEVERE() {
	return sLogger.isLoggable(Level.SEVERE);
    }

    public static void logSEVERE(String msg) {
	sLogger.log(Level.SEVERE, msg);
    }

    public static void logSEVERE(Throwable ex) {
	logSEVERE("", ex);
    }

    public static void logSEVERE(String msg, Throwable ex) {
	sLogger.log(Level.SEVERE, msg, ex);
    }

    // WARNING helper log methods
    public static boolean isLoggableWARNING() {
	return sLogger.isLoggable(Level.WARNING);
    }

    public static void logWARNING(String msg) {
	sLogger.log(Level.WARNING, msg);
    }

    public static void logWARNING(Throwable ex) {
	logWARNING("", ex);
    }

    public static void logWARNING(String msg, Throwable ex) {
	sLogger.log(Level.WARNING, msg, ex);
    }

    // INFO helper log methods
    public static boolean isLoggableINFO() {
	return sLogger.isLoggable(Level.INFO);
    }

    public static void logINFO(String msg) {
	sLogger.log(Level.INFO, msg);
    }

    public static void logINFO(Throwable ex) {
	logINFO("", ex);
    }

    public static void logINFO(String msg, Throwable ex) {
	sLogger.log(Level.INFO, msg, ex);
    }

    // CONFIG helper log methods
    public static boolean isLoggableCONFIG() {
	return sLogger.isLoggable(Level.CONFIG);
    }

    public static void logCONFIG(String msg) {
	sLogger.log(Level.CONFIG, msg);
    }

    public static void logCONFIG(Throwable ex) {
	logCONFIG("", ex);
    }

    public static void logCONFIG(String msg, Throwable ex) {
	sLogger.log(Level.CONFIG, msg, ex);
    }

    // FINE helper log methods
    public static boolean isLoggableFINE() {
	return sLogger.isLoggable(Level.FINE);
    }

    public static void logFINE(String msg) {
	sLogger.log(Level.FINE, msg);
    }

    public static void logFINE(Throwable ex) {
	logFINE("", ex);
    }

    public static void logFINE(String msg, Throwable ex) {
	sLogger.log(Level.FINE, msg, ex);
    }

    // FINER helper log methods
    public static boolean isLoggableFINER() {
	return sLogger.isLoggable(Level.FINER);
    }

    public static void logFINER(String msg) {
	sLogger.log(Level.FINER, msg);
    }

    public static void logFINER(Throwable ex) {
	logFINER("", ex);
    }

    public static void logFINER(String msg, Throwable ex) {
	sLogger.log(Level.FINER, msg, ex);
    }

    // FINEST helper log methods
    public static boolean isLoggableFINEST() {
	return sLogger.isLoggable(Level.FINEST);
    }

    public static void logFINEST(String msg) {
	sLogger.log(Level.FINEST, msg);
    }

    public static void logFINEST(Throwable ex) {
	logFINEST("", ex);
    }

    public static void logFINEST(String msg, Throwable ex) {
	sLogger.log(Level.FINEST, msg, ex);
    }

	public static String getLocalizedHTML(RequestContext rc, String fileName) {
		return getLocalizedHTML(rc, fileName, null);
	}
    public static String getLocalizedHTML(RequestContext rc, String fileName, Locale locale) {
	if (locale == null) {
		locale = getLocale();
	}
	if (locale.equals(Locale.ENGLISH) || 
            locale.getLanguage() == null || 
            locale.getLanguage().equalsIgnoreCase("null") || 
            locale.getLanguage().equals("en")) {
                return fileName;
	}
        
	int lastdot = fileName.lastIndexOf(".html");
	String newName = fileName.substring(0,lastdot)+"_"+locale.getLanguage()+".html";

	String path = rc.getServletContext().getRealPath(newName);
	File f = new File(path);

	if (!(f.exists())) {
	    if (isLoggableWARNING()) {
		logWARNING("AdminGUI: "+path+" File Not Found");
	    }
	    return fileName;
	}
	return newName;
    }
    
    //Use this API if you can't use the CI18N provided by lockhart. Most of our localization
    //can be done using CI18N, using this just for deployment screens.
    public static String getMessage(String baseName, String key, Object[] args) {
	return getMessage(baseName, key, args, null);
    }
    

    public static String getMessage(String baseName, String key, Object[] args, Locale locale) {
	if (key == null) {
	    return null;
	}
	if(locale == null) {
		locale = getLocale();
	}
	ResourceBundle bundle = getResourceBundle(baseName, locale);

	if(args == null) {
	    return getMessageFromBundle(key, bundle);
	}
	String pattern = getMessageFromBundle(key, bundle);
	MessageFormat mf = new MessageFormat(pattern);
	
	Object[] mfArgs = new Object[args.length];
	for(int i = 0; i < args.length; i++) {
	    mfArgs[i] = getMessageFromBundle(args[i].toString(), bundle);
	}
	
	key = mf.format(mfArgs);
	return key;
    }

	// Once the bugid: 6192814 is fixed in lockhart CCI18N, change
	// the below getLocale() method to use CCI18N. This is just a
	// temporary fix for bugid: 6191081

	private static Locale getLocale() {
		RequestContext ctx = RequestManager.getRequestContext();
		Locale locale = null;
		if(ctx != null) {
			HttpServletRequest request = ctx.getRequest();
                        //FIXME: temporary workaround.  jato context is lost 
                        //in new redeploy.jsp when a non-jato RedeployServlet is
                        //called.  Catch java.lang.IllegalStateException: Null request object
                        //from org.apache.coyote.tomcat5.CoyoteRequestFacade.getLocale.
                        //Use the Locale.getDefault() instead.
                        try {
                            locale = request.getLocale();
                        } catch (Exception ex) {
                            locale = Locale.getDefault();
                        }
		}
		return (locale == null ? Locale.getDefault() : locale);
	}

     public static String fileReader(String fileName) throws FileNotFoundException, IOException {
	StringWriter sr = new StringWriter();
	try {
		FileReader fr = new FileReader(fileName);
		
		char[] buf = new char[1024];
		int len = 0;
		while (len != -1) {
			try {
				len = fr.read(buf, 0, buf.length);
			} catch (EOFException eof) {
				break;
			}
			if (len != -1) {
				sr.write(buf, 0, len);
			}
		}

		fr.close();
		sr.close();

	} catch (FileNotFoundException fnfe) {
	    //We may never reach here, it's better to get this from the properties file.
	    throw fnfe;
	    //logWARNING("Please check whether the file "+fileName +" exists, and is readable");
	    //sr.write("Please check whether the file "+fileName +" exists, and is readable");
        	//System.out.println("FileNotFoundException ...");
	}  
	catch (IOException ioe) {
	    throw ioe;
	    //We may never reach here, it's better to get this from the properties file.
	    //logWARNING("IOException in Util.fileReader:"+fileName);
	}  
	return sr.toString();
    }
     
    public static boolean isEmpty(String str) {
        if (str == null) 
            return true;
        if (str.trim().length() == 0)
            return true;
        return false;
    }
     
    private static String getMessageFromBundle(String key, ResourceBundle bundle) {
	try {
	    key = bundle.getString(key);
	}
	catch (MissingResourceException ex) {
	}
	return key;
    }
    private static ResourceBundle getResourceBundle(String baseName, Locale locale) {
	ResourceBundle bundle = null;
	try {
	    bundle = ResourceBundle.getBundle(baseName, locale);
	}
	catch(MissingResourceException ex) {
	    try {
		bundle = ResourceBundle.getBundle(baseName);
	    }
	    catch(MissingResourceException ex1) {
		//returns the default ResourceBundle
		bundle = ResourceBundle.getBundle("com.sun.enterprise.tools.admingui.resources.Resources");
	    }
	}
	return bundle;
    }

    // three methods to manipulate a HTML string generated for a tag.
    public static String addHtmlProp(String tag, String addText) {
        int i = tag.indexOf(' ');
        if (i<0)
            return tag;
        return tag.substring(0,i) + " " + addText + tag.substring(i,tag.length());
    }
    
    
    public static String removeHtmlProp(String tag, String prop) {
        int i = tag.indexOf(prop);
        if (i<0) return tag;
        int j = tag.indexOf('\"', i+2+prop.length());
        if (j<0) return tag;
        
        
        
        //At this point, j is pointing to the end quote. doing the substring(j+1, tag.length()) should
        //be enough to remove that property.  We shouldn't remove one extra char.
        //bug# 6315465, the tag is of the form <a href="../admingui/links?links.childPageLink=_VALUE_&amp;jato.pageSession=" name="links.childPageLink">__0__</a>
        //removing one extra char means removing the ending '>',  causing a missing end tag and the links doesn't appear.
        
        /*
        if(j+2 == tag.length()) {
            return tag.substring(0,i) + tag.substring(j+1, tag.length());            
        } else {            
            return tag.substring(0,i) + tag.substring(j+2, tag.length());
        }
         */
        
        return tag.substring(0,i) + tag.substring(j+1, tag.length());
        
        
    }
    
    public static String extractHtmlProp(String tag, String prop) {
         int i = tag.indexOf(prop);
         if (i<0) return "";
         int j = tag.indexOf('\"', i+2+prop.length());
         if (j<0) return "";
         return tag.substring(i+2+prop.length(), j);
    }
    
    /**
     * Parses a string containing substrings separated from
     * each other by the standard separator characters and returns
     * a list of strings.
     *
     * Splits the string <code>line</code> into individual string elements 
     * separated by the field separators, and returns these individual 
     * strings as a list of strings. The individual string elements are 
     * trimmed of leading and trailing whitespace. Only non-empty strings
     * are returned in the list.
     *
     * @param line The string to split
     * @return     Returns the list containing the individual strings that
     *             the input string was split into.
     */
    public static List parseStringList(String line)
    {
        return parseStringList(line, null);
    }

    /**
     * Parses a string containing substrings separated from
     * each other by the specified set of separator characters and returns
     * a list of strings.
     *
     * Splits the string <code>line</code> into individual string elements 
     * separated by the field separators specified in <code>sep</code>, 
     * and returns these individual strings as a list of strings. The 
     * individual string elements are trimmed of leading and trailing
     * whitespace. Only non-empty strings are returned in the list.
     *
     * @param line The string to split
     * @param sep  The list of separators to use for determining where the
     *             string should be split. If null, then the standard
     *             separators (see StringTokenizer javadocs) are used.
     * @return     Returns the list containing the individual strings that
     *             the input string was split into.
     */
    public static List parseStringList(String line, String sep)
    {
        if (line == null)
            return null;

        StringTokenizer st;
        if (sep == null)
            st = new StringTokenizer(line);
        else 
            st = new StringTokenizer(line, sep);

        String token;

        List tokens = new Vector();
        while (st.hasMoreTokens())
        {
            token = st.nextToken().trim();
            if (token.length() > 0)
                tokens.add(token);
        }

        return tokens;
    }

    public static String getFormattedDate(long time, int dateStyle) {
	DateFormat df = DateFormat.getDateTimeInstance(dateStyle, DateFormat.MEDIUM, getLocale());
	String formattedDate = df.format(new Date(time));
	return formattedDate;
    }

    public static String getDomainRoot() {
        String domainRoot = System.getProperty(SystemPropertyConstants.INSTANCE_ROOT_PROPERTY);
        return domainRoot;
    }

    private static final String SELECTEDNODE = "selectedNode";
    private static final String TREE_MODEL_TYPE = "treeModelType";
    public static final String DEFAULT_TREE_MODEL_TYPE = "index";
    public static final Logger sLogger = Logger.getLogger(AdminGUIConstants.LOGGER_NAME);
}
