/* $Id$
 *
 * Generate C-Code from intermediate code.
 *
 * Copyright (C) 2010 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 "intermediate/visitor/GenCCode.hpp"
#include <cassert>
#include "intermediate/opcodes/Connect.hpp"
#include "intermediate/opcodes/Je.hpp"
#include "intermediate/opcodes/Jne.hpp"
#include "intermediate/opcodes/Jb.hpp"
#include "intermediate/opcodes/Jbe.hpp"
#include "intermediate/opcodes/Jmp.hpp"
#include "intermediate/opcodes/Mov.hpp"
#include "intermediate/opcodes/Abort.hpp"
#include "intermediate/opcodes/Add.hpp"
#include "intermediate/opcodes/Sub.hpp"
#include "intermediate/opcodes/IMul.hpp"
#include "intermediate/opcodes/Div.hpp"
#include "intermediate/opcodes/Call.hpp"
#include "intermediate/opcodes/Return.hpp"
#include "intermediate/opcodes/Proc.hpp"
#include "intermediate/opcodes/Update.hpp"
#include "intermediate/opcodes/GetSig.hpp"
#include "intermediate/opcodes/GetSimTime.hpp"
#include "intermediate/opcodes/ROffset.hpp"
#include "intermediate/opcodes/AOffset.hpp"
#include "intermediate/opcodes/Suspend.hpp"
#include "intermediate/opcodes/WakeOn.hpp"
#include "intermediate/opcodes/WakeAt.hpp"
#include "intermediate/opcodes/Log.hpp"
#include "intermediate/opcodes/BeginTransfer.hpp"
#include "intermediate/opcodes/EndTransfer.hpp"
#include "intermediate/opcodes/SetParam.hpp"
#include "intermediate/opcodes/GetParam.hpp"
#include "intermediate/operands/ImmediateOperand.hpp"
#include "intermediate/operands/IndirectOperand.hpp"
#include "intermediate/operands/Reference.hpp"
#include "intermediate/operands/Register.hpp"
#include "intermediate/container/CodeContainer.hpp"
#include "intermediate/container/Label.hpp"
#include "intermediate/container/Data.hpp"
#include "intermediate/container/TypeElement.hpp"
#include "intermediate/container/Type.hpp"

namespace intermediate {

/* Layout of stack frame:
 * struct container_sf {
 * 	struct vhdl_kernel *kernel;
 *	struct vhdl_process *process;
 * 	struct <parent>_sf *static_parent;
 *
 * 	struct {
 * 		// transfer elements (parameters)
 * 		// local data definition
 * 	} data;
 * 	void *transfer; // allocated child stack frame
 * };
 */

GenCCode::GenCCode(std::ostream &destination) 
	: 	dst(destination),
		nestingLevel(0),
		funcs(std::list<const std::string *>()),
		addFinale(true)
{
	this->prelude();
}

void
GenCCode::visit(CodeContainer &node)
{
	bool af = this->addFinale;
	this->addFinale = false;

	// register function to be called by name
	// that heuristic should match roughly the interpreter:
	// - not nested deeper than level 1 (that should be the architecture
	//   create functions)
	// - no parameters
	// - and the function in question must exist.
	if (   (node.nestingLevel < 2) 
	    && node.transferData.empty() 
	    && (! node.code.empty())) {

		this->funcs.push_back(&node.name);
	}

	this->nestingLevel = node.nestingLevel;
	this->listTraverse(node.typeDefinitions);

	this->dst << "struct " << node.name << "_sf {" << std::endl;
	this->dst << "\tstruct vhdl_kernel *kernel;" << std::endl;
	this->dst << "\tstruct vhdl_process *process;" << std::endl;
	if (node.staticParent != NULL) {
		this->dst << "\tstruct " << node.staticParent->name 
			<< "_sf *static_parent;" << std::endl;
	}

	if ((! node.transferData.empty()) || (! node.stackData.empty())) {
		this->dst << "\tstruct {" << std::endl
			<< "\t\t/* parameters */" << std::endl;
		this->listTraverse(node.transferData);
		this->dst << "\t\t/* local data */" << std::endl;
		this->listTraverse(node.stackData);
		this->dst << "\t} data;" << std::endl;
	}

	this->dst << "\tvoid *transfer;" << std::endl
		<< "};" << std::endl;

	this->dst << std::endl;
	this->listTraverse(node.children);
	this->nestingLevel = node.nestingLevel;

	if (! node.code.empty()) {
		this->dst << "static void *" << std::endl;
		this->dst << node.name << "(void *_cpssp)" << std::endl;
		this->dst << "{" << std::endl;
		this->dst << "\tstruct " << node.name 
			<< "_sf *cpssp = (struct "
			<< node.name << "_sf *)_cpssp;" << std::endl;

		// map registers to variables on the stack.
		this->genRegisters(node.regFab);
		this->dst << std::endl;
		this->initializeContainerData(node);
		this->listTraverse(node.code);
		this->dst << "}" << std::endl << std::endl;
	}

	if (af) {
		this->finale();
	}
}

void
GenCCode::genRegisters(const RegisterFactory &regFab)
{
	const std::vector<enum OpType> &tv = regFab.getRegTypes();
	size_t sz = 0;

	for (std::vector<enum OpType>::const_iterator i = 
		tv.begin(); i != tv.end(); i++, sz++) {

		this->dst << "\t";
		this->putOpType(*i);
		this->dst << " reg_" << sz << ";" << std::endl;
	}
}

void
GenCCode::initializeContainerData(const CodeContainer &container)
{
	for (std::list<Data *>::const_iterator i = 
		container.stackData.begin();
		i != container.stackData.end(); i++) {

		this->initializeData(**i);
	}
	// what about transfer data? does this need to get initialized as 
	// well (wrt. default arguments maybe?) (FIXME)
}

void
GenCCode::initializeData(const Data &data)
{
	switch (data.storage) {
	case STORAGE_TYPE_VARIABLE:
		this->createVariable(data);
		break;

	case STORAGE_TYPE_DRIVER:
		this->createDriver(data);
		break;

	case STORAGE_TYPE_SIGNAL:
		this->createSignal(data);
		break;
	}
}

void
GenCCode::createVariable(const Data &data)
{
	if (data.dataType->init.empty()) {
		return;
	}

	if (1 < data.dataType->ubound) {
		// FIXME: arrays (how on earth are arrays handled again?)
		return;
	}

	this->dst << "\tcpssp->data." << data.name << " = ";
	(*data.dataType->init.begin())->accept(*this);
	this->dst << "; " << std::endl;
}

void
GenCCode::createDriver(const Data &data)
{
	if (data.dataType->ubound == 1) {
		this->dst << "\tcpssp->data." << data.name 
			<< " = driver_create(cpssp->kernel);" << std::endl;
		return;
	}

	for (universal_integer sz = 0; sz < data.dataType->ubound; sz++) {
		this->dst << "\tcpssp->data." << data.name << "[" << sz 
			<< "] = driver_create(cpssp->kernel);" 
			<< std::endl;
	}
	// FIXME: initializers
}

void
GenCCode::createSignal(const Data &data)
{
	if (data.dataType->ubound == 1) {
		this->dst << "\tcpssp->data." << data.name 
			<< " = signal_create(cpssp->kernel, \""
			<< data.name << "\");" << std::endl;
		return;
	}

	for (universal_integer sz = 0; sz < data.dataType->ubound; sz++) {
		this->dst << "\tcpssp->data." << data.name << "[" << sz 
			<< "] = signal_create(cpssp->kernel, \""
			<< data.name << "\");"
			<< std::endl;
	}
	// FIXME: initializers
}

void
GenCCode::visit(ImmediateOperand &node)
{
	switch(node.type) {
	case OP_TYPE_REAL:
		this->dst << node.rValue << 'F';
		break;
	case OP_TYPE_INTEGER:
		this->dst << static_cast<uint64_t>(node.iValue) << "ULL "
			<< "/* " << node.iValue << " */";
		break;
	case OP_TYPE_POINTER:
		// illegal adressing mode: immediate pointer
		assert(false);
		break;
	}
}

void
GenCCode::visit(BeginTransfer &node)
{
	this->dst << "\tcpssp->transfer = malloc(sizeof(struct "
		<< node.src->name << "_sf));" << std::endl
		<< "\tassert(cpssp->transfer != NULL);" << std::endl;
}

void
GenCCode::visit(EndTransfer &node)
{
	if (node.cleanupStack)
	assert(node.cleanupStack->type == OP_TYPE_INTEGER);
	if (node.cleanupStack->iValue == 0) {
		this->dst << "\t/* EndTransfer: Stack still needed */"
			<< std::endl;
	} else {
		this->dst << "\tfree(cpssp->transfer);" << std::endl;
	}
}

void
GenCCode::visit(SetParam &node)
{
	this->dst << "\t((struct "
		<< node.container->name
		<< "_sf *) cpssp->transfer)->data."
		<< node.dst->name
		<< " = ";
	node.src->accept(*this);
	this->dst << ";" << std::endl;
}

void
GenCCode::visit(GetParam &node)
{
	this->dst << "\t";
	node.dst->accept(*this);
	this->dst << " = ((struct "
		<< node.container->name
		<< "_sf *) cpssp->transfer)->data."
		<< node.src->name << ";" << std::endl;
}

void
GenCCode::visit(Connect &node)
{
	this->dst << "\tdriver_connect((struct driver *)";
	node.driver->accept(*this);
	this->dst << ", (struct signal *)";
	node.signal->accept(*this);
	this->dst << ");" << std::endl;
}

void
GenCCode::visit(Mov &node)
{
	this->dst << "\t";
	node.dst->accept(*this);
	this->dst << " = ";
	node.src->accept(*this);
	this->dst << ";" << std::endl;
}


void
GenCCode::visit(Je &node)
{
	this->processCondJmp(node, "==");
}

void
GenCCode::visit(Jbe &node)
{
	this->processCondJmp(node, "<=");
}

void
GenCCode::visit(Jne &node)
{
	this->processCondJmp(node, "!=");
}

void
GenCCode::visit(Jb &node)
{
	this->processCondJmp(node, "<");
}

void
GenCCode::visit(Jmp &node)
{
	this->dst << "\tgoto " << node.trg->name << ";" << std::endl;
}

void
GenCCode::visit(Label &node)
{
	this->dst << node.name << ':' << std::endl;
}

void
GenCCode::visit(Add &node)
{
	this->processArith(node, "+");
}


void
GenCCode::visit(Abort &node)
{
	this->dst << "\tsched_abort();" << std::endl;
}

void
GenCCode::visit(Sub &node)
{
	this->processArith(node, "-");
}

void
GenCCode::visit(Call &node)
{
	assert(node.dst != NULL);

	this->chainStackFrames(*node.dst);
	this->dst << "\t((struct " << node.dst->name 
		<< "_sf *)cpssp->transfer)->process = cpssp->process;"
		<< std::endl
		<< "\t" << node.dst->name
		<< "(cpssp->transfer);" << std::endl;
}

void
GenCCode::visit(Return &node)
{
	this->dst << "\treturn NULL;" << std::endl;
}

void
GenCCode::visit(Proc &node)
{
	assert(node.dst != NULL);

	this->chainStackFrames(*node.dst);
	this->dst << "\t((struct " << node.dst->name
		<< "_sf *)cpssp->transfer)->process = "
		"sched_create_process(cpssp->kernel, cpssp->transfer, "
		<< node.dst->name << ");" << std::endl;
}

void
GenCCode::visit(Update &node)
{
	switch (node.src->type) {
	case OP_TYPE_INTEGER:
		this->dst << "\tdriver_update_int_after((struct driver *)";
		break;

	case OP_TYPE_REAL:
		this->dst << "\tdriver_update_real_after((struct driver *)";
		break;

	case OP_TYPE_POINTER:
		assert(false); // not supported, must not happen.
		break;
	}

	node.dst->accept(*this);
	this->dst << ", ";
	node.delay->accept(*this);
	this->dst << ", ";
	node.src->accept(*this);
	this->dst << ");" << std::endl;
}

void
GenCCode::visit(GetSig &node)
{
	this->dst << "\t";
	node.dst->accept(*this);
	this->dst << " = ";

	switch (node.dst->type) {
	case OP_TYPE_INTEGER:
		this->dst << "signal_read_int((struct signal *)";
		break;

	case OP_TYPE_REAL:
		this->dst << "signal_read_real((struct signal *)";
		break;

	case OP_TYPE_POINTER:
		assert(false); // not supported, must not happen.
		break;
	}
	node.src->accept(*this);
	this->dst << ");" << std::endl;
}

void
GenCCode::visit(GetSimTime &node)
{
	this->dst << "\t";
	node.dst->accept(*this);
	this->dst << " = time_virt();" << std::endl;
}


void
GenCCode::visit(IMul &node)
{
	this->processArith(node, "*");
}

void
GenCCode::visit(Div &node)
{
	this->processArith(node, "/");
}

void
GenCCode::visit(ROffset &node)
{
	this->dst << "\t";
	node.dst->accept(*this);
	this->dst << " = ((" << node.rtype->name << " *)&(";
	node.base->accept(*this);
	this->dst << "))->r" << node.offset->iValue << ";" << std::endl;
}

void
GenCCode::visit(AOffset &node)
{
	this->dst << "\t";
	node.dst->accept(*this);
	// FIXME might need to override for signals
	this->dst << " = ((" << node.atype->name << " *)";
	node.base->accept(*this);
	this->dst << ") + ";
	node.offset->accept(*this);
	this->dst << ";" << std::endl;
}

void
GenCCode::visit(Suspend &node)
{
	this->dst << "\tsched_suspend(cpssp->kernel, cpssp->process);" 
		<< std::endl;
}

void
GenCCode::visit(WakeOn &node)
{
	this->dst << "\tsched_wake_on(cpssp->process, (struct signal *)";
	node.src->accept(*this);
	this->dst << ");" << std::endl;
}

void
GenCCode::visit(WakeAt &node)
{
	this->dst << "\tsched_wake_at(cpssp, ";
	node.wakeTime->accept(*this);
	this->dst << ");" << std::endl;
}

void
GenCCode::visit(Log &node)
{
	this->dst << "\tvhdl_log(";
	node.lvl->accept(*this);
	this->dst << ", ";
	node.c->accept(*this);
	this->dst << ");" << std::endl;
}

void
GenCCode::visit(IndirectOperand &node)
{
	this->dst << "(*(";
	this->putOpType(node.type);
	this->dst << " *)";
	node.src->accept(*this);
	this->dst << ")";
}

void
GenCCode::visit(Reference &node)
{
	assert(node.associatedData != NULL);
	assert(node.associatedData->nestingLevel <= this->nestingLevel);

	this->dst << "&cpssp->";
	for (unsigned int i = node.associatedData->nestingLevel; 
		i < this->nestingLevel; i++) {

		this->dst << "static_parent->";
	}
	this->dst << "data." << node.name;
}

void
GenCCode::visit(Register &node)
{
	this->dst << "reg_" << node.num;
}

void
GenCCode::visit(Data &node)
{
	if (node.resolver) {
		this->dst << "/* FIXME the following signal is resolved */"
			<< std::endl;
	}

	switch (node.storage) {
	case STORAGE_TYPE_VARIABLE:
		if (node.dataType->name == "__pointer__") {
			this->dst << "\t\tvoid *";
		} else {
			this->dst << "\t\t" << node.dataType->name << " ";
		}
		break;

	case STORAGE_TYPE_DRIVER:
		this->dst << "\t\tstruct driver *";
		break;

	case STORAGE_TYPE_SIGNAL:
		this->dst << "\t\tstruct signal *";
		break;
	}

	this->dst << node.name;
	if (1 < node.dataType->ubound) {
		// FIXME braces correct for signals/drivers?
		this->dst << "[" << node.dataType->ubound << "]";
	}

	this->dst << ";" << std::endl;
}

void
GenCCode::visit(Type &node)
{
	assert(! node.elements.empty());

	// FIXME current type handling works only for non-signal types.
	// solution would be to define two typedefs, one for signals and
	// one for non-signals. The signals part should override
	// universal_integer and universal_real named TypeElements to
	// struct signal *.
	if (1 < node.elements.size()) {
		this->processRecord(node);
	} else {
		this->processArray(node);
	}
}

void
GenCCode::visit(TypeElement &node)
{
	this->dst << "/* FIXME TypeElement */" << std::endl;
}

void
GenCCode::processRecord(Type &node)
{
	this->dst << "typedef struct {" << std::endl;

	unsigned int cnt = 0;
	for (std::list<TypeElement*>::const_iterator i = 
		node.elements.begin();
		i != node.elements.end(); i++, cnt++) {

		this->processRecordElement(**i, cnt);
	}

	this->dst << "} " << node.name << ";" << std::endl;
}

void
GenCCode::processArray(Type &node)
{
	this->dst << "/* FIXME Array */" << std::endl;
}

void
GenCCode::processRecordElement(TypeElement &node, unsigned int number)
{
	this->dst << "\t" << node.name << " r" << number;

	if (node.ubound != 1) {
		this->dst << '[' << node.ubound << ']';
	}
	this->dst << ";" << std::endl;
	
	if (! node.init.empty()) {
		this->dst << "/* FIXME initializer */" << std::endl;
	}
}

template <typename T> 
void
GenCCode::processCondJmp(T &node, const char *op)
{
	this->dst << "\tif (";
	node.left->accept(*this);
	this->dst << " " << op << " ";
	node.right->accept(*this);
	this->dst << ") {" << std::endl 
		<< "\t\tgoto " << node.trg->name
		<< ";" << std::endl
		<< "\t}" << std::endl;
}

template <typename T>
void
GenCCode::processArith(T &node, const char *op)
{
	this->dst << "\t";
	node.dst->accept(*this);
	this->dst << " = ";
	node.left->accept(*this);
	this->dst << " " << op << " ";
	node.right->accept(*this);
	this->dst << ";" << std::endl;
}

void
GenCCode::putOpType(const enum OpType t)
{
	switch (t) {
	case OP_TYPE_INTEGER:
		this->dst << "universal_integer";
		break;

	case OP_TYPE_REAL:
		this->dst << "universal_real";
		break;

	case OP_TYPE_POINTER:
		this->dst << "void *";
		break;
	}
}

void
GenCCode::chainStackFrames(const Reference &callee)
{
	assert(callee.associatedContainer != NULL);

	this->dst << "\t((struct " << callee.name 
		<< "_sf *)cpssp->transfer)->static_parent = cpssp";

	// a: callee = 3, our=2 -> cpssp
	// b: callee = 2, our=2 -> cpssp->static_parent
	// c: callee = 1, our=2 -> cpssp->static_parent->static_parent

	for (unsigned int nl = this->nestingLevel;
		callee.associatedContainer->nestingLevel <= nl; nl--) {

		this->dst << "->static_parent";
	}

	this->dst << ";" << std::endl;

	// also set pointer to kernel
	this->dst << "\t((struct " << callee.name 
		<< "_sf *)cpssp->transfer)->kernel = cpssp->kernel;"
		<< std::endl;
}

void
GenCCode::prelude(void)
{
	// FIXME name of include file!
	this->dst << "#include <runtime.h>" << std::endl
		<< "/* Generated by fauhdlc */" << std::endl
		<< std::endl;
}

void
GenCCode::finale(void)
{
	// TODO instance pointer?

	this->dst << std::endl
		<< "int " << std::endl
		<< "vhdl_call_by_name(struct vhdl_kernel *kernel, "
		   "const char *name)"
		<< std::endl
		<< "{" << std::endl;

	for (std::list<const std::string *>::const_iterator i =
		this->funcs.begin();
		i != this->funcs.end(); i++) {

		this->dst << "\tif (strcmp(name, \"" << **i << "\") == 0) {"
			<< std::endl
			<< "\t\tstruct " << **i << "_sf *sf = malloc(sizeof("
				"struct " << **i << "_sf));" << std::endl
			<< "\t\tassert(sf != NULL);" << std::endl
			<< "\t\tsf->static_parent = NULL;" << std::endl
			<< "\t\tsf->kernel = kernel;" << std::endl
			<< "\t\t" << **i << "(sf);" << std::endl
			<< "\t\treturn 0;" << std::endl
			<< "\t}" << std::endl;
	}

	this->dst << "\treturn -1;" << std::endl
		<< "}" << std::endl;
}

}; /* namespace intermediate */
