package org.luaj.vm2.ast;

import java.util.List;

import org.luaj.vm2.LuaValue;
import org.luaj.vm2.ast.Exp.Constant;
import org.luaj.vm2.ast.Exp.NameExp;
import org.luaj.vm2.ast.Exp.VarExp;
import org.luaj.vm2.ast.Stat.Assign;
import org.luaj.vm2.ast.Stat.FuncDef;
import org.luaj.vm2.ast.Stat.GenericFor;
import org.luaj.vm2.ast.Stat.LocalAssign;
import org.luaj.vm2.ast.Stat.LocalFuncDef;
import org.luaj.vm2.ast.Stat.NumericFor;

/** 
 * Visitor that resolves names to scopes.
 * Each Name is resolved to a NamedVarible, possibly in a NameScope 
 * if it is a local, or in no named scope if it is a global. 
 */
public class NameResolver extends Visitor {

	private NameScope scope = null;
	private boolean injectMethodSelf;

	private void pushScope() {
		scope = new NameScope(scope);
	}
	private void popScope() {
		scope = scope.outerScope;
	}
	
	public void visit(NameScope scope) {
	}	

	public void visit(Block block) {
		pushScope();
		block.scope = scope;
		super.visit(block);
		popScope();
	}
	
	public void visit(FuncBody body) {
		pushScope();
		scope.functionNestingCount++;
		body.scope = scope;
		if (injectMethodSelf) {
			scope.define("self");
		}
		super.visit(body);
		popScope();
	}
	
	public void visit(LocalFuncDef stat) {
		defineLocalVar(stat.name);
		super.visit(stat);
	}

	public void visit(NumericFor stat) {
		pushScope();
		stat.scope = scope;
		defineLocalVar(stat.name);
		super.visit(stat);
		popScope();
	}

	public void visit(GenericFor stat) {
		pushScope();
		stat.scope = scope;
		defineLocalVars( stat.names );
		super.visit(stat);
		popScope();
	}

	public void visit(NameExp exp) {
		exp.name.variable = resolveNameReference(exp.name);
		super.visit(exp);
	}
	
	public void visit(FuncDef stat) {
		stat.name.name.variable = resolveNameReference(stat.name.name);
		stat.name.name.variable.hasassignments = true;
		boolean previousInjectMethodSelf = injectMethodSelf;
		injectMethodSelf = stat.name.method != null;
		try {
			super.visit(stat);
		} finally {
			injectMethodSelf = previousInjectMethodSelf;
		}
	}
	
	public void visit(Assign stat) {
		super.visit(stat);
		for ( int i=0, n=stat.vars.size(); i<n; i++ ) {
			VarExp v = (VarExp) stat.vars.get(i);
			v.markHasAssignment();
		}
	}

	public void visit(LocalAssign stat) {
		visitExps(stat.values);
		defineLocalVars( stat.names );
		int n = stat.names.size();
		int m = stat.values!=null? stat.values.size(): 0;
		boolean isvarlist = m>0 && m<n && ((Exp)stat.values.get(m-1)).isvarargexp();
		for ( int i=0; i<n && i<(isvarlist?m-1:m); i++ )
			if ( stat.values.get(i) instanceof Constant )
				((Name)stat.names.get(i)).variable.initialValue = ((Constant) stat.values.get(i)).value;
		if ( !isvarlist )
			for ( int i=m; i<n; i++ )
				((Name)stat.names.get(i)).variable.initialValue = LuaValue.NIL;
	}

	public void visit(ParList pars) {
		if ( pars.names != null )
			defineLocalVars(pars.names);
		if ( pars.isvararg )
			scope.define("arg");
		super.visit(pars);
	}
	
	protected void defineLocalVars(List<Name> names) {
		for ( int i=0, n=names.size(); i<n; i++ )
			defineLocalVar((Name) names.get(i));
	}

	protected void defineLocalVar(Name name) {
		name.variable = scope.define(name.name);
	}
	
	protected Variable resolveNameReference(Name name) {
		Variable v = scope.find(name.name);
		if ( v.isLocal() && scope.functionNestingCount != v.definingScope.functionNestingCount )
			v.isupvalue = true;
		return v;
	}
}
