// $Id: Var.cc,v 1.44 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include "Var.h"
#include "Func.h"
#include "Stmt.h"
#include "Scope.h"
#include "Serializer.h"
#include "EventRegistry.h"

// Reduces an aggregate type.
static BroType* reduce_type(BroType* t)
	{
	if ( t->Tag() == TYPE_LIST )
		return flatten_type(t);

	else if ( t->IsSet() )
		{
		TypeList* tl = t->AsTableType()->Indices();
		if ( tl->Types()->length() == 1 )
			return (*tl->Types())[0];
		else
			return tl;
		}

	else
		return t;
	}

static BroType* init_type(Expr* init)
	{
	if ( init->Tag() != EXPR_LIST )
		{
		BroType* t = init->InitType();
		if ( ! t )
			return 0;

		if ( t->Tag() == TYPE_LIST &&
		     t->AsTypeList()->Types()->length() != 1 )
			{
			init->Error("list used in scalar initialization");
			Unref(t);
			return 0;
			}

		return t;
		}

	ListExpr* init_list = init->AsListExpr();
	const expr_list& el = init_list->Exprs();

	if ( el.length() == 0 )
		{
		init->Error("empty list in untyped initialization");
		return 0;
		}

	// Could be a record, a set, or a list of table elements.
	Expr* e0 = el[0];
	if ( e0->IsRecordElement(0) )
		// ListExpr's know how to build a record from their
		// components.
		return init_list->InitType();

	BroType* t = e0->InitType();
	if ( t )
		t = reduce_type(t);
	if ( ! t )
		return 0;

	for ( int i = 1; t && i < el.length(); ++i )
		{
		BroType* el_t = el[i]->InitType();
		BroType* ti = el_t ? reduce_type(el_t) : 0;
		if ( ! ti )
			{
			Unref(t);
			return 0;
			}

		if ( same_type(t, ti) )
			{
			Unref(ti);
			continue;
			}

		BroType* t_merge = merge_types(t, ti);
		Unref(t);
		Unref(ti);
		t = t_merge;
		}

	if ( ! t )
		return 0;

	if ( t->Tag() == TYPE_TABLE && ! t->AsTableType()->IsSet() )
		// A list of table elements.
		return t;

	// A set.  If the index type isn't yet a type list, make
	// it one, as that's what's required for creating a set type.
	if ( t->Tag() != TYPE_LIST )
		{
		TypeList* tl = new TypeList(t);
		tl->Append(t);
		t = tl;
		}

	return new SetType(t->AsTypeList(), 0);
	}

static Val* init_val(Expr* init, const BroType* t, Val* aggr)
	{
	return init->InitVal(t, aggr);
	}

static void make_var(ID* id, BroType* t, init_class c, Expr* init,
			attr_list* attr, decl_type dt, int do_init)
	{
	if ( id->Type() )
		{
		if ( id->IsRedefinable() || (! init && attr) )
			{
			BroObj* redef_obj = init ? (BroObj*) init : (BroObj*) t;
			if ( dt != VAR_REDEF )
				id->Warn("redefinition requires \"redef\"", redef_obj, 1);
			}

		else if ( dt != VAR_REDEF || init || ! attr )
			{
			id->Error("already defined", init);
			return;
			}
		}

	if ( dt == VAR_REDEF )
		{
		if ( ! id->Type() )
			{
			id->Error("\"redef\" used but not previously defined");
			return;
			}

		if ( ! t )
			t = id->Type();
		}

	if ( id->Type() && id->Type()->Tag() != TYPE_ERROR )
		{
		if ( dt != VAR_REDEF &&
		     (! init || ! do_init || (! t && ! (t = init_type(init)))) )
			{
			id->Error("already defined", init);
			return;
			}

		// Allow redeclaration in order to initialize.
		if ( ! same_type(t, id->Type()) )
			{
			id->Error("redefinition changes type", init);
			return;
			}
		}

	if ( init && optimize )
		init = init->Simplify(SIMPLIFY_GENERAL);

	if ( t && t->IsSet() )
		{ // Check for set with explicit elements.
		SetType* st = t->AsTableType()->AsSetType();
		ListExpr* elements = st->SetElements();

		if ( elements )
			{
			if ( init )
				{
				id->Error("double initialization", init);
				return;
				}

			Ref(elements);
			init = elements;
			}
		}

	if ( ! t )
		{ // Take type from initialization.
		if ( ! init )
			{
			id->Error("no type given");
			return;
			}

		t = init_type(init);
		if ( ! t )
			{
			id->SetType(error_type());
			return;
			}
		}
	else
		Ref(t);

	id->SetType(t);

	if ( attr )
		id->AddAttrs(new Attributes(attr, t));

	if ( id->FindAttr(ATTR_PERSISTENT) )
		{
		if ( dt == VAR_CONST )
			{
			id->Error("persistant with constant");
			return;
			}

		if ( ! id->IsGlobal() )
			{
			id->Error("persistant with non-global");
			return;
			}

		persistence_serializer->RegisterID(id);
		}

	if ( do_init )
		{
		if ( (c == INIT_EXTRA && id->FindAttr(ATTR_ADD_FUNC)) ||
		     (c == INIT_REMOVE && id->FindAttr(ATTR_DEL_FUNC)) )
			// Just apply the function.
			id->SetVal(init, c);

		else if ( dt != VAR_REDEF || init || ! attr )
			{
			Val* aggr;
			if ( t->Tag() == TYPE_RECORD )
				aggr = new RecordVal(t->AsRecordType());

			else if ( t->Tag() == TYPE_TABLE )
				aggr = new TableVal(t->AsTableType(), id->Attrs());

			else
				aggr = 0;

			Val* v = 0;
			if ( init )
				{
				v = init_val(init, t, aggr);
				if ( ! v )
					return;
				}

			if ( aggr )
				id->SetVal(aggr, c);
			else if ( v )
				id->SetVal(v, c);
			}
		}

	if ( dt == VAR_CONST )
		{
		if ( ! init && ! id->IsRedefinable() )
			id->Error("const variable must be initialized");

		id->SetConst();
		}
	}


void add_global(ID* id, BroType* t, init_class c, Expr* init,
		attr_list* attr, decl_type dt)
	{
	make_var(id, t, c, init, attr, dt, 1);
	}

Stmt* add_local(ID* id, BroType* t, init_class c, Expr* init,
		attr_list* attr, decl_type dt)
	{
	make_var(id, t, c, init, attr, dt, 0);

	if ( init )
		{
		if ( c != INIT_FULL )
			id->Error("can't use += / -= for initializations of local variables");

		Ref(id);

		Stmt* stmt =
			new ExprStmt(new AssignExpr(new NameExpr(id), init, 0));
		stmt->SetLocationInfo(init->GetLocationInfo());

		return stmt;
		}

	else
		{
		if ( t->Tag() == TYPE_RECORD || t->Tag() == TYPE_TABLE )
			current_scope()->AddInit(id);

		return new NullStmt;
		}
	}

void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */)
	{
	id->SetType(t);
	id->MakeType();

	if ( attr )
		id->SetAttrs(new Attributes(attr, t));
	}

void begin_func(ID* id, const char* module_name, function_flavor flavor,
		int is_redef, FuncType* t)
	{
	if ( flavor == FUNC_FLAVOR_EVENT )
		{
		const BroType* yt = t->YieldType();

		if ( yt && yt->Tag() != TYPE_VOID )
			id->Error("event cannot yield a value", t);

		t->ClearYieldType();
		}

	if ( id->Type() )
		{
		if ( ! same_type(id->Type(), t) )
			id->Type()->Error("incompatible types", t);
		}

	else if ( is_redef )
		id->Error("redef of not-previously-declared value");

	if ( id->HasVal() )
		{
		int id_is_event = id->ID_Val()->AsFunc()->IsEvent();

		if ( id_is_event != (flavor == FUNC_FLAVOR_EVENT) )
			id->Error("inconsistency between event and function", t);
		if ( id_is_event )
			{
			if ( is_redef )
				// Clear out value so it will be replaced.
				id->SetVal(0);
			}
		else
			{
			if ( ! id->IsRedefinable() )
				id->Error("already defined");
			}
		}
	else
		id->SetType(t);

	push_scope(id);

	RecordType* args = t->Args();
	int num_args = args->NumFields();
	for ( int i = 0; i < num_args; ++i )
		{
		TypeDecl* arg_i = args->FieldDecl(i);
		ID* arg_id = lookup_ID(arg_i->id, module_name);

		if ( arg_id && ! arg_id->IsGlobal() )
			arg_id->Error("argument name used twice");

		arg_id = install_ID(copy_string(arg_i->id), module_name,
					false, false);
		arg_id->SetType(arg_i->type->Ref());
		}
	}

void end_func(Stmt* body)
	{
	int frame_size = current_scope()->Length();
	id_list* inits = current_scope()->GetInits();

	Scope* scope = pop_scope();
	ID* id = scope->ScopeID();

	if ( id->HasVal() )
		id->ID_Val()->AsFunc()->AddBody(body, inits, frame_size);
	else
		{
		Func* f = new BroFunc(id, body, inits, frame_size);
		id->SetVal(new Val(f));
		id->SetConst();
		}

	id->ID_Val()->AsFunc()->SetScope(scope);
	}

Val* internal_val(const char* name)
	{
	ID* id = lookup_ID(name, GLOBAL_MODULE_NAME);
	if ( ! id )
		internal_error("internal variable %s missing", name);

	return id->ID_Val();
	}

Val* opt_internal_val(const char* name)
	{
	ID* id = lookup_ID(name, GLOBAL_MODULE_NAME);
	return id ? id->ID_Val() : 0;
	}

double opt_internal_double(const char* name)
	{
	Val* v = opt_internal_val(name);
	return v ? v->InternalDouble() : 0.0;
	}

int opt_internal_int(const char* name)
	{
	Val* v = opt_internal_val(name);
	return v ? v->InternalInt() : 0;
	}

ListVal* internal_list_val(const char* name)
	{
	ID* id = lookup_ID(name, GLOBAL_MODULE_NAME);
	if ( ! id )
		return 0;

	Val* v = id->ID_Val();
	if ( v )
		{
		if ( v->Type()->Tag() == TYPE_LIST )
			return (ListVal*) v;

		else if ( v->Type()->IsSet() )
			{
			TableVal* tv = v->AsTableVal();
			ListVal* lv = tv->ConvertToPureList();
			return lv;
			}

		else
			internal_error("internal variable %s is not a list", name);
		}

	return 0;
	}

BroType* internal_type(const char* name)
	{
	ID* id = lookup_ID(name, GLOBAL_MODULE_NAME);
	if ( ! id )
		internal_error("internal type %s missing", name);

	return id->Type();
	}

Func* internal_func(const char* name)
	{
	Val* v = internal_val(name);
	if ( v )
		return v->AsFunc();
	else
		return 0;
	}

EventHandlerPtr internal_handler(const char* name)
	{
	// If there already is an entry in the registry, we have a
	// local handler on the script layer.
	EventHandler* h = event_registry->Lookup(name);
	if ( h )
		return h;

	h = new EventHandler(name);
	event_registry->Register(h);

	return h;
	}
