/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.awk.expr;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import net.morilib.awk.AwkCastException;
import net.morilib.awk.AwkLocation;
import net.morilib.awk.AwkUnboundException;
import net.morilib.awk.io.AwkFiles;
import net.morilib.awk.namespace.AwkNamespace;
import net.morilib.awk.value.AwkClass;
import net.morilib.awk.value.AwkFunction;
import net.morilib.awk.value.AwkUndefined;
import net.morilib.awk.value.AwkValue;

public class AwkApplier extends AwkExpression {

	private AwkExpression function;
	private AwkExpression[] args;

	/**
	 * 
	 * @param function
	 * @param args
	 */
	public AwkApplier(AwkExpression function,
			Collection<AwkExpression> args) {
		this.function = function;
		this.args     = args.toArray(new AwkExpression[0]);
	}

	@Override
	public AwkValue eval(AwkNamespace s, AwkFiles o) {
		List<AwkValue> l = new ArrayList<AwkValue>();
		AwkNamespace t, v;
		AwkLocation m;
		AwkFunction g;
		AwkValue fn, f, w;

		for(AwkExpression e : args)  l.add(e.eval(s, o));
		if(function instanceof AwkReferVariable) {
			m = ((AwkReferVariable)function).getLocation();
			t = m.isRoot() ? s.getRoot() : s;
			for(int i = 0; i < m.sizeOfPath() - 1; i++) {
				if((v = t.referNamespace(m.getPathOf(i))) != null) {
					t = v;
				} else if((w = t.find(m.getPathOf(i))) != null &&
						w.getNamespace() != null) {
					t = w.getNamespace();
				} else {
					return AwkUndefined.UNDEF;
				}
			}
			f = t.findFunction(m.getName());
			if(f == null)  f = t.find(m.getName());
			if(f == null) {
				throw new AwkUnboundException(
						"unbound function " + m.toString());
			} else if(!(f instanceof AwkFunction)) {
				throw new AwkCastException("function required");
			}

			g = (AwkFunction)f;
			if(g.getName().equals("new") && !t.isRoot()) {
				t = t.newInstance();
				g.init(t, o, l);
				return new AwkClass(t);
			} else {
				return g.apply(t, o, l);
			}
		} else if((fn = function.eval(s, o)) instanceof AwkFunction) {
			g = (AwkFunction)fn;
			return g.apply(g.getEnvironment(), o, l);
		} else {
			throw new AwkCastException("function required");
		}
	}

}
