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

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.ibm.bpc.clientcore.DataObjectUtils;
import com.ibm.bpe.jsf.exception.PropertyNotFoundException;
import com.ibm.bpe.jsf.exception.PropertyPopulationException;
import com.ibm.task.api.QueryResultSet;
import com.ibm.task.clientmodel.HTMConnection;
import com.ibm.task.clientmodel.bean.TaskInstanceBean;
import com.ibm.wbit.tel.client.jsf.infrastructure.SharedConstants;
import commonj.sdo.DataObject;


/**
 * ToDoInstance represents an entry in list open ToDos and under work ToDos. 
 */
public class ToDoInstance extends TaskInstanceBean implements SharedConstants {
	
	private static final long serialVersionUID = 602L;
	
	/**
     * The value map which contains the values of the input message. 
     * Task input messages can be get by JSF pages. 
     */
	private HashMap<String, Object> inputValues = null;
	 
	/**
     * The value map which contains the values of the output message.
     * Task output messages can be set by JSF pages. 
     */
	private HashMap<String, Object> outputValues = null;
	
	/**
	 * The value map which contains the values of the input message for arrays.
	 */
	private HashMap<String, ArrayInstance> inputValuesArray;
	
	/**
	 * The value map which contains the values of the output message for arrays.
	 */
	private HashMap<String, ArrayInstance> outputValuesArray;
	
	
	/**
	 * The map to store the optional BO instance objects.
	 */
	private HashMap<String, OptionalBoInstance> inputOptionalBo;
	
	
	/**
	 * The map to store the optional BO instance objects.
	 */
	private HashMap<String, OptionalBoInstance> outputOptionalBo;
	

	
		
    /**
	 * Constructs a ToDoInstance from a query result set returned by the HumanTaskManager API
	 * 
	 * @param resultSet the query result from a query call; represents a row of this result
	 * @param htmConnection the connection to the Human Task Manager
	 */
    public ToDoInstance(QueryResultSet resultSet, HTMConnection htmConnection) {
        super(resultSet, htmConnection);  
        this.inputValuesArray = new HashMap<String, ArrayInstance>();
        this.outputValuesArray = new HashMap<String, ArrayInstance>();
        this.inputOptionalBo = new HashMap<String, OptionalBoInstance>();
        this.outputOptionalBo = new HashMap<String, OptionalBoInstance>();        
    }

	/**
     * Get the value map which contains the values of the input message. 
     * This can be get by JSF pages. 
     * 
     * @return the value map which contains the values of the input message
     */
    public HashMap<String, Object> getInputValues() {
        return inputValues;
    }
    
    /** 
     * Get all values for the input message of this ToDoInstance.
	 * To get them values form arrays and non arrays are merged. 
	 * 
	 * @return the value map which contains all values
	 */ 
    public HashMap<String, Object> getInputValuesAll() {
    	return getValuesAll(inputValuesArray, inputValues, inputOptionalBo);    	
    }
    
    /**
     * Get the value map which contains the values of the input message for arrays. 
     * This can be get by JSF pages. 
     * 
     * @return the value map which contains the values of the input message for arrays
     */
    public HashMap<String, ArrayInstance> getInputValuesArray() {
    	return inputValuesArray;
    }
       
    /**
     * Get the value map which contains the values for the output message.
     * This can be get by JSF pages.
     * 
     * @return the value map which contains the values for the output message
     */
    public HashMap<String, Object> getOutputValues() {
        return outputValues;
    }
        
    /** 
     * Get all values for the output message of this ToDoInstance.
	 * To get them values form arrays and non arrays are merged. 
	 * 
	 * @return the value map which contains all values
	 */ 
    public HashMap<String, Object> getOutputValuesAll() {
    	return getValuesAll(outputValuesArray, outputValues, outputOptionalBo);    	
    }
    
	/**
     * Get the values for the output message.
     * If passed <param>dataType</param> is an instance of DataObject the return value will also be of this type.
     * Otherwise the data primitive of output mapped to key SINGLE_PRIMITIVE_TYPE is returned. 
     *  
     * @param dataType the structure (may be empty) of the data to get
     * @return the values / the value of the output message
     * @throws PropertyPopulationException 
     *      		This exception will be thrown if the outputValues of this ToDoInstance does not match the 
     * 				expected Service Data Object.
     * @throws PropertyNotFoundException 
     *      		This exception will be thrown if the outputValues of this ToDoInstance does not match the 
     * 				expected Service Data Object.
     */
    public Object getOutputValuesAll(Object dataType) throws PropertyNotFoundException, PropertyPopulationException {
    	Object ret = null;

		if (outputValues != null) {
			if (dataType instanceof DataObject) {
				ret = DataObjectUtils.populateAll((DataObject) dataType, getValuesAll(outputValuesArray, outputValues, outputOptionalBo) );
			} else {
				ret = outputValues.get(SINGLE_PRIMITIVE_TYPE);
			}
		}
		return ret; 
    }
    
    /**
     * Get the value map which contains the values of the output message for arrays. 
     * This can be get by JSF pages. 
     * 
     * @return the value map which contains the values of the output message for arrays
     */
    public HashMap<String, ArrayInstance> getOutputValuesArray() {
		return outputValuesArray;
	}
       
    /**
	 * Set all values for the input message of this ToDoInstance (array values and non array values). 
	 * If passed <param>inputData</param> is an instance of DataObject or HashMap all 
	 * values will be set to this ToDoInstance. 
	 * Otherwise passed <param>inputData</param> will be mapped to input using key SINGLE_PRIMITIVE_TYPE.
	 * 
	 * @param inputValues the values for the input message
	 */
    @SuppressWarnings("unchecked")
	public void setInputValuesAll(Object inputData) {
    	
    	if (inputData == null) {
			this.inputValues = null;
		} else if (inputData instanceof DataObject) {
			this.inputValues = (HashMap<String, Object>) DataObjectUtils.getValueMap((DataObject) inputData);
		} else if (inputData instanceof HashMap) {
			this.inputValues = (HashMap<String, Object>) inputData;
		} else {
			this.inputValues = new HashMap<String, Object>();
			this.inputValues.put(SINGLE_PRIMITIVE_TYPE, inputData);
		}   
    	setValuesArray(inputValuesArray, inputValues);

    }
   

	/**
     * Set all values for the output message of this ToDoInstance (array values and non array values).
     * If passed <param>outputData</param> is an instance of DataObject or HashMap all
     * values will be set to this ToDoInstance. 
     * Otherwise passed <param>outputData</param> will be mapped using key SINGLE_PRIMITIVE_TYPE.
     * 
     * @param outputValues the values for the output message
     */
    @SuppressWarnings("unchecked")
	public void setOutputValuesAll(Object outputData) {
    	
    	if (outputData == null) {
			this.outputValues = null;
		} else if (outputData instanceof DataObject) {
			this.outputValues = (HashMap<String, Object>) DataObjectUtils.getValueMap((DataObject) outputData);
		} else if (outputData instanceof HashMap) {
			this.outputValues = (HashMap<String, Object>) outputData;
		} else {
			this.outputValues = new HashMap<String, Object>();
			this.outputValues.put(SINGLE_PRIMITIVE_TYPE, outputData);
		}
    	setValuesArray(outputValuesArray, outputValues);
    }
    
    /**
     * Set the values to all arrays of this ToDoInstance. 
     * 
     * @param valuesArray to which to set the values
	 * @param values from which to get all values 
     */
    private void setValuesArray(HashMap<String, ArrayInstance> valuesArray, HashMap<String, Object> values) {
    	
    	for (ArrayInstance array : valuesArray.values()) {
			array.updateValues(values);				
		}	   	
    }
    
    /**
	 * Initialize the input values array associated with passed arrayPrefix.
	 * The structure of nested arrays will be initialized recursively.
	 *  
	 * @param subViewID of the JSF page on which the array is shown 
	 * @param arrayPrefix of the root array to be initialized
	 */
	public void initInputValuesArray(String subViewID, String arrayPrefix) {
		
		if (!inputValuesArray.containsKey(arrayPrefix)) {
			inputValuesArray.put(arrayPrefix, new ArrayInstance(subViewID, arrayPrefix, inputValues));
		}
	}
   
	/**
	 * Initialize the output values array associated with passed arrayPrefix.
	 * The structure of nested arrays will be initialized recursively.
	 *  
	 * @param subViewID of the JSF page on which the array is shown 
	 * @param arrayPrefix of the root array to be initialized
	 */
	public void initOutputValuesArray(String subViewID, String arrayPrefix) {
		
		if (!outputValuesArray.containsKey(arrayPrefix)) {
			outputValuesArray.put(arrayPrefix, new ArrayInstance(subViewID, arrayPrefix, outputValues));
		} 
	}
	
	/**
	 * Get all values of this ToDoInstance.
	 * To do this the passed valuesArray and values are merged. 
	 * 
	 * @param valuesArray from which to get all array values
	 * @param values to from which to get all non array values 
	 * @return the value map which contains all values 
	 */
	private HashMap<String, Object> getValuesAll(HashMap<String, ArrayInstance> valuesArray, HashMap<String, Object> values, Map<String, OptionalBoInstance> optionalBOs) {
		
		if (values != null) {
			Set<String> keys = ( new HashMap<String, Object>(values)).keySet();
			//remove all old array keys, due to remove they may be invalid
			for (String key : keys ) {
				if (isPartOfArray(key) || isPartOfClosedOptionalBO(key, optionalBOs)) {
					values.remove(key);
				}
			}			
		} else {
			values= new HashMap<String, Object>();					
		}			
		for (ArrayInstance array : valuesArray.values()) {
			if(!isPartOfClosedOptionalBO(array.getArrayPrefix(), optionalBOs)){
				values.putAll( array.transformRelativeToAbsolute() );				
			}
		}
		return values;
	}    
	
	/**
	 * This method will return true if the value was part of an array.
	 * 
	 * @param key
	 * 	 		The xpath of the value
	 * @return
	 * 		 True if the element is part of an array.
	 */
	private boolean isPartOfArray(String key) {
		if(this.inputValuesArray.size() == 0){
			return false;
		}
		return key.contains("[");
	}    
    
	/**
	 * This method will return true if the value was part of on closed
	 * optional BO.
	 * 
	 * @param key
	 * 			The xpath of the value
	 * 
	 * @return
	 * 			True if at least one parent BO is closed
	 */
    private boolean isPartOfClosedOptionalBO(String key, Map<String, OptionalBoInstance> optionalBOs) {
       	boolean allParentsOpen = true;
        	
        for(String prefix : optionalBOs.keySet()){
        	if(key.startsWith(prefix)){
        		allParentsOpen = allParentsOpen && optionalBOs.get(prefix).isVisible();
        	}
        }
        	
        return !allParentsOpen;    	
	}
	
	
	public Map<String, OptionalBoInstance> getInputValuesOptionalBO(){
		return this.inputOptionalBo;
	}
	
	public Map<String, OptionalBoInstance> getOutputValuesOptionalBO(){
		return this.outputOptionalBo;
	}
	
	public void initOutputValuesOptionalBO(String xpath){
		if(!this.outputOptionalBo.containsKey(xpath)){
			OptionalBoInstance instance = new OptionalBoInstance();
			boolean visible = instance.isVisible() | isOptionalVisible(this.outputValues,xpath);
			instance.setVisible(visible);
			this.outputOptionalBo.put(xpath, instance);
		}

	}

	public void initInputValuesOptionalBO(String xpath){
		if(!this.inputOptionalBo.containsKey(xpath)){
			OptionalBoInstance instance =  new OptionalBoInstance();
			boolean visible = instance.isVisible() | isOptionalVisible(this.inputValues,xpath);
			instance.setVisible(visible);
			this.inputOptionalBo.put(xpath, instance);
		}

	}
	
	
	private boolean isOptionalVisible(HashMap<String, Object> values, String xpath) {

		for(String key : values.keySet()){
			if(key.length() > xpath.length() && key.startsWith(xpath)){
				return true;
			}	    		
		}
		
		return false;
	}
}
