#include "node.hpp"
#include "codegen.hpp"
#include "decl.hpp"
#include "environ.hpp"
#include "ex.hpp"
#include "toker.hpp"
#include "type.hpp"

#include "varnode.hpp"
#include "exprnode.hpp"

#include <stdutil.hpp>

std::set<std::string> Node::usedfuncs;

///////////////////////////////
// generic exception thrower //
///////////////////////////////
void Node::ex()
{
	ex("INTERNAL COMPILER ERROR");
}

void Node::ex(const std::string& e)
{
	throw BlitzException(e, -1, "");
}

void Node::ex(const std::string& e, int pos)
{
	throw BlitzException(e, pos, "");
}

void Node::ex(const std::string& e, int pos, const std::string& f)
{
	throw BlitzException(e, pos, f);
}

///////////////////////////////
// Generate a local variable //
///////////////////////////////
VarNode* Node::genLocal(Environ* e, Type* ty)
{
	std::string t = genLabel();
	Decl*       d = e->decls->insertDecl(t, ty, DECL_LOCAL);
	return new DeclVarNode(d);
}

/////////////////////////////////////////////////
// if type is const, return const value else 0 //
/////////////////////////////////////////////////
ConstNode* Node::constValue(Type* ty)
{
	ConstType* c = ty->constType();
	if (!c)
		return 0;
	ty = c->valueType;
	if (ty == Type::int_type)
		return new IntConstNode(c->intValue);
	if (ty == Type::float_type)
		return new FloatConstNode(c->floatValue);
	return new StringConstNode(c->stringValue);
}

///////////////////////////////////////////////////////
// work out var offsets - return size of local frame //
///////////////////////////////////////////////////////
int Node::enumVars(Environ* e)
{
	//calc offsets
	int p_size = 0, l_size = 0;
	for (int k = 0; k < e->decls->size(); ++k) {
		Decl* d = e->decls->decls[k];
		if (d->kind & DECL_PARAM) {
			d->offset = p_size + 20;
			p_size += 4;
		} else if (d->kind & DECL_LOCAL) {
			d->offset = -4 - l_size;
			l_size += 4;
		}
	}
	return l_size;
}

//////////////////////////////
// initialize all vars to 0 //
//////////////////////////////
TNode* Node::createVars(Environ* e)
{
	int    k;
	TNode* t = 0;
	//initialize locals
	for (k = 0; k < e->decls->size(); ++k) {
		Decl* d = e->decls->decls[k];
		if (d->kind != DECL_LOCAL)
			continue;
		if (d->type->vectorType())
			continue;
		if (!t)
			t = new TNode(IR_CONST, 0, 0, 0);
		TNode* p = new TNode(IR_LOCAL, 0, 0, d->offset);
		p        = new TNode(IR_MEM, p, 0);
		t        = new TNode(IR_MOVE, t, p);
	}
	//initialize vectors
	for (k = 0; k < e->decls->size(); ++k) {
		Decl* d = e->decls->decls[k];
		if (d->kind == DECL_PARAM)
			continue;
		VectorType* v = d->type->vectorType();
		if (!v)
			continue;
		TNode* p = call("__bbVecAlloc", global(v->label));
		TNode* m = d->kind == DECL_GLOBAL ? global("_v" + d->name) : local(d->offset);
		p        = move(p, mem(m));
		if (t)
			t = seq(t, p);
		else
			t = p;
	}
	return t;
}

////////////////////////
// release local vars //
////////////////////////
TNode* Node::deleteVars(Environ* e)
{
	TNode *t = 0, *l = 0, *p, *p1 = nullptr, *p2 = nullptr;
	for (int k = 0; k < e->decls->size(); ++k) {
		Decl*       d    = e->decls->decls[k];
		Type*       type = d->type;
		std::string func;
		if (type == Type::string_type) {
			if (d->kind == DECL_LOCAL || d->kind == DECL_PARAM) {
				func = "__bbStrRelease";
				p1   = mem(local(d->offset));
				p2   = 0;
			}
		} else if (type->structType()) {
			if (d->kind == DECL_LOCAL) {
				func = "__bbObjRelease";
				p1   = mem(local(d->offset));
				p2   = 0;
			}
		} else if (VectorType* v = type->vectorType()) {
			if (d->kind == DECL_LOCAL) {
				func = "__bbVecFree";
				p1   = mem(local(d->offset));
				p2   = global(v->label);
			}
		}
		if (!func.size())
			continue;
		p              = new TNode(IR_SEQ, call(func, p1, p2), 0);
		(l ? l->r : t) = p;
		l              = p;
	}
	return t;
}

//////////////////////////////////////////////////////////////
// compare 2 translated operands - return 1 if true, else 0 //
//////////////////////////////////////////////////////////////
TNode* Node::compare(int op, TNode* l, TNode* r, Type* ty)
{
	int n = 0;
	if (ty == Type::float_type) {
		switch (op) {
		case '=':
			n = IR_FSETEQ;
			break;
		case NE:
			n = IR_FSETNE;
			break;
		case '<':
			n = IR_FSETLT;
			break;
		case '>':
			n = IR_FSETGT;
			break;
		case LE:
			n = IR_FSETLE;
			break;
		case GE:
			n = IR_FSETGE;
			break;
		}
	} else {
		switch (op) {
		case '=':
			n = IR_SETEQ;
			break;
		case NE:
			n = IR_SETNE;
			break;
		case '<':
			n = IR_SETLT;
			break;
		case '>':
			n = IR_SETGT;
			break;
		case LE:
			n = IR_SETLE;
			break;
		case GE:
			n = IR_SETGE;
			break;
		}
	}
	if (ty == Type::string_type) {
		l = call("__bbStrCompare", l, r);
		r = new TNode(IR_CONST, 0, 0, 0);
	} else if (ty->structType()) {
		l = call("__bbObjCompare", l, r);
		r = new TNode(IR_CONST, 0, 0, 0);
	}
	return new TNode(n, l, r);
}

/////////////////////////////////
// calculate the type of a tag //
/////////////////////////////////
Type* Node::tagType(const std::string& tag, Environ* e)
{
	Type* t;
	if (tag.size()) {
		t = e->findType(tag);
		if (!t)
			ex("Type \"" + tag + "\" not found");
	} else
		t = 0;
	return t;
}

////////////////////////////////
// Generate a fresh ASM label //
////////////////////////////////
std::string Node::genLabel()
{
	static int cnt;
	return "_" + itoa(++cnt & 0x7fffffff);
}

//////////////////////////////////////////////////////
// create a stmt-type function call with int result //
//////////////////////////////////////////////////////
TNode* Node::call(const std::string& func, TNode* a0, TNode* a1, TNode* a2)
{
	int    size = 0;
	TNode* t    = 0;
	if (a0) {
		t = move(a0, mem(arg(0)));
		size += 4;
		if (a1) {
			t = seq(t, move(a1, mem(arg(4))));
			size += 4;
			if (a2) {
				t = seq(t, move(a2, mem(arg(8))));
				size += 4;
			}
		}
	}
	TNode* l = new TNode(IR_GLOBAL, 0, 0, func);
	return new TNode(IR_CALL, l, t, size);
}

////////////////////////////////////////////////////////
// create a stmt-type function call with float result //
////////////////////////////////////////////////////////
TNode* Node::fcall(const std::string& func, TNode* a0, TNode* a1, TNode* a2)
{
	int    size = 0;
	TNode* t    = 0;
	if (a0) {
		t = move(a0, mem(arg(0)));
		size += 4;
		if (a1) {
			t = seq(t, move(a1, mem(arg(4))));
			size += 4;
			if (a2) {
				t = seq(t, move(a2, mem(arg(8))));
				size += 4;
			}
		}
	}
	TNode* l = new TNode(IR_GLOBAL, 0, 0, func);
	return new TNode(IR_FCALL, l, t, size);
}

TNode* Node::seq(TNode* l, TNode* r)
{
	return new TNode(IR_SEQ, l, r);
}

TNode* Node::move(TNode* src, TNode* dest)
{
	return new TNode(IR_MOVE, src, dest);
}

TNode* Node::global(const std::string& s)
{
	return new TNode(IR_GLOBAL, 0, 0, s);
}

TNode* Node::local(int offset)
{
	return new TNode(IR_LOCAL, 0, 0, offset);
}

TNode* Node::arg(int offset)
{
	return new TNode(IR_ARG, 0, 0, offset);
}

TNode* Node::mem(TNode* ref)
{
	return new TNode(IR_MEM, ref, 0);
}

TNode* Node::add(TNode* l, TNode* r)
{
	return new TNode(IR_ADD, l, r);
}

TNode* Node::mul(TNode* l, TNode* r)
{
	return new TNode(IR_MUL, l, r);
}

TNode* Node::iconst(int n)
{
	return new TNode(IR_CONST, 0, 0, n);
}

TNode* Node::ret()
{
	return new TNode(IR_RET, 0, 0);
}

TNode* Node::jsr(const std::string& s)
{
	return new TNode(IR_JSR, 0, 0, s);
}

TNode* Node::jump(const std::string& s)
{
	return new TNode(IR_JUMP, 0, 0, s);
}

TNode* Node::jumpt(TNode* expr, const std::string& s)
{
	return new TNode(IR_JUMPT, expr, 0, s);
}

TNode* Node::jumpf(TNode* expr, const std::string& s)
{
	return new TNode(IR_JUMPF, expr, 0, s);
}

TNode* Node::jumpge(TNode* l, TNode* r, const std::string& s)
{
	return new TNode(IR_JUMPGE, l, r, s);
}
