//Mudar o for do iterator para o novo for.
package plp.functional1.expression;

import plp.functional1.memory.*;
import plp.functional1.util.DefFuncao;
import plp.functional1.util.Tipo;
import plp.functional1.util.TipoFuncao;

import java.util.*;

import static java.util.Arrays.asList;
import static plp.functional1.util.ToStringProvider.listToString;

public class Aplicacao implements Expressao {

	private Id func;
	private List<? extends Expressao> argsExpressao;

	public Aplicacao(Id f, Expressao... expressoes) {
		this(f, asList(expressoes));
	}

	public Aplicacao(Id f, List<? extends Expressao> expressoes) {
		func = f;
		argsExpressao = expressoes;
	}

	public Valor avaliar(AmbienteExecucao ambiente)
			throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException {
		AmbienteExecucaoFuncional ambienteFuncional = (AmbienteExecucaoFuncional) ambiente;

		DefFuncao funcao;
		try {
			funcao = ambienteFuncional.getFuncao(func);
		} catch (IdentificadorNaoDeclaradoException e) {
			throw new VariavelJaDeclaradaException(func);
		}

		Map<Id, Valor> mapIdValor = resolveParametersBindings(ambiente, funcao);

		ambiente.incrementa();

		includeValueBindings(ambiente, mapIdValor);

		Valor vresult = funcao.getExp().avaliar(ambiente);
		ambiente.restaura();
		return vresult;
	}

	/**
	 * Realiza a verificacao de tipos desta expressao.
	 * 
	 * @param ambiente
	 *            o ambiente de compila��o.
	 * @return <code>true</code> se os tipos da expressao sao validos;
	 *         <code>false</code> caso contrario.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador nao declarado no ambiente.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador declarado mais de uma vez no
	 *                mesmo bloco do ambiente.
	 */
	public boolean checaTipo(AmbienteCompilacao ambiente)
			throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException {
		boolean result;
		Tipo aux = ambiente.get(func);

		if (aux instanceof TipoFuncao) {
			TipoFuncao tipoFuncao = (TipoFuncao) aux;

			result = tipoFuncao.checaTipo(ambiente, argsExpressao);
		} else {
			// A fun��o func n�o foi declarada.

			// TODO: lan�ar uma exce��o ou separar vari�veis de fun��es no
			// contexto de compila��o.

			result = false;
		}
		return result;
	}

	/**
	 * Returns the args.
	 * 
	 * @return ListaExpressao
	 */
	public List<? extends Expressao> getArgsExpressao() {
		return argsExpressao;
	}

	/**
	 * Returns the func.
	 * 
	 * @return Id
	 */
	public Id getFunc() {
		return func;
	}

	/**
	 * Retorna os tipos possiveis desta expressao.
	 * 
	 * @param ambiente
	 *            o ambiente de compila��o.
	 * @return os tipos possiveis desta expressao.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador nao declarado no ambiente.
	 * @exception VariavelNaoDeclaradaException
	 *                se existir um identificador declarado mais de uma vez no
	 *                mesmo bloco do ambiente.
	 * @precondition this.checaTipo();
	 */
	public Tipo getTipo(AmbienteCompilacao ambiente)
			throws VariavelNaoDeclaradaException, VariavelJaDeclaradaException {
		TipoFuncao tipoFuncao = (TipoFuncao) ambiente.get(func);
		return tipoFuncao.getTipo(ambiente, argsExpressao);
	}

	private void includeValueBindings(AmbienteExecucao ambiente,
			Map<Id, Valor> mapIdValor) throws VariavelJaDeclaradaException {
		for (Map.Entry<Id, Valor> mapeamento : mapIdValor.entrySet()) {
			Id id = mapeamento.getKey();
			Valor valor = mapeamento.getValue();
			ambiente.map(id, valor);
		}
	}

	private Map<Id, Valor> resolveParametersBindings(AmbienteExecucao ambiente,
			DefFuncao funcao) throws VariavelNaoDeclaradaException,
			VariavelJaDeclaradaException {
		List<ArgumentoFuncao> parametrosId = funcao.getListaId();
		List<? extends Expressao> expressoesValorReal = argsExpressao;

		Map<Id, Valor> mapIdValor = new HashMap<Id, Valor>();

		Iterator<? extends Expressao> iterExpressoesValor = expressoesValorReal
				.iterator();
		//TODO: Se parametro for lazy, não avaliar, salvar toda a expressao no map
		for (ArgumentoFuncao arg : parametrosId) {
			Expressao exp = iterExpressoesValor.next();
			Valor valorReal = exp.avaliar(ambiente);
			mapIdValor.put(arg.getId(), valorReal);
		}
		return mapIdValor;
	}

	/**
	 * Retorna uma representacao String desta expressao. Util para depuracao.
	 * 
	 * @return uma representacao String desta expressao.
	 */
	@Override
	public String toString() {
		return String.format("%s(%s)", func, listToString(argsExpressao, ","));
	}

	public Expressao reduzir(AmbienteExecucao ambiente) {
		// TODO Auto-generated method stub
		return null;
	}
	
	public Aplicacao clone() {
		Aplicacao retorno;
		ArrayList<Expressao> novaLista = new ArrayList<Expressao>(this.argsExpressao.size());

		Iterator<? extends Expressao> iterator = argsExpressao.iterator();
		while (iterator.hasNext()){
			Expressao exp = iterator.next();
			novaLista.add(exp.clone());			
		}
		
		retorno = new Aplicacao(this.func.clone(), novaLista);
		
		return retorno;
	}
}

@@ -1049,37 +1049,41 @@
 %0A%09%09%7D%0A%0A%09%09Map%3CId, 
-Valor
+Expressao
 %3E mapIdValor = r
@@ -3373,37 +3373,41 @@
 nte,%0A%09%09%09Map%3CId, 
-Valor
+Expressao
 %3E mapIdValor) th
@@ -3459,29 +3459,33 @@
 p.Entry%3CId, 
-Valor
+Expressao
 %3E mapeamento
@@ -3547,27 +3547,35 @@
 y();%0A%09%09%09
-Valor valor
+Expressao expressao
  = mapea
@@ -3612,21 +3612,25 @@
 map(id, 
-valor
+expressao
 );%0A%09%09%7D%0A%09
@@ -3649,21 +3649,25 @@
 Map%3CId, 
-Valor
+Expressao
 %3E resolv
@@ -3942,21 +3942,25 @@
 Map%3CId, 
-Valor
+Expressao
 %3E mapIdV
@@ -3982,17 +3982,8 @@
 Map%3C
-Id, Valor
 %3E();

Change Ambient Map to <Id, Expressao> instead of <Id, Valor> #3 (#9)