/* $Id$
 *
 * SymbolTable: Raw storage facility for symbols.
 *
 * Copyright (C) 2007-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 <cassert>
#include <iostream>
#include <sstream>
#include "frontend/misc/SymbolTable.hpp"
#include "frontend/misc/RegisterBuiltins.hpp"

#define DEBUG 0

namespace ast {

SymbolTable::SymbolTable()
{
}

SymbolTable::~SymbolTable() 
{
	for (std::list<Symbol*>::const_iterator i = this->libraries.begin();
		i != this->libraries.end(); i++) {

		DeclarativeRegion* r = (*i)->region;
		assert(r);
		delete r;
		delete *i;
	}
}

Symbol*
SymbolTable::registerSymbol(
	enum symType type,
	SymbolDeclaration &decl
)
{
	assert(! this->regionStack.empty());
	DeclarativeRegion* current = this->getCurrentRegion();
	Symbol* sym = new Symbol(decl.name, type, NULL, decl);

	// FIXME potyra use s.th. different
	current->registerSymbol(sym);

	/* register builtin operations for type symbols as well. */
	if (type == SYMBOL_TYPE) {
		RegisterBuiltins::regTypeOps(decl, *this);
	}

#if DEBUG
	std::cerr << __func__ << ": register=" << *sym << std::endl;
#endif
	return sym;
}

void
SymbolTable::registerSymbolWithRegion(
	enum symType type,
	ast::SymbolDeclaration &decl)
{
	DeclarativeRegion *current;
	DeclarativeRegion *region;

	current = this->getCurrentRegion();
	assert(current);

	this->pushNewRegion();
	region = this->getCurrentRegion();
	assert(region);

	Symbol *sym = new Symbol(decl.name, type, region, decl);

	current->registerSymbol(sym);
	decl.region = region;

	assert(type != SYMBOL_TYPE); /* shouldn't happen, need to 
					Register builtins here. */
#if DEBUG	
	std::cerr << __func__ << ": register=" << *sym << std::endl;
#endif
}

void
SymbolTable::pushNewRegion(void)
{
	assert(! this->regionStack.empty());
	DeclarativeRegion* current = this->regionStack.top();
	assert(current);
	DeclarativeRegion* n = new DeclarativeRegion(current);
	this->regionStack.push(n);
}

void
SymbolTable::pushLibrary(Library &node)
{
	// there shouldn't be a parent region on the stack.
	assert(this->regionStack.empty());

	DeclarativeRegion *n = NULL;

	// check for existing libraries with same name.
	for (std::list<Symbol*>::const_iterator i = this->libraries.begin();
		i != this->libraries.end(); i++) {

		if ((*(*i)->name) == (*node.name)) {
			n = (*i)->region;
			break;
		}
	}

	// eventually allocate new region.
	if (n == NULL) {
		n = new DeclarativeRegion(NULL);
		Symbol *sym = new Symbol(node.name, SYMBOL_LIBRARY, n, node);
		// push on library list
		this->libraries.push_back(sym);

		// add it to imported symbols (as it must be rendered invisible, in 
		// case a Homograph hides it in the library region, instead of 
		// creating a duplicate name error).
		n->imports.push_back(sym);

		// set the region of the symbol to the current region
		node.region = n;
	}

	// push new region on stack
	this->regionStack.push(n);
}

void
SymbolTable::pushRegion(DeclarativeRegion &region)
{
	assert(! this->regionStack.empty());
	this->regionStack.push(&region);
}

void
SymbolTable::popRegion(void)
{
	assert(! this->regionStack.empty());
	this->regionStack.pop();
}

std::list<Symbol*>
SymbolTable::lookup(const std::string &name) const
{
	DeclarativeRegion *current = this->getCurrentRegion();
	assert(current);
	std::list<Symbol*> ret = current->lookup(name);

#if DEBUG
	std::cerr << __func__ << " for " << name << std::endl;
	for (std::list<Symbol*>::const_iterator i = ret.begin();
		i != ret.end(); i++) {

		std::cerr << "    " << **i << std::endl;
	}
	std::cerr << "----" << std::endl;
#endif

	return ret;
}

void
SymbolTable::importSymbol(Symbol &sym)
{
	DeclarativeRegion* current = this->getCurrentRegion();
	assert(current);

	for (std::list<Symbol*>::const_iterator i = current->imports.begin();
		i != current->imports.end(); i++) {

		if (*i == &sym) {
			return;
		}
	}

	current->imports.push_back(&sym);
#if DEBUG
	std::cerr << __func__ << ": importing=" << sym << std::endl;
#endif
}

bool
SymbolTable::addlibrary(const std::string &name)
{
	for (std::list<Symbol*>::const_iterator i = this->libraries.begin();
		i != this->libraries.end(); i++) {

		if ((*i)->name && (*(*i)->name == name)) {
			// library found, import it and return.
			this->importSymbol(**i);
			return true;
		}
	}

	return false;
}


void
SymbolTable::lateRegisterAttach(
	enum symType type,
	SymbolDeclaration &decl
)
{
	DeclarativeRegion *current = this->getCurrentRegion();
	assert(current);

	Symbol *sym = new Symbol(decl.name, type, current, decl);
	decl.region = current;
	DeclarativeRegion *check = current->parent;
	assert(check);

	this->popRegion();
	DeclarativeRegion *above = this->getCurrentRegion();
	assert(above == check);

	above->registerSymbol(sym);

	if (type == SYMBOL_TYPE) {
		RegisterBuiltins::regTypeOps(decl, *this);
	}

	this->pushRegion(*current);
}

void
SymbolTable::flipStack(void)
{
	DeclarativeRegion *top = this->getCurrentRegion();
	assert(top);
	this->popRegion();
	DeclarativeRegion *last = this->getCurrentRegion();
	assert(last);
	this->popRegion();
	DeclarativeRegion *check = this->getCurrentRegion();
	assert(check);

	assert(last->parent == check);
	assert(top->parent == check);
	last->parent = top;

	this->pushRegion(*top);
	this->pushRegion(*last);
}

void
SymbolTable::importSymbols(const DeclarativeRegion &region)
{
#if DEBUG
	std::cerr << "importSymbols: region.size()=" 
	          << region.declarations.size()
		  << " at: " << &region
		  << std::endl;
#endif
	for (std::list<Symbol*>::const_iterator i = 
		region.declarations.begin();
		i != region.declarations.end();
		i++) {

		this->importSymbol(**i);
	}
}

DeclarativeRegion*
SymbolTable::getCurrentRegion(void) const
{
	if (this->regionStack.empty()) {
		return NULL;
	}

	return this->regionStack.top();
}

void
SymbolTable::pushStdStandard(void)
{
	bool found = false;
	for (std::list<Symbol*>::const_iterator i = this->libraries.begin();
		i != this->libraries.end(); i++) {

		if (*(*i)->name == "std") {
			assert((*i)->region != NULL);
			this->regionStack.push((*i)->region);

			found = true;
			break;
		}
	}

	assert(found); /* builtin library std not found if assertion fails */
	
	std::list<Symbol*> p = this->lookup("standard");
	assert(p.size() == 1);
	
	Symbol *ps = p.front();
	assert(ps->type == SYMBOL_PACKAGE); // error in std.standard otherwise
	assert(ps->region != NULL);

	this->regionStack.push(ps->region);
}

TypeDeclaration*
SymbolTable::getStdStandardType(const char *name)
{
	typeMap::iterator i = this->stdStandardTypes.find(name);
	if (i == this->stdStandardTypes.end()) {
		// not found in cache
		this->pushStdStandard();
		std::list<Symbol*> l = this->lookup(name);
		this->popRegion(); // standard
		this->popRegion(); // std

		if (l.size() != 1) {
			return NULL;
		}

		Symbol *sym = l.front();
		if (sym->type != SYMBOL_TYPE) {
			// not a type
			return NULL;
		}

		TypeDeclaration *t = dynamic_cast<TypeDeclaration*>(
							&sym->declaration);
		if (! t) {
			return NULL;
		}

		this->stdStandardTypes[name] = t;
		return t;
	}

	return i->second;
}

std::string
SymbolTable::getMangledPathName(void) const
{
	static int cnt = 0;

	std::stringstream stream;
	stream << "__anonymous__"; 	// prefix
	stream << cnt;
	cnt++;
	std::string result = stream.str();

	return result;
}

}; /* namespace ast */
