/* valasymbolresolver.vala
 *
 * Copyright (C) 2006-2010  Jürg Billeter
 * Copyright (C) 2006-2008  Raffaele Sandrini
 *
 * This library 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 2.1 of the License, or (at your option) any later version.

 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 * 	Raffaele Sandrini <raffaele@sandrini.ch>
 */

using GLib;

/**
 * Code visitor resolving symbol names.
 */
public class Vala.SymbolResolver : CodeVisitor {
	Symbol root_symbol;
	Scope current_scope;

	/**
	 * Resolve symbol names in the specified code context.
	 *
	 * @param context a code context
	 */
	public void resolve (CodeContext context) {
		root_symbol = context.root;

		context.root.accept (this);

		root_symbol = null;
	}

	public override void visit_namespace (Namespace ns) {
		var old_scope = current_scope;
		current_scope = ns.scope;

		ns.accept_children (this);

		current_scope = old_scope;
	}

	public override void visit_class (Class cl) {
		if (cl.checked) {
			return;
		}

		current_scope = cl.scope;

		cl.accept_children (this);

		cl.base_class = null;
		foreach (DataType type in cl.get_base_types ()) {
			if (type.type_symbol is Class) {
				if (cl.base_class != null) {
					cl.error = true;
					Report.error (type.source_reference, "%s: Classes cannot have multiple base classes (`%s' and `%s')", cl.get_full_name (), cl.base_class.get_full_name (), type.type_symbol.get_full_name ());
					return;
				}
				cl.base_class = (Class) type.type_symbol;
				if (cl.base_class.is_subtype_of (cl)) {
					cl.error = true;
					Report.error (type.source_reference, "Base class cycle (`%s' and `%s')", cl.get_full_name (), cl.base_class.get_full_name ());
					return;
				}
			}
		}

		current_scope = current_scope.parent_scope;
	}

	public override void visit_struct (Struct st) {
		if (st.checked) {
			return;
		}

		current_scope = st.scope;

		st.accept_children (this);

		if (st.base_type != null) {
			var base_type = st.base_struct;
			if (base_type != null) {
				if (base_type.is_subtype_of (st)) {
					st.error = true;
					Report.error (st.source_reference, "Base struct cycle (`%s' and `%s')", st.get_full_name (), base_type.get_full_name ());
					return;
				}
			}
		}

		current_scope = current_scope.parent_scope;
	}

	public override void visit_interface (Interface iface) {
		if (iface.checked) {
			return;
		}

		current_scope = iface.scope;

		iface.accept_children (this);

		foreach (DataType type in iface.get_prerequisites ()) {
			if (type.type_symbol != null && type.type_symbol.is_subtype_of (iface)) {
				iface.error = true;
				Report.error (type.source_reference, "Prerequisite cycle (`%s' and `%s')", iface.get_full_name (), type.type_symbol.get_full_name ());
				return;
			}
		}

		current_scope = current_scope.parent_scope;
	}

	public override void visit_enum (Enum en) {
		if (en.checked) {
			return;
		}

		current_scope = en.scope;

		en.accept_children (this);

		current_scope = current_scope.parent_scope;
	}

	public override void visit_error_domain (ErrorDomain ed) {
		if (ed.checked) {
			return;
		}

		current_scope = ed.scope;

		ed.accept_children (this);

		current_scope = current_scope.parent_scope;
	}

	public override void visit_delegate (Delegate cb) {
		if (cb.checked) {
			return;
		}

		current_scope = cb.scope;

		cb.accept_children (this);

		current_scope = current_scope.parent_scope;
	}

	public override void visit_constant (Constant c) {
		if (c.checked) {
			return;
		}

		var old_scope = current_scope;
		if (!(c.parent_symbol is Block)) {
			// non-local constant
			current_scope = c.scope;
		}

		c.accept_children (this);

		current_scope = old_scope;
	}

	public override void visit_field (Field f) {
		if (f.checked) {
			return;
		}

		current_scope = f.scope;

		f.accept_children (this);

		current_scope = current_scope.parent_scope;
	}

	public override void visit_method (Method m) {
		if (m.checked) {
			return;
		}

		current_scope = m.scope;

		m.accept_children (this);

		current_scope = current_scope.parent_scope;
	}

	public override void visit_creation_method (CreationMethod m) {
		if (m.checked) {
			return;
		}
		m.accept_children (this);
	}

	public override void visit_formal_parameter (Parameter p) {
		if (p.checked) {
			return;
		}
		p.accept_children (this);
	}

	public override void visit_property (Property prop) {
		if (prop.checked) {
			return;
		}
		prop.accept_children (this);
	}

	public override void visit_property_accessor (PropertyAccessor acc) {
		if (acc.checked) {
			return;
		}
		acc.accept_children (this);
	}

	public override void visit_signal (Signal sig) {
		if (sig.checked) {
			return;
		}
		sig.accept_children (this);
	}

	public override void visit_constructor (Constructor c) {
		if (c.checked) {
			return;
		}
		c.accept_children (this);
	}

	public override void visit_destructor (Destructor d) {
		if (d.checked) {
			return;
		}
		d.accept_children (this);
	}

	public override void visit_block (Block b) {
		if (b.checked) {
			return;
		}
		b.accept_children (this);
	}

	public override void visit_using_directive (UsingDirective ns) {
		var unresolved_symbol = ns.namespace_symbol as UnresolvedSymbol;
		if (unresolved_symbol != null) {
			ns.namespace_symbol = resolve_symbol (unresolved_symbol);
			if (!(ns.namespace_symbol is Namespace)) {
				ns.error = true;
				Report.error (ns.source_reference, "The namespace name `%s' could not be found", unresolved_symbol.to_string ());
				return;
			}
		}
	}

	private Symbol? resolve_symbol (UnresolvedSymbol unresolved_symbol) {
		if (unresolved_symbol.qualified) {
			// qualified access to global symbol
			return root_symbol.scope.lookup (unresolved_symbol.name);
		} else if (unresolved_symbol.inner == null) {
			Symbol sym = null;
			Scope scope = current_scope;
			while (sym == null && scope != null) {
				sym = scope.lookup (unresolved_symbol.name);

				// only look for types and type containers
				if (!(sym is Namespace || sym is TypeSymbol)) {
					sym = null;
				}

				scope = scope.parent_scope;
			}
			// Look for matches in inner types of base-types/prerequisites
			ObjectTypeSymbol? current_symbol = null;
			if (sym == null) {
				scope = current_scope;
				while (scope != null) {
					if (scope.owner is ObjectTypeSymbol) {
						current_symbol = (ObjectTypeSymbol) scope.owner;
						break;
					}
					scope = scope.parent_scope;
				}
			}
			if (current_symbol != null) {
				unowned List<DataType> types;
				if (current_symbol is Class) {
					types = ((Class) current_symbol).get_base_types ();
				} else if (current_symbol is Interface) {
					types = ((Interface) current_symbol).get_prerequisites ();
				} else {
					assert_not_reached ();
				}
				foreach (DataType type in types) {
					if (type.type_symbol == null) {
						continue;
					}

					var local_sym = SemanticAnalyzer.symbol_lookup_inherited (type.type_symbol, unresolved_symbol.name);

					// only look for types and type containers
					if (!(local_sym is Namespace || local_sym is TypeSymbol)) {
						local_sym = null;
					}

					if (local_sym != null && local_sym.access == SymbolAccessibility.PUBLIC) {
						if (sym != null && sym != local_sym) {
							unresolved_symbol.error = true;
							Report.error (unresolved_symbol.source_reference, "`%s' is an ambiguous reference between `%s' and `%s'", unresolved_symbol.name, sym.get_full_name (), local_sym.get_full_name ());
							return null;
						}
						sym = local_sym;
					}
				}
			}
			if (sym == null && unresolved_symbol.source_reference != null) {
				foreach (UsingDirective ns in unresolved_symbol.source_reference.using_directives) {
					if (ns.error || ns.namespace_symbol is UnresolvedSymbol) {
						continue;
					}

					var local_sym = ns.namespace_symbol.scope.lookup (unresolved_symbol.name);

					// only look for types and type containers
					if (!(local_sym is Namespace || local_sym is TypeSymbol)) {
						local_sym = null;
					}

					if (local_sym != null) {
						if (sym != null && sym != local_sym) {
							unresolved_symbol.error = true;
							Report.error (unresolved_symbol.source_reference, "`%s' is an ambiguous reference between `%s' and `%s'", unresolved_symbol.name, sym.get_full_name (), local_sym.get_full_name ());
							return null;
						}
						sym = local_sym;
					}
				}
			}

			return sym;
		} else {
			var parent_symbol = resolve_symbol (unresolved_symbol.inner);
			if (parent_symbol == null) {
				unresolved_symbol.error = true;
				Report.error (unresolved_symbol.inner.source_reference, "The symbol `%s' could not be found", unresolved_symbol.inner.name);
				return null;
			}
			parent_symbol.used = true;

			return parent_symbol.scope.lookup (unresolved_symbol.name);
		}
	}

	bool has_base_struct_cycle (Struct st, Struct loop_st) {
		if (!(st.base_type is UnresolvedType)) {
			return false;
		}

		var sym = resolve_symbol (((UnresolvedType) st.base_type).unresolved_symbol);
		unowned Struct? base_struct = sym as Struct;
		if (base_struct == null) {
			return false;
		}

		if (base_struct == loop_st) {
			return true;
		}

		return has_base_struct_cycle (base_struct, loop_st);
	}

	DataType get_type_for_struct (Struct st, Struct base_struct, SourceReference? source_reference) {
		if (st.base_type != null) {
			// make sure that base type is resolved

			if (has_base_struct_cycle (st, st)) {
				// recursive declaration in base type
				return new StructValueType (st, source_reference);
			}

			if (current_scope == st.scope) {
				// recursive declaration in generic base type
				return new StructValueType (st, source_reference);
			}

			var old_scope = current_scope;
			current_scope = st.scope;

			st.base_type.accept (this);

			current_scope = old_scope;
		}

		if (base_struct.base_struct != null) {
			return get_type_for_struct (st, base_struct.base_struct, source_reference);
		}

		// attributes are not processed yet, access them directly
		if (base_struct.has_attribute ("BooleanType")) {
			return new BooleanType (st, source_reference);
		} else if (base_struct.has_attribute ("IntegerType")) {
			return new IntegerType (st, null, null, source_reference);
		} else if (base_struct.has_attribute ("FloatingType")) {
			return new FloatingType (st, source_reference);
		} else {
			return new StructValueType (st, source_reference);
		}
	}

	private DataType resolve_type (UnresolvedType unresolved_type) {
		DataType type = null;

		// still required for vapigen
		if (unresolved_type.unresolved_symbol.name == "void") {
			return new VoidType (unresolved_type.source_reference);
		}

		var sym = resolve_symbol (unresolved_type.unresolved_symbol);
		if (sym == null) {
			// don't report same error twice
			if (!unresolved_type.unresolved_symbol.error) {
				Report.error (unresolved_type.source_reference, "The type name `%s' could not be found", unresolved_type.unresolved_symbol.to_string ());
			}
			return new InvalidType ();
		}

		if (sym is TypeSymbol) {
			if (sym is Delegate) {
				type = new DelegateType ((Delegate) sym, unresolved_type.source_reference);
			} else if (sym is Class) {
				unowned Class cl = (Class) sym;
				if (cl.is_error_base) {
					type = new ErrorType (null, null, unresolved_type.source_reference);
				} else {
					type = new ObjectType (cl, unresolved_type.source_reference);
				}
			} else if (sym is Interface) {
				type = new ObjectType ((Interface) sym, unresolved_type.source_reference);
			} else if (sym is Struct) {
				type = get_type_for_struct ((Struct) sym, (Struct) sym, unresolved_type.source_reference);
			} else if (sym is Enum) {
				type = new EnumValueType ((Enum) sym, unresolved_type.source_reference);
			} else if (sym is ErrorDomain) {
				type = new ErrorType ((ErrorDomain) sym, null, unresolved_type.source_reference);
			} else if (sym is ErrorCode) {
				type = new ErrorType ((ErrorDomain) sym.parent_symbol, (ErrorCode) sym, unresolved_type.source_reference);
			} else 	if (sym is TypeParameter) {
				type = new GenericType ((TypeParameter) sym, unresolved_type.source_reference);
			} else {
				Report.error (unresolved_type.source_reference, "internal error: `%s' is not a supported type", sym.get_full_name ());
				return new InvalidType ();
			}
		} else {
			Report.error (unresolved_type.source_reference, "`%s' is not a type", sym.get_full_name ());
			return new InvalidType ();
		}

		type.source_reference = unresolved_type.source_reference;
		type.value_owned = unresolved_type.value_owned;
		sym.used = true;

		if (type is GenericType) {
			// type parameters are always considered nullable
			// actual type argument may or may not be nullable
			type.nullable = true;
		} else {
			type.nullable = unresolved_type.nullable;
		}

		type.is_dynamic = unresolved_type.is_dynamic;
		foreach (DataType type_arg in unresolved_type.get_type_arguments ()) {
			type.add_type_argument (type_arg);
		}

		return type;
	}

	public override void visit_data_type (DataType data_type) {
		data_type.accept_children (this);

		if (!(data_type is UnresolvedType)) {
			return;
		}

		var unresolved_type = (UnresolvedType) data_type;

		unresolved_type.parent_node.replace_type (unresolved_type, resolve_type (unresolved_type));
	}

	public override void visit_declaration_statement (DeclarationStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_local_variable (LocalVariable local) {
		if (local.checked) {
			return;
		}
		local.accept_children (this);
	}

	public override void visit_initializer_list (InitializerList list) {
		if (list.checked) {
			return;
		}
		list.accept_children (this);
	}

	public override void visit_with_statement (WithStatement stmt) {
		stmt.accept_children (this);
	}

	public override void visit_expression_statement (ExpressionStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_if_statement (IfStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_switch_statement (SwitchStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_switch_section (SwitchSection section) {
		if (section.checked) {
			return;
		}
		section.accept_children (this);
	}

	public override void visit_switch_label (SwitchLabel label) {
		if (label.checked) {
			return;
		}
		label.accept_children (this);
	}

	public override void visit_loop_statement (LoopStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_while_statement (WhileStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_do_statement (DoStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_for_statement (ForStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_foreach_statement (ForeachStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_return_statement (ReturnStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_yield_statement (YieldStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_throw_statement (ThrowStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_try_statement (TryStatement stmt) {
		if (stmt.checked) {
			return;
		}
		stmt.accept_children (this);
	}

	public override void visit_delete_statement (DeleteStatement stmt) {
		stmt.accept_children (this);
	}

	public override void visit_catch_clause (CatchClause clause) {
		if (clause.checked) {
			return;
		}
		clause.accept_children (this);
	}

	public override void visit_array_creation_expression (ArrayCreationExpression e) {
		if (e.checked) {
			return;
		}
		e.accept_children (this);
	}

	public override void visit_template (Template tmpl) {
		if (tmpl.checked) {
			return;
		}
		tmpl.accept_children (this);
	}

	public override void visit_tuple (Tuple tuple) {
		if (tuple.checked) {
			return;
		}
		tuple.accept_children (this);
	}

	public override void visit_member_access (MemberAccess expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_method_call (MethodCall expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_element_access (ElementAccess expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_slice_expression (SliceExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_postfix_expression (PostfixExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_object_creation_expression (ObjectCreationExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_sizeof_expression (SizeofExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_typeof_expression (TypeofExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_unary_expression (UnaryExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_cast_expression (CastExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_named_argument (NamedArgument expr) {
		expr.accept_children (this);
	}

	public override void visit_pointer_indirection (PointerIndirection expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_addressof_expression (AddressofExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_reference_transfer_expression (ReferenceTransferExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_binary_expression (BinaryExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_type_check (TypeCheck expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_conditional_expression (ConditionalExpression expr) {
		if (expr.checked) {
			return;
		}
		expr.accept_children (this);
	}

	public override void visit_lambda_expression (LambdaExpression l) {
		if (l.checked) {
			return;
		}
		l.accept_children (this);
	}

	public override void visit_assignment (Assignment a) {
		if (a.checked) {
			return;
		}
		a.accept_children (this);
	}
}
