package edu.uprm.admg.nettraveler.schema;

//JDK imports
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Arrays;

import edu.uprm.admg.nettraveler.type.MDObject;
import edu.uprm.admg.util.DataVerify;

/**
 * <code>FunctionValueAttribute</code> is used to implement an attribute
 * whose value is computed by evaluating a function call, and this attribute
 * will not be a projection in the query.
 * <br>
 * Universidad de Puerto Rico@Mayaguez.
 * <br>
 * <code>NetTraveler</code>
 */
/*
 * @author Manuel Rodriguez-Martinez
 * @author Elliot A. Vargas-Figueroa - M.S. Thesis Project.
 * P.I. Manuel Rodriguez-Martinez
 *
 */

public class FunctionValueAttribute extends ValueAttribute {
	/**
	 * Serial version
	 */
	private static final long serialVersionUID = -1423731526899217646L;
	/** The method descriptor used to compute the value of this attribute. **/
	private MethodDescriptor methodDesc = null;
	/** index with the positions of the arguments for the function in the 
	 * Tuple object **/
	private int[] argumentsIdx = null;
	/** The index of the result of this function in the
	 * base tuple **/
	private int baseIdx;
	/** Storage for the arguments */
	private transient MDObject[] arguments = null;
	/**
	 * Cached hash value
	 */
	private transient int cachedHashCode = 0;
	
	/**
	 * Constructs a new <code>FunctionValueAttribute</code> from an 
	 * attribute name, attribute type, attribute base class, array
	 * with the positions of the function arguments in a tuple, and 
	 * a method descriptor used to executed the body of the function.
	 * @param name the name of the attribute.
	 * @param argsIdx an array with the positions of the arguments to the function a the argument tuple.
	 * @param methodDesc the method descriptor used to execute the body of the function.
	 * @param resultClassName the class implementing the base type for the result of the function.
	 * @param baseIdx Index of the result of the operation in the working tuple.
	 * 
	 * @exception IllegalArgumentException if any of the argument is null or has any illegal value.
	 */
	public FunctionValueAttribute(String name, int[] argsIdx, 
			MethodDescriptor methodDesc, String resultClassName,
			int baseIdx) 
	throws IllegalArgumentException {
		
		super(name, resultClassName);
		
		DataVerify.VerifyObjectParam("args_idx", argsIdx);
		DataVerify.VerifyObjectParam("method_desc", methodDesc);
		this.argumentsIdx = argsIdx;
		this.methodDesc = methodDesc;
		this.arguments = new MDObject[argumentsIdx.length];
	}
	
	/**
	 * Initializes the method descriptor in this attribute.
	 * @exception FunctionValueException if an error occurs while 
	 * initializing the method descriptor.
	 */
	public void initializeMethod() throws FunctionValueException {
		try {
			methodDesc.initializeMethod();
		}
		catch(MethodInitializeException e){
			throw new FunctionValueException
			("Unable to initialize the method descriptor.", e);
			
		}
	}
	
	
	/**
	 * Computes the value of this attribute based on the current tuple
	 * and iteration number. If the iteration number is the same as the
	 * one in the last call, then the cached value is returned.
	 * @exception FunctionValueException if an error occurs while evaluating the function value attribute.
	 */
	public MDObject compute(Tuple tuple) 
				throws FunctionValueException {
		DataVerify.VerifyObjectParam("tuple", tuple);
		int i=0, len = 0;
		MDObject[] tempArgs = null;
		
		tempArgs = arguments;
		len = argumentsIdx.length;
		try {
			// gather the arguments to the function from the argument tuple
			for (i=0; i < len; ++i){
				tempArgs[i] = tuple.getAttribute(argumentsIdx[i]);
			}
			return methodDesc.invokeMethod(tempArgs);
		}
		catch(MethodInvocationException e1){
			throw new FunctionValueException 
			("Error while evaluating function value.", e1);
		}
		catch(Exception e2){
			throw new FunctionValueException 
			("Unexpected error while evaluating function value.", e2);
		}
	}
	/**
	 * @return <code>true</code> if the 
	 * <code>FunctionValueAttribute</code>
	 * is initialized and ready
	 * to compute.
	 */
	public boolean isInitialized() {
		return methodDesc.isInitialized();
	} 

	/**
	 * Returns the index in the base tuple 
	 * of the object that results from the execution 
	 * of this operation.
	 * 
	 * @return The index in the base tuple 
	 * of the object that results from the execution 
	 * of this operation.
	 */
	public final int getBaseIdx() {
		return baseIdx;
	}
	/**
	 * @see edu.uprm.admg.nettraveler.schema.Attribute#localEquals(edu.uprm.admg.nettraveler.schema.Attribute)
	 */
	protected boolean localEquals(Attribute attr) {
		if(!(attr instanceof FunctionValueAttribute))
			return false;
		FunctionValueAttribute o = (FunctionValueAttribute) attr;
		return ((methodDesc.equals(o.methodDesc))
				&& (Arrays.equals(argumentsIdx, o.argumentsIdx)));
	}
	
	/**
	 * @see edu.uprm.admg.nettraveler.schema.Attribute#localHashCode()
	 */
	protected int localHashCode() {
		if(cachedHashCode == 0){
			cachedHashCode = 17;
			cachedHashCode += cachedHashCode * 37 + methodDesc.hashCode();
			int i;
			for(i=0;i<argumentsIdx.length;i++)
				cachedHashCode += cachedHashCode * 37 + argumentsIdx[i];
		}
		return cachedHashCode;
	}
	/**
	 * 
	 * @param in
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
		in.defaultReadObject();
		try{
			DataVerify.VerifyObjectParam("Arguments indexes", argumentsIdx);
			DataVerify.VerifyObjectParam("Method Descriptor", methodDesc);
			arguments = new MDObject[argumentsIdx.length];
		}catch(Exception e){
			throw new IOException(e.toString());
		}
		
	}
	
}


