/* $Id$
 *
 * RegisterBuiltins: can register builtin functions for a given type
 * declaration.
 *
 * Copyright (C) 2008-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */


#include "frontend/misc/RegisterBuiltins.hpp"

#include <string>
#include <cassert>
#include <iostream>
#include "frontend/ast/SimpleName.hpp"
#include "frontend/ast/FunctionDeclaration.hpp"
#include "frontend/ast/UnconstrainedArrayType.hpp"
#include "frontend/ast/EnumerationType.hpp"
#include "frontend/ast/RecordType.hpp"
#include "frontend/ast/ConstInteger.hpp"
#include "frontend/ast/ConstReal.hpp"
#include "frontend/visitor/TopDownVisitor.hpp"
#include "frontend/misc/SymbolTable.hpp"
#include "frontend/reporting/CompileError.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"
#include "frontend/visitor/ResolveTypes.hpp"

namespace ast {

void
RegisterBuiltins::regTypeOps(SymbolDeclaration &type, SymbolTable &s)
{
	/* FIXME: deriving from NullVisitor is enough. However for debugging
	 *        purposes (no calls for wrong nodes, we'll stay with 
	 *        TopDownVisitor).
	 */
	class Dispatcher : public TopDownVisitor {
	public:
		//! c'tor
		/** @param r RegisterBuiltins instance.
		 */
		Dispatcher(SymbolTable &st) : symTab(st) {}

		virtual void visit(RangeConstraintType &t) {
			if (t.baseType == BASE_TYPE_UNSET) {
				RegisterBuiltins::determineBaseType(t, 
								this->symTab);
			}
			if (t.baseType == BASE_TYPE_UNSET) {
				// type error occured, bail out.
				return;
			}
			RegisterBuiltins::registerTypeOps(t, this->symTab);
		}

		virtual void visit(PhysicalType &t) {
			RegisterBuiltins::registerTypeOps(t, this->symTab);
		}

		virtual void visit(EnumerationType &t) {
			RegisterBuiltins::registerTypeOps(t, this->symTab);
		}

		virtual void visit(RecordType &t) {
			RegisterBuiltins::registerTypeOps(t, this->symTab);
		}
		virtual void visit(SubtypeIndication &t) {
			//skip. subtypes inherit operations of base types.
		}
		virtual void visit(UnconstrainedArrayType &t) {
			RegisterBuiltins::registerTypeOps(t, this->symTab);
		}

	private:
		// cppcheck-suppress unusedPrivateFunction (false positive)
		virtual void process(AstNode &t) {
			std::cerr << "BANG: found no type node: " << t 
				<< " at " << t.location << std::endl;
			assert(false); 	// not a type or no visit method for
					// given type in Dispatcher
		}

		SymbolTable &symTab;
	};

	Dispatcher d = Dispatcher(s);
	type.accept(d);
}


void
RegisterBuiltins::registerTypeOps(RangeConstraintType &type, SymbolTable &s)
{
	/* FIXME split between integer and float types? */


	if (type.baseType == BASE_TYPE_INTEGER) {
		// relational operators
		// equality, inequality: any type except file and protected
		RegisterBuiltins::boolBinOp("=", 
			type, 
			new BoolEqual<ConstInteger, universal_integer>(s),
			new GCBuiltinsEqual(),
			s);
		RegisterBuiltins::boolBinOp("/=", 
			type, 
			new BoolNotEqual<ConstInteger, universal_integer>(s),
			new GCBuiltinsInEqual(),
			s);
		// ordering: any scalar and discrete array (= 1-dim array of 
		// discrete type)
		RegisterBuiltins::boolBinOp("<", 
			type, 
			new BoolLess<ConstInteger, universal_integer>(s),
			new GCBuiltinsLess(),
			s);
		RegisterBuiltins::boolBinOp("<=", 
			type, 
			new BoolLessOrEqual<ConstInteger, universal_integer>(s),
			new GCBuiltinsLessEqual(),
			s);
		RegisterBuiltins::boolBinOp(">", 
			type, 
			new BoolGreater<ConstInteger, universal_integer>(s),
			new GCBuiltinsGreater(),
			s);
		RegisterBuiltins::boolBinOp(">=", 
			type, 
			new BoolGreaterOrEqual<ConstInteger, universal_integer>(s),
			new GCBuiltinsGreaterEqual(),
			s);

		// adding operators (any numeric type)
		RegisterBuiltins::binOpSameType("+", 
			type, 
			new BinaryPlus<ConstInteger, universal_integer>(),
			new GCBuiltinsPlus(),
			s);
		RegisterBuiltins::binOpSameType("-",
			type, 
			new BinaryMinus<ConstInteger, universal_integer>(),
			new GCBuiltinsMinus(),
			s);
		// (no concatenation for RangeConstraintType)
		// multiplying operators
		RegisterBuiltins::binOpSameType("*", 
			type, 
			new BinaryMult<ConstInteger, universal_integer>(),
			new GCBuiltinsMult(),
			s);
		RegisterBuiltins::binOpSameType("/", 
			type,
			new BinaryDiv<ConstInteger, universal_integer>(),
			new GCBuiltinsDiv(),
			s);

		// sign operators (idendity, negation). Any numeric type.
		RegisterBuiltins::unOpSameType("+", 
			type, 
			new UnaryPlus<ConstInteger, universal_integer>(),
			new GCBuiltinsIdentity(),
			s);
		RegisterBuiltins::unOpSameType("-", 
			type, 
			new UnaryMinus<ConstInteger, universal_integer>(),
			new GCBuiltinsUnaryMinus(),
			s);

		RegisterBuiltins::unOpSameType("abs", 
			type, 
			new UnaryAbs<ConstInteger, universal_integer>(),
			new GCBuiltinsAnd(),
			s);

		// modulus/remainder
		RegisterBuiltins::binOpSameType("mod", 
			type, 
			new BinaryMod(),
			NULL, /* FIXME */
			s);
		RegisterBuiltins::binOpSameType("rem", 
			type, 
			new BinaryRem(),
			NULL, /* FIXME */
			s);
	} else {
		// relational operators
		// equality, inequality: any type except file and protected
		RegisterBuiltins::boolBinOp("=", 
			type, 
			new BoolEqual<ConstReal, universal_real>(s), 
			new GCBuiltinsEqual(),
			s);
		RegisterBuiltins::boolBinOp("/=", 
			type, 
			new BoolNotEqual<ConstReal, universal_real>(s),
			new GCBuiltinsInEqual(),
			s);
		// ordering: any scalar and discrete array (= 1-dim array of 
		// discrete type)
		RegisterBuiltins::boolBinOp("<", 
			type, 
			new BoolLess<ConstReal, universal_real>(s),
			new GCBuiltinsLess(),
			s);
		RegisterBuiltins::boolBinOp("<=", 
			type, 
			new BoolLessOrEqual<ConstReal, universal_real>(s),
			new GCBuiltinsLessEqual(),
			s);
		RegisterBuiltins::boolBinOp(">", 
			type, 
			new BoolGreater<ConstReal, universal_real>(s),
			new GCBuiltinsGreater(),
			s);
		RegisterBuiltins::boolBinOp(">=", 
			type, 
			new BoolGreaterOrEqual<ConstReal, universal_real>(s),
			new GCBuiltinsGreaterEqual(),
			s);

		// adding operators (any numeric type)
		RegisterBuiltins::binOpSameType("+", 
			type, 
			new BinaryPlus<ConstReal, universal_real>(),
			new GCBuiltinsPlus(),
			s);
		RegisterBuiltins::binOpSameType("-",
			type, 
			new BinaryMinus<ConstReal, universal_real>(),
			new GCBuiltinsMinus(),
			s);
		// (no concatenation for RangeConstraintType)
		// multiplying operators
		RegisterBuiltins::binOpSameType("*", 
			type, 
			new BinaryMult<ConstReal, universal_real>(),
			new GCBuiltinsMult(),
			s);
		RegisterBuiltins::binOpSameType("/", 
			type,
			new BinaryDiv<ConstInteger, universal_integer>(),
			new GCBuiltinsDiv(),
			s);
		// sign operators (idendity, negation). Any numeric type.
		RegisterBuiltins::unOpSameType("+", 
			type, 
			new UnaryPlus<ConstReal, universal_real>(), 
			new GCBuiltinsIdentity(),
			s);
		RegisterBuiltins::unOpSameType("-", 
			type, 
			new UnaryMinus<ConstReal, universal_real>(),
			new GCBuiltinsUnaryMinus(),
			s);
		RegisterBuiltins::unOpSameType("abs", 
			type, 
			new UnaryAbs<ConstReal, universal_real>(), 
			new GCBuiltinsAnd(),
			s);
	}
	
	// miscellaneous
	RegisterBuiltins::binOp("**", type, "integer", type, s);
}

void
RegisterBuiltins::registerTypeOps(PhysicalType &type, SymbolTable &s)
{
	// relational operators
	// equality, inequality: any type except file and protected
	RegisterBuiltins::boolBinOp("=", 
		type, 
		new BoolEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsEqual(),
		s);
	RegisterBuiltins::boolBinOp("/=", 
		type, 
		new BoolNotEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsInEqual(),
		s);
	// ordering: any scalar and discrete array (= 1-dim array of 
	// discrete type)
	RegisterBuiltins::boolBinOp("<", 
		type, 
		new BoolLess<ConstInteger, universal_integer>(s),
		new GCBuiltinsLess(),
		s);
	RegisterBuiltins::boolBinOp("<=", 
		type, 
		new BoolLessOrEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsLessEqual(),
		s);
	RegisterBuiltins::boolBinOp(">", 
		type, 
		new BoolGreater<ConstInteger, universal_integer>(s),
		new GCBuiltinsGreater(),
		s);
	RegisterBuiltins::boolBinOp(">=", 
		type, 
		new BoolGreaterOrEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsGreaterEqual(),
		s);
	// adding operators (any numeric type)
	RegisterBuiltins::binOpSameType("+", 
		type, 
		new BinaryPlus<ConstInteger, universal_integer>(),
		new GCBuiltinsPlus(),
		s);
	RegisterBuiltins::binOpSameType("-", 
		type, 
		new BinaryMinus<ConstInteger, universal_integer>(),
		new GCBuiltinsMinus(),
		s);
	// (no concatenation for physical types)
	// sign operators (idendity, negation). Any numeric type.
	RegisterBuiltins::unOpSameType("+", 
		type, 
		new UnaryPlus<ConstInteger, universal_integer>(),
		new GCBuiltinsIdentity(),
		s);
	RegisterBuiltins::unOpSameType("-", 
		type, 
		new UnaryMinus<ConstInteger, universal_integer>(),
		new GCBuiltinsUnaryMinus(),
		s);
	// multiplying operators (physical types only)
	FunctionDeclaration *f;
	f = RegisterBuiltins::binOp("*", type, "integer", type, s);
	f->builtin = new BinaryMult<ConstInteger, universal_integer>();
	f->gcBuiltin = new GCBuiltinsMult();

	// FIXME builtins!
	RegisterBuiltins::binOp("*", type, "real", type, s);
	// int -> int -> int
	f = RegisterBuiltins::binOp("*", "integer", type, type, s);
	f->builtin = new BinaryMult<ConstInteger, universal_integer>();
	f->gcBuiltin = new GCBuiltinsMult();

	// FIXME builtins!
	RegisterBuiltins::binOp("*", "real", type, type, s);
	
	f = RegisterBuiltins::binOp("/", type, "integer", type, s);
	f->builtin = new BinaryDiv<ConstInteger, universal_integer>();
	f->gcBuiltin = new GCBuiltinsDiv();

	// FIXME builtins!
	RegisterBuiltins::binOp("/", type, "real", type, s);

	TypeDeclaration *univ_int = 
		s.getStdStandardType("__universal_integer__");

	RegisterBuiltins::binOp("/", type, type, *univ_int, s);
	// miscellaneous
	RegisterBuiltins::unOpSameType("abs", 
		type, 
		new UnaryAbs<ConstInteger, universal_integer>(),
		new GCBuiltinsAbs(),
		s);
}

void
RegisterBuiltins::registerTypeOps(
	UnconstrainedArrayType &type, 
	SymbolTable &s
)
{
	// relational operators
	// equality, inequality: any type except file and protected
	// FIXME builtins
	RegisterBuiltins::boolBinOp("=", type, NULL, NULL, s);
	// FIXME builtins
	RegisterBuiltins::boolBinOp("/=", type, NULL, NULL, s);

	if (type.numIndices != 1) {
		return;
	}

	TypeDeclaration *elementType = type.elementType;
	assert(elementType != NULL);

	// concatenation (1-dim arrays)
	RegisterBuiltins::binOp("&", type, type, type, s);
	RegisterBuiltins::binOp("&", type, *elementType, type, s);
	RegisterBuiltins::binOp("&", *elementType, type, type, s);
	RegisterBuiltins::binOp("&", *elementType, *elementType, type, s);

	if (! RegisterBuiltins::isDiscreteType(elementType)) {
		return;
	}

	// ordering: any scalar and discrete array (= 1-dim array of 
	// discrete type)
	// FIXME builtins
	RegisterBuiltins::boolBinOp("<", type, NULL, NULL, s);
	RegisterBuiltins::boolBinOp("<=", type, NULL, NULL, s);
	RegisterBuiltins::boolBinOp(">", type, NULL, NULL, s);
	RegisterBuiltins::boolBinOp(">=", type, NULL, NULL, s);


	if (! RegisterBuiltins::isBoolOrBit(elementType, s)) {
		return;
	}

	// shift operators (1-dim array of bool or bit)
	RegisterBuiltins::binOp("sll", type, "integer", type, s);
	RegisterBuiltins::binOp("srl", type, "integer", type, s);
	RegisterBuiltins::binOp("sla", type, "integer", type, s);
	RegisterBuiltins::binOp("sra", type, "integer", type, s);
	RegisterBuiltins::binOp("rol", type, "integer", type, s);
	RegisterBuiltins::binOp("ror", type, "integer", type, s);
}

void
RegisterBuiltins::registerTypeOps(EnumerationType &type, SymbolTable &s)
{
	// relational operators
	// equality, inequality: any type except file and protected
	RegisterBuiltins::boolBinOp("=", 
		type, 
		new BoolEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsEqual(),
		s);
	RegisterBuiltins::boolBinOp("/=", 
		type, 
		new BoolNotEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsInEqual(),
		s);
	// ordering: any scalar and discrete array (= 1-dim array of 
	// discrete type)
	RegisterBuiltins::boolBinOp("<", 
		type, 
		new BoolLess<ConstInteger, universal_integer>(s),
		new GCBuiltinsLess(),
		s);
	RegisterBuiltins::boolBinOp("<=", 
		type, 
		new BoolLessOrEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsLessEqual(),
		s);
	RegisterBuiltins::boolBinOp(">", 
		type, 
		new BoolGreater<ConstInteger, universal_integer>(s),
		new GCBuiltinsGreater(),
		s);
	RegisterBuiltins::boolBinOp(">=", 
		type, 
		new BoolGreaterOrEqual<ConstInteger, universal_integer>(s),
		new GCBuiltinsGreaterEqual(),
		s);
}

void
RegisterBuiltins::registerTypeOps(RecordType &type, SymbolTable &s)
{
	// relational operators
	// equality, inequality: any type except file and protected
	// FIXME builtins
	RegisterBuiltins::boolBinOp("=", type, NULL, NULL, s);
	RegisterBuiltins::boolBinOp("/=", type, NULL, NULL, s);
}

SubtypeIndication*
RegisterBuiltins::generateSubtypeIndic(
	const TypeDeclaration &type
)
{
	SubtypeIndication *subtypeI = new SubtypeIndication(&type, 
					Location("RegisterBuiltins."));
	return subtypeI;
}

ConstantDeclaration*
RegisterBuiltins::generateArgument(
	const TypeDeclaration &type, 
	const SymbolTable &s
)
{
	SubtypeIndication *si = 
		RegisterBuiltins::generateSubtypeIndic(type);
	ConstantDeclaration *ret = new ConstantDeclaration(
					new std::string("__anonymous__"),
					NULL, 	/* initializier */
					si, 	/* SubtypeIndication */
					false,
					type.location);
	return ret;
}

FunctionDeclaration*
RegisterBuiltins::boolBinOp(
	const char *declName, 
	const TypeDeclaration &opType,
	BuiltinFunction *bf,
	GCBuiltins *gcBf,
	SymbolTable &s
)
{
	TypeDeclaration *ret = s.getStdStandardType("boolean");
	FunctionDeclaration *f = 
		RegisterBuiltins::binOp(declName, opType, opType, *ret, s);
	f->builtin = bf;
	f->gcBuiltin = gcBf;
	return f;
}

FunctionDeclaration*
RegisterBuiltins::binOpSameType(
	const char *declName,
	const TypeDeclaration &opType,
	BuiltinFunction *bf,
	GCBuiltins *gcBf,
	SymbolTable &s
)
{
	FunctionDeclaration *f = 
		RegisterBuiltins::binOp(declName, opType, opType, opType, s);
	f->builtin = bf;
	f->gcBuiltin = gcBf;
	return f;
}

FunctionDeclaration*
RegisterBuiltins::unOpSameType(
	const char *declName,
	const TypeDeclaration &opType,
	BuiltinFunction *bf,
	GCBuiltins *gcBf,
	SymbolTable &s
)
{
	std::string *n = new std::string(declName);
	std::list<ValDeclaration*> *args = new std::list<ValDeclaration*>();

	args->push_back(RegisterBuiltins::generateArgument(opType, s));

	FunctionDeclaration *ret = new FunctionDeclaration(
			n, 	// name of function
			args, 	// arguments
			RegisterBuiltins::generateSubtypeIndic(opType),
			true, 	// is pure?
			opType.location);

	ret->isBuiltin = true;
	ret->builtin = bf;
	ret->gcBuiltin = gcBf;
	
	s.registerSymbol(SYMBOL_FUNCTION, *ret);
	return ret;
}

FunctionDeclaration*
RegisterBuiltins::binOp(
	const char *declName,
	const TypeDeclaration &left,
	const TypeDeclaration &right,
	const TypeDeclaration &returnT,
	SymbolTable &s
)
{
	std::list<ValDeclaration*> *args = new std::list<ValDeclaration*>();

	args->push_back(RegisterBuiltins::generateArgument(left, s));
	args->push_back(RegisterBuiltins::generateArgument(right, s));

	FunctionDeclaration *ret = new FunctionDeclaration(
			new std::string(declName), // name of function
			args, 	// arguments
			RegisterBuiltins::generateSubtypeIndic(returnT),
			true, 	// is pure?
			returnT.location);
	ret->isBuiltin = true;

	s.registerSymbol(SYMBOL_FUNCTION, *ret);
	return ret;
}

FunctionDeclaration*
RegisterBuiltins::binOp(
	const char *declName,
	const char *left,
	const TypeDeclaration &right,
	const TypeDeclaration &returnT,
	SymbolTable &s
)
{
	TypeDeclaration *l = s.getStdStandardType(left);
	return RegisterBuiltins::binOp(declName, *l, right, returnT, s);
}

FunctionDeclaration*
RegisterBuiltins::binOp(
	const char *declName,
	const TypeDeclaration &left,
	const char *right,
	const TypeDeclaration &returnT,
	SymbolTable &s
)
{
	TypeDeclaration *r = s.getStdStandardType(right);
	return RegisterBuiltins::binOp(declName, left, *r, returnT, s);
}

bool
RegisterBuiltins::isDiscreteType(const TypeDeclaration *type)
{
	// integer type or enumeration type.
	if (type == NULL) {
		return false;
	}

	const EnumerationType *et = 
		dynamic_cast<const EnumerationType*>(type);

	if (et != NULL) {
		return true;
	}

	const RangeConstraintType *rt = 
		dynamic_cast<const RangeConstraintType*>(type);
	
	if (rt == NULL) {
		return false;
	}

	switch(rt->baseType) {
	case BASE_TYPE_INTEGER:
		return true;
	case BASE_TYPE_REAL:
		return false;
	case BASE_TYPE_UNSET:
		return false;
	default:
		assert(false);
	}

	return false;
}

bool
RegisterBuiltins::isBoolOrBit(const TypeDeclaration *type, SymbolTable &s)
{
	const TypeDeclaration *reference = s.getStdStandardType("boolean");
	
	if (reference == type) {
		return true;
	}

	reference = s.getStdStandardType("bit");
	if (reference == type) {
		return true;
	}

	return false;
}

void
RegisterBuiltins::determineBaseType(RangeConstraintType &t, SymbolTable &s)
{
	ResolveTypes r = ResolveTypes(s);
	t.accept(r);
}

}; /* namespace ast */
