/* Copyright 2009-2013 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.moeaframework.util.tree;

/**
 * The node for defining a callable, named function.  Once a named function is
 * defined, it can be invoked using {@link Call}.  The behavior (body) of this
 * function can be mutated.  For immutable functions, see {@link Lambda}.
 * 
 * @see Call
 */
public class Define extends Node {
	
	/**
	 * The name of this function.
	 */
	private final String functionName;
	
	/**
	 * The names of the arguments to this function.
	 */
	private final String[] variableNames;
	
	/**
	 * The types of the arguments to this function.
	 */
	private final Class<?>[] variableTypes;
	
	/**
	 * Constructs a new node for defining a function that takes no arguments.
	 * 
	 * @param functionName the name of the function
	 * @param returnType the return type of the function
	 */
	public Define(String functionName, Class<?> returnType) {
		this(functionName, returnType, new String[0], new Class<?>[0]);
	}
	
	/**
	 * Constructs a new node for defining a function that takes one argument.
	 * 
	 * @param functionName the name of the function
	 * @param returnType the return type of the function
	 * @param variableName the name of the argument
	 * @param variableType the type of the argument
	 */
	public Define(String functionName, Class<?> returnType, String variableName,
			Class<?> variableType) {
		this(functionName, returnType, new String[] { variableName },
				new Class<?>[] { variableType });
	}
	
	/**
	 * Constructs a new node for defining a function that takes two arguments.
	 * 
	 * @param functionName the name of the function
	 * @param returnType the return type of the function
	 * @param name1 the name of the first argument
	 * @param type1 the type of the first argument
	 * @param name2 the name of the second argument
	 * @param type2 the type of the second argument
	 */
	public Define(String functionName, Class<?> returnType, String name1,
			Class<?> type1, String name2, Class<?> type2) {
		this(functionName, returnType, new String[] { name1, name2 },
				new Class<?>[] { type1, type2 });
	}
	
	/**
	 * Constructs a new node for defining a function that takes a
	 * user-defined number of arguments.
	 * 
	 * @param functionName the name of the function
	 * @param returnType the return type of the function
	 * @param variableNames the names of the arguments to the function
	 * @param variableTypes the types of the arguments to the function
	 */
	public Define(String functionName, Class<?> returnType,
			String[] variableNames, Class<?>[] variableTypes) {
		super(Void.class, returnType);
		this.functionName = functionName;
		this.variableNames = variableNames;
		this.variableTypes = variableTypes;
	}

	/**
	 * Returns the name of this function.
	 * 
	 * @return the name of this function
	 */
	public String getFunctionName() {
		return functionName;
	}

	/**
	 * Returns the names of the arguments to this function.
	 * 
	 * @return the names of the arguments to this function
	 */
	public String[] getVariableNames() {
		return variableNames;
	}

	/**
	 * Returns the types of the arguments to this function.
	 * 
	 * @return the types of the arguments to this function
	 */
	public Class<?>[] getVariableTypes() {
		return variableTypes;
	}

	@Override
	public Node copyNode() {
		return new Define(functionName, getArgumentType(0), variableNames,
				variableTypes);
	}

	@Override
	public Void evaluate(Environment environment) {
		environment.set(functionName, getArgument(0));
		return null;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append('(');
		sb.append(getClass().getSimpleName());
		sb.append(' ');
		sb.append(getFunctionName());
		sb.append(" (");
		
		for (int i = 0; i < variableNames.length; i++) {
			if (i > 0) {
				sb.append(' ');
			}
			
			sb.append(variableNames[i]);
		}
		
		sb.append(") ");
		sb.append(getArgument(0).toString());
		sb.append(')');
		return sb.toString();
	}

}
