/*
 *
 * Sample program for use with the Product
 * Licensed Materials  - Property of IBM
 * 5724-I66
 * (c) Copyright IBM Corp.  2006, 2009
 *   
 */
package com.ibm.wbit.tel.client.jsf.infrastructure;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import com.ibm.bpc.clientcore.ClientException;
import com.ibm.bpc.clientcore.DataObjectUtils;
import com.ibm.bpc.clientcore.MessageWrapper;
import com.ibm.wbit.tel.client.jsf.bean.ToDoInstance;
import com.ibm.wbit.tel.client.jsf.handler.ToDoMessageHandler;
import commonj.sdo.DataObject;

/**
 * This class provides some general utilities, usefully in the context of JSF and this Business User Client.
 */
public class FacesUtils implements SharedConstants {

  
	/**
     * Get the external context of faces application.
     * 
     * @return external context of faces application
     */
    public static ExternalContext getContext() {
        FacesContext facesContext = FacesContext.getCurrentInstance();

        return (facesContext == null) ? null : facesContext.getExternalContext();
    }

    
    /**
     * Return the locale of the request.
     * 
     * @return the locale
     */
    public static Locale getLocale() {
        Locale locale = null;

        if (getContext() != null) {
            locale = getContext().getRequestLocale();
        }

        if (locale == null) {
            locale = new Locale("en", "us");
        }

        return locale;
    }

    
    /**
     * Get managed bean instance for passed name. 
     * The passed name must be defined in faces-config as <managed-bean-name>
     * 
     * @param managedBeanName
     *            see <managed-bean-name></managed-bean-name> in faces-config.
      * @param managedBeanClass
     *            see <managed-bean-class></managed-bean-class>  in faces-config.
     * @return managed bean instance for passed name
     */
    public static Object getManagedBeanInstance(String managedBeanName, Class<?>  managedBeanClass) {
                
        ExpressionFactory expFactory = FacesContext.getCurrentInstance().getApplication().getExpressionFactory();
        ELContext elContext = FacesContext.getCurrentInstance().getELContext();
        String name = "#{" + managedBeanName + "}";
    
        ValueExpression valueExp = expFactory.createValueExpression(elContext, name,  managedBeanClass);

        return valueExp.getValue(elContext);
    }
    

	/**
	 * The ID which makes human tasks unique for template based approach.
	 * 
	 * @param toDoInstance for which to get the unique ID
	 * @return ID which makes human tasks unique for template based approach.
	 */
    public static String getHumanTaskID(ToDoInstance toDoInstance) {
    	
    	return toDoInstance.getDefinitionNamespace() + toDoInstance.getDefinitionName();
    }
    
    
    /**
     * Get input values of passed toDoInstance as HashMap.
     * 
     * @param toDoInstance for which to get the input values
     * @return HashMap populated with input values (API result of query) of passed toDoInstance
     */
    @SuppressWarnings("unchecked")
	public static HashMap<String, Object> getInputMessage(ToDoInstance toDoInstance) throws ClientException {
    	HashMap<String, Object> ret = null;

		if (toDoInstance != null) {
			Object object = toDoInstance.getInputMessageWrapper().getMessage();
			if (object instanceof DataObject) {
				ret = (HashMap<String, Object>) DataObjectUtils.getValueMap((DataObject) object);
			} else {
				ret = new HashMap<String, Object>();
				ret.put(SINGLE_PRIMITIVE_TYPE, object);
			}
		}
		return ret; 
    }
    
    /**
     * Get output values of passed toDoInstance as HashMap.
     * 
     * @param toDoInstance for which to get the output values
     * @return HashMap populated with output values (API result of query) of passed toDoInstance
     */
    @SuppressWarnings("unchecked")
	public static HashMap<String, Object> getOutputMessage(ToDoInstance toDoInstance) throws ClientException {
		HashMap<String, Object> ret = null;

		if ((toDoInstance != null) && (toDoInstance.getOutputMessageTypeName() != null)) {
			MessageWrapper outputMessage = toDoInstance.getOutputMessageWrapper();
			// does outputmessage contain values?
			if (outputMessage != null && outputMessage.getMessage() != null) {
				Object object = outputMessage.getMessage();
				if (object instanceof DataObject) {
					ret = (HashMap<String, Object>) DataObjectUtils.getValueMap((DataObject) object);
				} else {
					ret = new HashMap<String, Object>();
					ret.put(SINGLE_PRIMITIVE_TYPE, object);
				}
			}
		}
		return ret;
	}
    
    /**
     * Get HashMap pre-populated with values from input messsage of toDoInstance.
     * If pre-population is not possible, due to different definition 
     * of input- and output message, null is returned.
	 *   
     * @param toDoInstance from which to get the input values
     * @return HashMap populated with values of toDoInstances input message 
     * and keys suitable for output messsage, or null if pre-population not possible.
     */
    @SuppressWarnings("unchecked")
	public static HashMap<String, Object> getValueMapInOut(ToDoInstance toDoInstance) throws ClientException {
    	
    	//check if input and output-message use same Businesss Objects   
    	HashMap<String, String> inEqualsOut = (HashMap<String, String>) getManagedBeanInstance("toDosInEqualsOut", HashMap.class);
    	return getValueMap(getInputMessage(toDoInstance), getHumanTaskID(toDoInstance), inEqualsOut);
    }
    
    
    /**
     * Get HashMap pre-populated with values from passed soruceValues.
     * The passed humanTaskID is used to get the prefixes for the keys in returned HashMap. 
     * If pre-population is not possible, due to different definition 
     * of output- and input message, null is returned.
	 *   
	 * @param sourceValues used to populate return HashMap
	 * @param humanTaskID to identify the task according to template based approach
     * @return HashMap populated with values of passed soruceValues 
     * and keys suitable for input messsage, or null if pre-population not possible.
     */
    @SuppressWarnings("unchecked")
	public static HashMap<String, Object> getValueMapOutIn(HashMap<String, Object> sourceValues, String humanTaskID) throws ClientException {
    	
    	//check if output and input-message use same Businesss Objects   
    	HashMap<String, String> outEqualsIn = (HashMap<String, String>) getManagedBeanInstance("toDosOutEqualsIn", HashMap.class);
    	return getValueMap(sourceValues, humanTaskID, outEqualsIn);
    }
    
    
    /**
	 * Get HashMap pre-populated with values from input messsage of mainToDo. 
	 * If pre-population MainIn->SubIn is not possible, null is returned.
	 * 
	 * @param subToDoID for which to get pre-populated HashMap
	 * @param mainToDo from which to get the pre-population values
	 * @return HashMap populated with values of mainToDo input message 
     * and keys suitable for subToDo input messsage, or null if pre-population not possible.
	 */
    @SuppressWarnings("unchecked")
	public static HashMap<String, Object> getValueMapMainInSubIn(String subToDoID, ToDoInstance mainToDo) throws ClientException {
    	HashMap<String, Object> ret = null;

		// check if subToDo and mainToDo are of same type
		if (subToDoID.equals(FacesUtils.getHumanTaskID(mainToDo))) {
			// pre-population works anyway
			ret = getInputMessage(mainToDo);
		} else {
			// check if subToDo and mainToDo use same Businesss Objects regarding input message
			HashMap<String, String> mainInSubIn;
			mainInSubIn = (HashMap<String, String>) getManagedBeanInstance("toDosMainInEqualsSubIn", HashMap.class);
			ret = getValueMap(getInputMessage(mainToDo), getHumanTaskID(mainToDo) + subToDoID, mainInSubIn);
		}
		return ret;
    }
    
    
    /**
     * Get HashMap pre-populated with values from output messsage of subToDo.
     * Finished state of subToDo is not required by this method. 
     * If pre population SubOut->MainOut is not possible, null is returned.
     *  
     * @param subToDo from which to get the pre-population values	
     * @param mainToDo whose output message should be pre-populated
     * @return HashMap populated with values of subToDo output message 
     * and keys suitable for mainToDo output messsage, or null if pre-population not possible.
     */
    @SuppressWarnings("unchecked")
	public static HashMap<String, Object> getValueMapSubOutMainOut(ToDoInstance subToDo, ToDoInstance mainToDo) throws ClientException {
		HashMap<String, Object> ret = null;

		// outputmessage defined?
		if (mainToDo.getOutputMessageTypeName() != null) {
			// check if subToDo and mainToDo are of same type
			if (getHumanTaskID(subToDo).equals(getHumanTaskID(mainToDo))) {
				// pre-population works anyway
				ret = getOutputMessage(subToDo);
			} else {
				// check if subToDo and mainToDo use same Business Objects regarding output message
				HashMap<String, String> subOutMainOut;
				subOutMainOut = (HashMap<String, String>) getManagedBeanInstance("toDosSubOutEqualsMainOut", HashMap.class);
				ret = getValueMap(getOutputMessage(subToDo), getHumanTaskID(subToDo) + getHumanTaskID(mainToDo), subOutMainOut);
			}
		}
		return ret;
	}   
    
    
    /**
	 * Get HashMap populated with values of passed sourceValues. The key prefixes
	 * in returned HashMap come for passed prefixMappings.
	 * If pre population is not possible, null is returned. 
	 * 
	 * @param sourceValues used to populate return HashMap
	 * @param prefixMappingKey prefix for key in passed HashMap prefixMappings
	 * @param prefixMappings to get the key prefixes for return HashMap
	 * @return HashMap populated with values from sourceValues and keys according to passed prefixMappings
	 */
    private static HashMap<String, Object> getValueMap(HashMap<String,Object> sourceValues, String prefixMappingKey, HashMap<String, String> prefixMappings) {
    	
		HashMap<String, Object> ret = null;
		boolean mappingFailed = false;
		
		if ((sourceValues != null) && (prefixMappings != null)) {
			Iterator<String> anIterator = sourceValues.keySet().iterator();
			String prefixSrc, prefixTgt, keySrc, keyTgt;
			Object value;
			int idx;
			//we have to "copy" all values and to change the key
			while (anIterator.hasNext() && !mappingFailed) {
				keySrc = anIterator.next();
				value = sourceValues.get(keySrc);
				idx = keySrc.indexOf("/", 1);
				// wrapper element complex data type (BusinessObject) ?
				if (idx != -1) {
					prefixSrc = keySrc.substring(1, idx);
				} else {
					prefixSrc = keySrc.substring(1);
				}
				// get prefix for target mapping
				prefixTgt = (String) prefixMappings.get(prefixMappingKey + prefixSrc);

				if (prefixTgt != null) {
					keyTgt = "/" + prefixTgt;
					// wrapper element complex data type (BusinessObject) ?
					if (idx != -1) {
						keyTgt = keyTgt + keySrc.substring(idx);
					}
					if (ret == null) {
						ret = new HashMap<String, Object>((int) (sourceValues.size() * 1.4f + 0.5f));
					}
					ret.put(keyTgt, value);
				} else {
					mappingFailed = true;
				}
			}
		}
		return mappingFailed ? null : ret;
	}
    
    /**
     * Returns true if the inout and output message for the todomessage handler are equal.
     * They are equal if the can be replaced or if the refer to the same message.
     * 
     * @param toDoMessageHandler The ToDoMessage handler to check.
     * @return true if the inout and outputmessage are equal, otherwise false.
     */
    public static boolean isInputEqualsOutput(ToDoMessageHandler toDoMessageHandler) throws ClientException{
    	
		boolean isReplaceable = FacesUtils.getValueMapInOut(toDoMessageHandler.getToDoInstance()) != null;
		String inputMessageName = toDoMessageHandler.getToDoInstance().getInputMessageTypeName();
		String outputMessage = toDoMessageHandler.getToDoInstance().getOutputMessageTypeName();
		boolean isMessageNameEqual = inputMessageName.equals(outputMessage);
		
		return isReplaceable || isMessageNameEqual;
    }
    
	
	/**
	 * This method will remove all array index statements like "[1]" form 
	 * the passed xpath. Example: If the String "a/b[1]" is passed into
	 * this method the String "a/b[]" would be returned.
	 * 
	 * @param xpaths
	 *            A String object representing xpath statements.
	 *            
	 * @return A object representing no array xpaths.
	 */
	public static String removeArrayIndex(String xpath){
		if (xpath != null && !"".equals(xpath)) {

			String tempXpath = xpath;
			StringBuffer newXpath = new StringBuffer();

			int pos = tempXpath.indexOf("[");
			int pos2 = tempXpath.indexOf("]");

			while (pos >= 0 && pos2 >= 2 && pos < pos2) {
				String firstPart = tempXpath.substring(0, pos + 1);
				newXpath = newXpath.append(firstPart).append("]");
				tempXpath = tempXpath.substring(pos2 + 1);
				pos = tempXpath.indexOf("[");
				pos2 = tempXpath.indexOf("]");
			}
			newXpath.append(tempXpath);

			xpath = newXpath.toString();
		}
		return xpath;
	}
	
	
	/**
	 * This method will return the xpath of the parent node or null if it has no
	 * parent. Example: "/a/b[]/c" => "/a/b[]" and "/a" => null.
	 * 
	 * @param xpath
	 *            The xpath whichs parent to find out.
	 * @return The xpath to the parent node or null.
	 */
	public static String removeLastPart(String xpath) {
		String parentXpath = null;
		if (xpath != null && !"".equals(xpath)) {
			int pos = xpath.lastIndexOf('/');
			if (pos == 0) {
				parentXpath = null;
			} else if (pos == xpath.length() - 1) {
				parentXpath = removeLastPart(xpath.substring(0,
						xpath.length() - 1));
			} else if (pos != -1) {
				parentXpath = xpath.substring(0, pos);
			}
		}
		return parentXpath;
	}
	
	/**
     * Returns true if pre population should be suppressed for passed FORMS task, even if pre population would be possible. 
     * 
     * @param toDoInstance for which to check pre population suppression. 
     * @return true if pre population should be suppressed, otherwise false.
     */
    @SuppressWarnings("unchecked")
	public static boolean isSuppressPrePopulation(ToDoInstance toDoInstance) throws ClientException{
    	Object suppress = FacesUtils.getManagedBeanInstance("formsTaskSuppressPrePopulation", ArrayList.class);
    			
		return suppress != null ? ((ArrayList<String>)suppress).contains( FacesUtils.getHumanTaskID(toDoInstance)) : false ;
    }

}
