#include "yab2cpp.h"

enum COMPILEERRORS errorLevel=E_OK;
unsigned int mode=0;
unsigned int indentLevel=0;
bool scopeGlobal=true;

extern ofstream output_cpp;
extern ofstream funcs_h;
extern ofstream heap_h;
extern ofstream consts_h;
extern ofstream logfile;
extern ofstream varNames;

extern ifstream src;

/* private prototypes */
void helpText(string &);
void shutDown();
void logger(string &);

/* process command line parameters */
int main(int argc, char *argv[])
{
	atexit(shutDown);
	switch (argc)
	{
		case 1:
			mode=COMPILE;
			cout << "\nCompile initiated." << endl;
			compile();
			break;
		case 2:
			if (argv[1][0]=='-')
			{
				switch (argv[1][1])
				{
					case 'd':
						cout << "\nIdentifier dump initiated." << endl;
						mode=DUMP;
						compile();
						break;
					case 'v':
						cout << "\n" << argv[0] << " version " 
							<< VER_MAJOR << "." << VER_MINOR << "." << VER_RELEASE << endl;
						break;
					case 'V':
						cout << "\nVerbose compile initiated." << endl;
						mode=DUMP|COMPILE;
						compile();
						break;
					case 'D':
						cout << "\nCompiler debug and dump mode initiated." << endl;
						mode=DUMP|DEBUG;
						compile();
						break;
					case 'G':
						cout << "\nDebug, dump and compile initiated." << endl;
						mode=DUMP|DEBUG|COMPILE;
						compile();
						break;
					default:
						helpText(argv[0]);
						break;
				}
			}
			break;
		default:
			helpText(argv[0]);
			break;
	}
	return 0;
}

/* print the help text to stdout */
void helpText(string &commandname)
{
	cout << commandname << "[-d|D|V|v|G] < filename.mb\n" <<
		"Compiles filename.mb by default unless a flag is specified.\n" <<
		"\n The optional flags are as follows:\n" <<
		"-d is a dump of build to the parse.log file.\n" <<
		"-D is a dump of identifiers and logged build.\n" <<
		"-V is for a verbose build where the compiler logs and compiles.\n" <<
		"-v prints the version and exits.\n\n" <<
		"-G activates dump, debug and compile all at once.\n" << endl;
}

/* open files and initialize them*/
void setUp()
{
	if (mode & COMPILE)
	{
		/* compile mode */
		output_cpp=new ofstream("build/output.cpp");
		funcs_h=new ofstream ("functions.h");
		consts_h=new ofstream("consts.h");
		heap_h=new ofstream("heap.h");
		output_cpp << "#include <runtime.h>\n#include \"consts.h\"\n"
			<< "#include \"heap.h\"\n#include \"functions.h\"\n"
			<< "unsigned int state=start;\nint run(){\nwhile (state>=start){\n"
			<< "switch(state){\ncase start:" << endl;
		if (mode & DEBUG)
		{
			varNames=new ofstream("varnames.txt");
		}
	}
	if (mode & DUMP)
	{
		/* dump identifier mode */
		logfile=fopen("parse.log","w");
		logger("Setup complete.");
	}
}

/* write a note in the logfile */
void logger(string &contents)
{
	unsigned int count;
	if (mode & DEBUG)
	{
		count=indentLevel;
		while (count > 0)
		{
			logfile << '\t';
			--count;
		}
		logfile << contents << endl;
	}
}

/* shutdown the compiler and exit */
void shutDown()
{
	if  (errorLevel != E_OK) cerr << "\nERROR: " << COMPILEERRORNAMES[errorLevel] << "\n\n" << endl;
	if (fn::isCallStackEmpty())
	{
		logger("Stack was empty");
	}
	else
	{
		logger("Dumping stack.");
		if (mode & DUMP && logfile != NULL)
		{
			fn::dumpCallStack(logfile);
		}
	}
	operands::dumpVars();
	label::dumpLabels();
	output_cpp << "}\n}return state;\n}"<< endl;
	}
}

/* open files and compile */
void compile()
{
	setUp();
		
	/* parse */
	ctx = mb_create(NULL);
	while(mb_parse(ctx, NULL)){logger("done");}
	mb_destroy(ctx);

	shutDown();
}

/* methods for operands */
static void operands::dumpVars(ostream &out)
{
	out << "Global Variables\n";
	for(auto iter=globals.begin(); iter!=globals.end(); ++iter)
	{
		out << "variable " << iter->first << " has ID " << iter->second << "\n";
	}
	out << endl;
}
unsigned int operands::getOrCreateStr(string &s, ostream &k)
{
	auto iter=constStr.find(s);
	if (iter!=constStr.end()) return iter->second;
	++nextID;
	k << "const string sk" << nextID << "=\"" << s << "\";\n";
	constStr[s]=nextID;
	return nextID;
}

enum TYPES operands::getSimpleVarType()
{
	switch type
	{
		case T_FLOAT:
		case T_FLOATCALL_ARRAY:
		case T_FLOATVAR:
			return T_FLOATVAR;
		case T_INT:
		case T_INTCALL_ARRAY:
		case T_INTVAR:
			return T_INTVAR;
		case T_STRING:
		case T_STRINGCALL_ARRAY:
		case T_STRINGVAR:
			return T_STRINGVAR;
	}
	return T_NONE;
}

enum TYPES operands::coerceTypes()
{
	if this->isBinOp()
	{
		if (l->getSimpleVarType()==T_INTVAR && r->getSimpleVarType()==T_FLOATVAR)
		{
			/* promote l to float */
			t=T_FLOATVAR;
			break;
		}
		if (l->getSimpleVarType()==T_FLOAT && r->getSimpleVarType()==T_INT)
		{
			/* promote r to float */
			t=T_FLOATVAR;
			break;
		}
		if (l->getSimpleVarType()==r->getSimpleVarType())
		{
			break;
		}
		errorLevel=E_TYPE_MISMATCH;
		exit(1);
	}
	else
	{
		if (t==T_NONE)
		{
			errorLevel=E_TYPE_MISMATCH;
		}
	}

}

/* operands used by expression parser and variables */

operands::operands(enum TYPES t)
{
	this->id = ++nextID;
	this->type=t;
}

void operands::generateBox(ostream &out)
{
	switch (this->getSimpleVarType())
	{
	case T_INTVAR:
		out << "int v";
		break;
	case T_FLOATVAR:
		out << "double v";
		break;
	case T_STRINGVAR:
		out << "string v";
		break;
	default:
		errorLevel=E_TYPE_MISMATCH;
		exit(1);
		break;
	}
	out << this->getID() << ";\n";
}

void operands::boxName(ostream &out)
{
	switch (this->getType())
	{
	case T_STRINGVAR:
	case T_INTVAR:
	case T_FLOATVAR:
		break;
	
	default:
		errorLevel=E_INTERNAL;
		exit(1);
		break;
	}
	out << "v" << this->getID();
}

/* expression parsing routines */

/* binary vs. unary ops */
bool expression::isBinOp()
{
	switch this->getOp()
	{
		case O_NEGATE:
		case O_NOT:
		case O_INVERT:
			return false;
			break;
	}
	return true;
}

operands *expression::evaluate(ostream &scope, ostream &output_cpp)
{
	operands *l, *r;
	if (this->getOp()==O_TERM) return op;
	l=this->getLeft()->evaluate();
	enum TYPES t=this->coerceTypes();
	l->getSimpleVarType();
	r=(this->isBinOp()?this->getRight()->evaluate():NULL);
	switch (this->getOp())
	{
	case O_INVERT:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "= ~" << l->boxName() << ";\n";
		break;
	case O_NEGATE:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "= -" << l->boxName() << ";\n";
		break;
	case O_NOT:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "= !" << l->boxName() << ";\n";
		break;
		/* TODO:  Check for divide by zero error and modulo zero error */
	case O_REMAINDER:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=" << l->boxName() << "%" << r->boxName() << ";\n";
		break;
	case O_DIVIDE:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=" << l->boxName() << "/" << r->boxName() << ";\n";
		break;
	case O_PLUS:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=" << l->boxName() << "+" << r->boxName() << ";\n";
		break;
	case O_MINUS:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=" << l->boxName() << "-" << r->boxName() << ";\n";
		break;
	case O_MULTIPLY:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=" << l->boxName() << "*" << r->boxName() << ";\n";
		break;
	case O_OR:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=" << l->boxName() << "|" << r->boxName() << ";\n";
		break;
	case O_AND:
		this->op=new operands(t);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=" << l->boxName() << "&" << r->boxName() << ";\n";
		break;
	case O_GREATER:
		this->op=new operands(T_INTVAR);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=(" << l->boxName() << ">" << r->boxName() << ")?-1:0;\n";
		break;
	case O_LESS:
		this->op=new operands(T_INTVAR);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=(" << l->boxName() << "<" << r->boxName() << ")?-1:0;\n";
		break;
	case O_GREATER_EQUAL:
		this->op=new operands(T_INTVAR);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=(" << l->boxName() << ">=" << r->boxName() << ")?-1:0;\n";
		break;
	case O_LESS_EQUAL:
		this->op=new operands(T_INTVAR);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=(" << l->boxName() << "<=" << r->boxName() << ")?-1:0;\n";
		break;
	case O_EQUAL:
		this->op=new operands(T_INTVAR);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=(" << l->boxName() << "==" << r->boxName() << ")?-1:0;\n";
		break;
	case O_UNEQUAL:
		this->op=new operands(T_INTVAR);
		this->op->generateBox(scope);
		output_cpp << this->op->boxName() << "=(" << l->boxName() << "!=" << r->boxName() << ")?-1:0;\n";
		break;
	default:
		errorLevel=E_INTERNAL;
		exit(1);
		break;
	}
	delete this->left;
	this->left=NULL;
	if (this->isBinOp())
	{
		delete this->right;
		this->right=NULL;
	}
	this->oper=O_TERM;
	return this->op;
}

expression::~expression()
{
	if(this->getOp()==O_TERM)
	{
		delete this->op;
	}
	else
	{
		delete this->left;
		if (this->isBinOp())
		{
			delete this->right;
		}
	}
}

/* base class of all the code structure types */

codeType::codeType(enum CODES t)
{
	this->id= ++nextID;
	nesting.push_back(this);
	this->type=t;
}

codeType *codeType::getCurrent()
{
	return nesting.back;
}

void codeType::close()
{
	nesting.pop_back();
}

/* label definitions and helper routines */

label *label::find(string &s)
{
	auto ret=lookup.find(s);
	return(ret==lookup.end()?NULL:ret->second);
}

void label::dumpLabels(ostream &v)
{
	v << "Global Labels\n\n";
	for(auto iter=lookup.begin(); iter!=lookup.end(); ++iter)
	{
		v << "label " << iter->first << " has ID " << iter->second->getID() << "\n" ;
	}
	v << endl;
}

void label::generateJumpTo(ostream &out)
{
	out << "state=" << this->getID() << ";\nbreak;\n";
}

void label::generateOnNSkip(ostream &k, list<label *> &dest)
{
	if (dest->size()<2)
	{
		errorLevel=E_BAD_SYNTAX;
		exit(1);
	}
	auto iter=dest.start(); 
	k << "j" << this->getID() << "[]={" << *iter;
	++iter;
	while(iter!=dest.end())
	{
		k << ", " << *iter;
		++iter;
	}
	k << "}\njs" << this->getID()<< "=" << dest->size() << ";\n";
}

void label::generateOnNTo(ostream &out, expression *e)
{
	operands *o=e->evaluate();
	if (o->getType()==T_INT||o->getType()==T_INTVAR)
	{
		out << "if(";
		o->boxName(out);
		out << ">=0 && ";
		o->boxName(out);
		out << "<js" << this->getID() << ")state=j["; 
		o->boxName(out);
		out << "];\nbreak;\n";
	}
	delete e;
}

void label::generateCondJump(ostream &out, expression *e)
{
	operands *o=e->evaluate();
	if (o->getType()==T_INT||o->getType()==T_INTVAR)
	{
		out << "if(";
		o->boxName(out);
		out << "!=0)state=" << this->getID() << ";\nbreak;\n";
	}
	delete e;
}

void label::generate(ostream &out)
{
	out << "case " << this->getID() <<":\n";
}

/* conditional definition */

conditional::conditional(ostream &out, expression *e):codeType(T_IF)
{
	this->redo=new label();
	redo->generate(out);
	this->done=new label();
	expression *f=new expression(e,O_NOT);
	this->chain=new label();
	chain->generateCondJump(out, f);
}

void conditional::generateBreak(ostream &out)
{
	done->generateJumpTo(out);
}

void conditional::generateContinue(ostream &out)
{
	redo->generateJumpTo(out);
}

void conditional::alternative(ostream &out, expression *e=NULL)
{
	done->generateJumpTo(out);
	this->chain->generate();
	delete this->chain;
	this->chain=NULL;
	if(e!=NULL)
	{
		this->chain=new label();
		expression *f=new expression(e,O_NOT);
		chain->generateJumpCond(out, f);
	}
}

void conditional::close(ostream &out)
{
	if(this->chain)
	{
		/* elsif ended without else in between */
		errorLevel=E_BAD_SYNTAX;
		exit(1);
	}
	this->done->generate();
	delete this->done;
	delete this->redo;
}

/* Loop definitions */

/* function definitions */

void fn::generateOnNSub(ostream &out, expression *e)
{
	this->ret=new label();
	fn::callStack.push_back(this);
	label::generateOnNTo(out, e);
	out << "case " << ret->getID() << ":\n";
}

void fn::generateGosub(ostream &out, shared_ptr<label> sub)
{
	this->ret=new label();
	fn::callStack.push_back(this);
	out << "state=" << sub->getID() << ";\nbreak;\n";
	out << "case " << ret->getID() << ":\n";
}
