// $Id: Type.h,v 1.49 2003/09/01 23:42:40 vern Exp $
//
// Copyright (c) 1995, 1996, 1997, 1998, 1999, 2001, 2002, 2003
//      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.

#ifndef type_h
#define type_h

#include <string>
#include <map>

#include "Obj.h"
#include "Attr.h"
#include "BroList.h"
#include "Dict.h"

// BRO types.

typedef enum {
	TYPE_VOID,
	TYPE_BOOL, TYPE_INT, TYPE_COUNT, TYPE_COUNTER, TYPE_DOUBLE,
	TYPE_TIME, TYPE_INTERVAL,
	TYPE_STRING, TYPE_PATTERN,
	TYPE_ENUM,
	TYPE_TIMER,
	TYPE_PORT, TYPE_ADDR, TYPE_NET, TYPE_SUBNET,
	TYPE_ANY,
	TYPE_TABLE,
	TYPE_UNION,
	TYPE_RECORD,
	TYPE_SET,
	TYPE_LIST,
	TYPE_FUNC,
	TYPE_FILE,
	TYPE_ERROR
#define NUM_TYPES (int(TYPE_ERROR) + 1)
} TypeTag;

typedef enum { FUNC_FLAVOR_FUNCTION, FUNC_FLAVOR_EVENT } function_flavor;

typedef enum {
	TYPE_INTERNAL_VOID,
	TYPE_INTERNAL_INT, TYPE_INTERNAL_UNSIGNED, TYPE_INTERNAL_DOUBLE,
	TYPE_INTERNAL_STRING, TYPE_INTERNAL_ADDR, TYPE_INTERNAL_SUBNET,
	TYPE_INTERNAL_OTHER, TYPE_INTERNAL_ERROR
} InternalTypeTag;

class Expr;
class Attributes;
class TypeList;
class TableType;
class SetType;
class RecordType;
class SubNetType;
class FuncType;
class ListExpr;
class EnumType;

extern bool in_global_attr_decl;
extern RecordType* global_attributes_type;

class BroType : public BroObj {
public:
	BroType(TypeTag tag);

	TypeTag Tag() const		{ return tag; }
	InternalTypeTag InternalType() const	{ return internal_tag; }

	// Type for the attributes (metadata) on this type.
	RecordType* AttributesType()
		{
		if ( ! attributes_type )
			attributes_type = global_attributes_type;
		return attributes_type;
		}
	bool SetAttributesType(type_decl_list* attr_types);

	// Whether it's stored in network order.
	int IsNetworkOrder() const	{ return is_network_order; }

	// Typechecks the given expression list, returning true if it
	// match's this type's index (and promoting its subexpressions
	// as necessary), and false if it can't match (or the type is
	// not an indexable type).
	virtual int MatchesIndex(ListExpr*& index) const;

	// Returns the type yielded by this type.  For example, if
	// this type is a table[string] of port, then returns the "port"
	// type.  Returns nil if this is not an index type.
	virtual BroType* YieldType();
	const BroType* YieldType() const
		{ return ((BroType*) this)->YieldType(); }

	// Returns true if this type is a record and contains the
	// given field, false otherwise.
	virtual int HasField(const char* field) const;

	// Returns the type of the given field, or nil if no such field.
	virtual BroType* FieldType(const char* field) const;

	const TypeList* AsTypeList() const
		{
		if ( tag != TYPE_LIST )
			BadTag("BroType::AsTypeList");
		return (const TypeList*) this;
		}
	TypeList* AsTypeList()
		{
		if ( tag != TYPE_LIST )
			BadTag("BroType::AsTypeList");
		return (TypeList*) this;
		}

	const TableType* AsTableType() const
		{
		if ( tag != TYPE_TABLE )
			BadTag("BroType::AsTableType");
		return (const TableType*) this;
		}
	TableType* AsTableType()
		{
		if ( tag != TYPE_TABLE )
			BadTag("BroType::AsTableType");
		return (TableType*) this;
		}

	SetType* AsSetType()
		{
		if ( ! IsSet() )
			BadTag("BroType::AsSetType");
		return (SetType*) this;
		}
	const SetType* AsSetType() const
		{
		if ( ! IsSet() )
			BadTag("BroType::AsSetType");
		return (const SetType*) this;
		}

	const RecordType* AsRecordType() const
		{
		if ( tag != TYPE_RECORD )
			BadTag("BroType::AsRecordType");
		return (const RecordType*) this;
		}
	RecordType* AsRecordType()
		{
		if ( tag != TYPE_RECORD )
			BadTag("BroType::AsRecordType");
		return (RecordType*) this;
		}

	const SubNetType* AsSubNetType() const
		{
		if ( tag != TYPE_SUBNET )
			BadTag("BroType::AsSubNetType");
		return (const SubNetType*) this;
		}
	SubNetType* AsSubNetType()
		{
		if ( tag != TYPE_SUBNET )
			BadTag("BroType::AsSubNetType");
		return (SubNetType*) this;
		}

	const FuncType* AsFuncType() const
		{
		if ( tag != TYPE_FUNC )
			BadTag("BroType::AsFuncType");
		return (const FuncType*) this;
		}
	FuncType* AsFuncType()
		{
		if ( tag != TYPE_FUNC )
			BadTag("BroType::AsFuncType");
		return (FuncType*) this;
		}

	const EnumType* AsEnumType() const
		{
		if ( tag != TYPE_ENUM )
			BadTag("BroType::AsEnumType");
		return (EnumType*) this;
		}

	EnumType* AsEnumType()
		{
		if ( tag != TYPE_ENUM )
			BadTag("BroType::AsEnumType");
		return (EnumType*) this;
		}

	int IsSet() const
		{
		return tag == TYPE_TABLE && (YieldType() == 0);
		}

	BroType* Ref()		{ ::Ref(this); return this; }

	virtual void Describe(ODesc* d) const;

protected:
	void SetError();

private:
	TypeTag tag;
	InternalTypeTag internal_tag;
	int is_network_order;
	RecordType* attributes_type;
};

class TypeList : public BroType {
public:
	TypeList(BroType* arg_pure_type = 0) : BroType(TYPE_LIST)
		{
		pure_type = arg_pure_type;
		if ( pure_type )
			pure_type->Ref();
		}
	~TypeList();

	const type_list* Types() const	{ return &types; }
	type_list* Types()		{ return &types; }

	int IsPure() const		{ return pure_type != 0; }

	// Returns the underlying pure type, or nil if the list
	// is not pure or is empty.
	BroType* PureType()		{ return pure_type; }
	const BroType* PureType() const	{ return pure_type; }

	// True if all of the types match t, false otherwise.  If
	// is_init is true, then the matching is done in the context
	// of an initialization.
	int AllMatch(const BroType* t, int is_init) const;

	void Append(BroType* t);
	void AppendEvenIfNotPure(BroType* t);

	void Describe(ODesc* d) const;

protected:
	BroType* pure_type;
	type_list types;
};

class IndexType : public BroType {
public:
	int MatchesIndex(ListExpr*& index) const;

	TypeList* Indices() const		{ return indices; }
	const type_list* IndexTypes() const	{ return indices->Types(); }
	BroType* YieldType();

	void Describe(ODesc* d) const;

	// Returns true if this table is solely indexed by subnet.
	bool IsSubNetIndex() const;

protected:
	IndexType(TypeTag t, TypeList* arg_indices, BroType* arg_yield_type) :
		BroType(t)
		{
		indices = arg_indices;
		yield_type = arg_yield_type;
		}
	~IndexType();

	TypeList* indices;
	BroType* yield_type;
};

class TableType : public IndexType {
public:
	TableType(TypeList* ind, BroType* yield);

protected:
	TypeList* ExpandRecordIndex(RecordType* rt) const;
};

class SetType : public TableType {
public:
	SetType(TypeList* ind, ListExpr* arg_elements);
	~SetType();

	ListExpr* SetElements() const	{ return elements; }

protected:
	ListExpr* elements;
};

class FuncType : public BroType {
public:
	FuncType(RecordType* args, BroType* yield, int is_event);

	~FuncType();

	RecordType* Args() const	{ return args; }
	BroType* YieldType();
	int IsEvent() const		{ return is_event; }

	// Used to convert a function type to an event type.
	void ClearYieldType()
		{ Unref(yield); yield = 0; is_event = 1; }

	int MatchesIndex(ListExpr*& index) const;
	int CheckArgs(const type_list* args) const;

	TypeList* ArgTypes()	{ return arg_types; }

	void Describe(ODesc* d) const;

protected:
	RecordType* args;
	TypeList* arg_types;
	BroType* yield;
	int is_event;
};

class TypeDecl {
public:
	TypeDecl(BroType* t, char* i, attr_list* attrs = 0);
	~TypeDecl();

	const Attr* FindAttr(attr_tag a) const
		{ return attrs ? attrs->FindAttr(a) : 0; }

	BroType* type;
	Attributes* attrs;
	char* id;
};

class RecordField {
public:
	RecordField(int arg_base, int arg_offset, int arg_total_offset);

	int base;	// which base element it belongs to
	int offset;	// where it is in that base
	int total_offset;	// where it is in the aggregate record
};
declare(PDict,RecordField);

class RecordType : public BroType {
public:
	RecordType(type_decl_list* types);
	RecordType(TypeList* base, type_decl_list* refinements);

	~RecordType();

	int HasField(const char* field) const;
	BroType* FieldType(const char* field) const;
	BroType* FieldType(int field) const;

	// A field's offset is its position in the type_decl_list,
	// starting at 0.  Returns negative if the field doesn't exist.
	int FieldOffset(const char* field) const;

	// Given an offset, returns the field's name.
	const char* FieldName(int field) const;

	// Given an offset, returns the field's TypeDecl.
	const TypeDecl* FieldDecl(int field) const;
	TypeDecl* FieldDecl(int field);

	int NumFields() const			{ return num_fields; }

	void Describe(ODesc* d) const;
	void DescribeFields(ODesc* d) const;

protected:
	int num_fields;
	PDict(RecordField)* fields;
	TypeList* base;
	type_decl_list* types;
};

class SubNetType : public BroType {
public:
	SubNetType();
	void Describe(ODesc* d) const;
};

class FileType : public BroType {
public:
	FileType(BroType* yield_type);
	~FileType();

	BroType* YieldType();

	void Describe(ODesc* d) const;

protected:
	BroType* yield;
};

class EnumType : public BroType {
public:
	EnumType(bool arg_is_export);

	// The value of this name is next counter value, which is returned.
	// A return value of -1 means that the identifier already existed
	// (and thus could not be used).
	int AddName(const string& module_name, const char* name);

	// Add in names from the suppled EnumType; the return value is
	// the value of the last enum added.
	int AddNamesFrom(const string& module_name, EnumType* et);

	// -1 indicates not found.
	const int Lookup(const string& module_name, const char* name); 
	const char* Lookup(int value); // Returns 0 if not found

protected:
	typedef std::map< const char*, int, ltstr > NameMap;
	NameMap names;
	int counter;
	bool is_export;
};

// Returns the name of the type.
extern const char* type_name(TypeTag t);

// Returns the given type refinement, or error_type() if it's illegal.
extern BroType* refine_type(TypeList* base, type_decl_list* refinements);

// Returns the BRO basic (non-parameterized) type with the given type.
extern BroType* base_type(TypeTag tag);

// Returns the BRO basic error type.
inline BroType* error_type()	{ return base_type(TYPE_ERROR); }

// True if the two types are equivalent.  If is_init is true then the
// test is done in the context of an initialization.
extern int same_type(const BroType* t1, const BroType* t2, int is_init=0);

// Returns true if the record sub_rec can be promoted to the record
// super_rec.
extern int record_promotion_compatible(const RecordType* super_rec,
					const RecordType* sub_rec);

// If the given BroType is a TypeList with just one element, returns
// that element, otherwise returns the type.
extern const BroType* flatten_type(const BroType* t);
extern BroType* flatten_type(BroType* t);

// Returns the "maximum" of two type tags, in a type-promotion sense.
extern TypeTag max_type(TypeTag t1, TypeTag t2);

// Given two types, returns the "merge", in which promotable types
// are promoted to the maximum of the two.  Returns nil (and generates
// an error message) if the types are incompatible.
extern BroType* merge_types(const BroType* t1, const BroType* t2);

// True if the given type tag corresponds to an integral type.
#define IsIntegral(t)	(t == TYPE_INT || t == TYPE_COUNT || t == TYPE_COUNTER)

// True if the given type tag corresponds to an arithmetic type.
#define IsArithmetic(t)	(IsIntegral(t) || t == TYPE_DOUBLE)

// True if the given type tag corresponds to a boolean type.
#define IsBool(t)	(t == TYPE_BOOL)

// True if the given type tag corresponds to a record type.
#define IsRecord(t)	(t == TYPE_RECORD || t == TYPE_UNION)

// True if the given type tag corresponds to a function type.
#define IsFunc(t)	(t == TYPE_FUNC)

// True if the given type tag corresponds to type that can be assigned to.
extern int is_assignable(BroType* t);

// True if the given type tag corresponds to the error type.
#define IsErrorType(t)	(t == TYPE_ERROR)

// True if both tags are integral types.
#define BothIntegral(t1, t2) (IsIntegral(t1) && IsIntegral(t2))

// True if both tags are arithmetic types.
#define BothArithmetic(t1, t2) (IsArithmetic(t1) && IsArithmetic(t2))

// True if either tags is an arithmetic type.
#define EitherArithmetic(t1, t2) (IsArithmetic(t1) || IsArithmetic(t2))

// True if both tags are boolean types.
#define BothBool(t1, t2) (IsBool(t1) && IsBool(t2))

// True if either tag is the error type.
#define EitherError(t1, t2) (IsErrorType(t1) || IsErrorType(t2))

#endif
