/*
 * compiler program copyright (C) 2009 - 2013 H.Niwa
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <errno.h>
#include <setjmp.h>
#include <sys/time.h>
#include <math.h>
#include <libgen.h>
#include <setjmp.h>

#include <string>
#include <complex>

#include "syserr.h"

#include "bin_node.h"
#include "gc.h"
#include "var.h"
#include "pred.h"
#include "context.h"
#include "unify.h"
#include "builtin.h"
#include "sysmodule.h"
#include "compiler.h"


/*
 * compiler functions 
 */


int CompGetVarSub(Node* &nvar, Node* nkey, Node* nlist);

int CompCheckReserve(Context* cx, Node* goalscar, List* module);
int CompAddVar(Context* cx, Node* goalscar, List* module);
int CompGetVar(Context* cx, Node* goalscar, List* module);
int CompCheckVar(Context* cx, Node* goalscar, List* module);
int CompNewFunc(Context* cx, Node* goalscar, List* module);
int CompEndFunc(Context* cx, Node* goalscar, List* module);


/* ---------------------------------------------------------*/

int compmodule(Context* cx, Node* goalscar, Node* goalscdr, 
				Node* goals, List* module, int& r)
{
	Node* retn;
	int	rn;

	std::string	s;

	if (goalscar->Val()->Car()->kind() == ATOM) {
		((Atom*)(goalscar->Val()->Car()))->toString(s);

		if (s == "CheckReserved") {
			r = CompCheckReserved(cx, goalscar, module);
			return 1;
		} else if (s == "GetVar") {
			r = CompGetVar(cx, goalscar, module);
			return 1;
		} else if (s == "AddVar") {
			r = CompAddVar(cx, goalscar, module);
			return 1;
		} else if (s == "CheckVar") {
			r = CompCheckVar(cx, goalscar, module);
			return 1;
		} else if (s == "GetConst") {
			r = CompGetVar(cx, goalscar, module);
			return 1;
		} else if (s == "AddConst") {
			r = CompAddVar(cx, goalscar, module);
			return 1;
		} else if (s == "CheckConst") {
			r = CompCheckVar(cx, goalscar, module);
			return 1;
		} else if (s == "NewFunc") {
			r = CompNewFunc(cx, goalscar, module);
			return 1;
		} else if (s == "EndFunc") {
			r = CompEndFunc(cx, goalscar, module);
			return 1;
		}
	}
	r = -1;
	syserr("::compiler %s: The predicate that did not exist in the compiler module is used. \n",
			s.c_str());
	return 1;
}


int CompGetVarSub(Node* &nout, Node* nkey, Node* nlist)
{
	Node* ne, * n;
	nout = Nil;

	for (ne = nlist; ne != Nil; ne = ne->Cdr()) {
		for (n = ne->Car(); n->kind() != ATOM; n = n->Cdr()) {
			if (n->Car()->Car()->Eq(nkey)) {
				nout = n->Car()->Cdr()->Car();
				return 1;
			}
		}
	}
	return 0;
}


int CompCheckReserved(Context* cx, Node* goalscar, List* module)
{
	Node* g = goalscar->Cdr()->Val();
	int ll = ListLength(g);
	if (ll != 2) {
		syserr("usage : ::compiler <CheckReserved NAME LIST> \n");
		return 0;
	}

	Node* nname = g->Car()->Val();
	int rn;

	if ((rn = FuncArg(cx, nname, goalscar, module)) <= 0) {
		syserr("CheckReserved: failed in the evaluation of the argument. \n");
		return 0;
	}

	if (nname->kind() != ATOM) {
		syserr("usage : ::compiler <CheckReserved NAME LIST> \n");
		return 0;
	}

	Node* nlist = g->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nlist, goalscar, module)) <= 0) {
		syserr("CheckReserved: failed in the evaluation of the argument. \n");
		return 0;
	}

	if ((nlist->kind() != LIST) && (nlist != Nil)) {
		syserr("usage : ::compiler <CheckReserved NAME LIST> \n");
		return 0;
	}

	for (; nlist->kind() != ATOM; nlist = nlist->Cdr()) {
		if (nlist->Car()->Eq(nname)) {
			return 1;
		}
	}
	
	return -1;
}

int CompGetVar(Context* cx, Node* goalscar, List* module)
{
	/* GetVar VAR KEY LIST */

	Node* g = goalscar->Cdr()->Val();
	int ll = ListLength(g);
	if (ll != 3) {
		syserr("usage : ::compiler <GetVar VAR KEY LIST> \n");
		return 0;
	}

	Node* nvar = g->Car()->Val();
	int rn;

	if ((rn = FuncArg(cx, nvar, goalscar, module)) <= 0) {
		syserr("GetVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	if (nvar->kind() != UNDEF) {
		syserr("usage : ::compiler <GetVar VAR KEY LIST> \n");
		return 0;
	}

	Node* nkey = g->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nkey, goalscar, module)) <= 0) {
		syserr("GetVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	Node* nlist = g->Cdr()->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nlist, goalscar, module)) <= 0) {
		syserr("GetVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	if ((nlist->kind() != LIST) && (nlist != Nil)) {
		syserr("usage : ::compiler <GetVar VAR KEY LIST> \n");
		return 0;
	}

	Node* nout;
	if (!CompGetVarSub(nout, nkey, nlist)) {
		return -1;
	}
		
	Node* env = Nil->Cons(Nil);

	SetEnv(env, nvar);
	((Undef*)(nvar->Val()))->Set(nout);

	PushStack(cx, Nil, Nil, env);

	return 1;
}

int CompAddVar(Context* cx, Node* goalscar, List* module)
{
	/* AddVar VAR KEY VAL LIST */

	Node* g = goalscar->Cdr()->Val();
	int ll = ListLength(g);
	if (ll != 4) {
		syserr("usage : ::compiler <AddVar VAR KEY VAL LIST> \n");
		return 0;
	}

	Node* nvar = g->Car()->Val();
	int rn;

	if ((rn = FuncArg(cx, nvar, goalscar, module)) <= 0) {
		syserr("AddVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	if (nvar->kind() != UNDEF) {
		syserr("usage : ::compiler <AddVar VAR KEY VAL LIST> \n");
		return 0;
	}

	Node* nkey = g->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nkey, goalscar, module)) <= 0) {
		syserr("AddVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	Node* nval = g->Cdr()->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nval, goalscar, module)) <= 0) {
		syserr("AddVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	Node* nlist = g->Cdr()->Cdr()->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nlist, goalscar, module)) <= 0) {
		syserr("AddVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	if ((nlist->kind() != LIST) && (nlist != Nil)) {
		syserr("usage : ::compiler <AddVar VAR KEY VAL LIST> \n");
		return 0;
	}
#if 0
	Node* nout;
	if (CompGetVarSub(nout, nkey, nlist)) {
		return -1;
	}
#endif
		
	nlist = Cons(Cons(MkList(nkey, nval), nlist->Car()), nlist->Cdr());
		
	Node* env = Nil->Cons(Nil);

	SetEnv(env, nvar);
	((Undef*)(nvar->Val()))->Set(nlist);

	PushStack(cx, Nil, Nil, env);

	return 1;
}

int CompCheckVar(Context* cx, Node* goalscar, List* module)
{
	/* GetVar KEY LIST */

	Node* g = goalscar->Cdr()->Val();
	int ll = ListLength(g);
	if (ll != 2) {
		syserr("usage : ::compiler <CheckVar KEY LIST> \n");
		return 0;
	}

	Node* nkey = g->Car()->Val();
	int rn;

	if ((rn = FuncArg(cx, nkey, goalscar, module)) <= 0) {
		syserr("CheckVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	Node* nlist = g->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nlist, goalscar, module)) <= 0) {
		syserr("CheckVar: failed in the evaluation of the argument. \n");
		return 0;
	}

	nlist = nlist->Val();
	if ((nlist->kind() != LIST) && (nlist != Nil)) {
		syserr("usage : ::compiler <CheckVar KEY LIST> \n");
		return 0;
	}

	Node* nout;
	if (!CompGetVarSub(nout, nkey, nlist)) {
		return -1;
	}
		
	return 1;
}

int CompNewFunc(Context* cx, Node* goalscar, List* module)
{
	/* NewFunc VAR LIST */
	
	Node* g = goalscar->Cdr()->Val();
	int ll = ListLength(g);
	if (ll != 2) {
		syserr("usage : ::compiler <NewFunc VAR LIST> \n");
		return 0;
	}

	Node* nvar = g->Car()->Val();
	int rn;
	
	if ((rn = FuncArg(cx, nvar, goalscar, module)) <= 0) {
		syserr("NewFunc: failed in the evaluation of the argument. \n");
		return 0;
	}


	if (nvar->kind() != UNDEF) {
		syserr("usage : ::compiler <NewFunc VAR LIST> \n");
		return 0;
	}

	Node* nlist = g->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nlist, goalscar, module)) <= 0) {
		syserr("NewFunc: failed in the evaluation of the argument. \n");
		return 0;
	}

	if ((nlist->kind() != LIST) && (nlist != Nil)) {
		syserr("usage : ::compiler <NewFunc VAR LIST> \n");
		return 0;
	}

	nlist = Cons(Nil, nlist);
	
	Node* env = Nil->Cons(Nil);

	SetEnv(env, nvar);
	((Undef*)(nvar->Val()))->Set(nlist);

	PushStack(cx, Nil, Nil, env);

	return 1;
}

int CompEndFunc(Context* cx, Node* goalscar, List* module)
{
	/* EndFunc VAR LIST */
	
	Node* g = goalscar->Cdr()->Val();
	int ll = ListLength(g);
	if (ll != 2) {
		syserr("usage : ::compiler <EndFunc VAR LIST> \n");
		return 0;
	}

	Node* nvar = g->Car()->Val();
	int rn;
	
	if ((rn = FuncArg(cx, nvar, goalscar, module)) <= 0) {
		syserr("EndFunc: failed in the evaluation of the argument. \n");
		return 0;
	}

	if (nvar->kind() != UNDEF) {
		syserr("usage : ::compiler <EndFunc VAR LIST> \n");
		return 0;
	}

	Node* nlist = g->Cdr()->Car()->Val();

	if ((rn = FuncArg(cx, nlist, goalscar, module)) <= 0) {
		syserr("EndFunc: failed in the evaluation of the argument. \n");
		return 0;
	}

	if ((nlist->kind() != LIST) && (nlist != Nil)) {
		syserr("usage : ::compiler <EndFunc VAR LIST> \n");
		return 0;
	}

	nlist = nlist->Cdr();
	
	Node* env = Nil->Cons(Nil);

	SetEnv(env, nvar);
	((Undef*)(nvar->Val()))->Set(nlist);

	PushStack(cx, Nil, Nil, env);

	return 1;
}

