/*
 * Binary node program copyright (C) 2009 - 2012 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 <string.h>
#include <errno.h>

#include <string>
#include <complex>

#include "syserr.h"

#include "code.h"
#include "bin_node.h"

unsigned int PrCount = 0;

// The flag which prints a double quote on atom
int printstrflag = 0;

// True object
Atom  Truearea("true");
Node* True = &Truearea;

// Nil object
Atom  Nilarea("()");
Node* Nil = &Nilarea;

Atom  AllNodesBody("AllNodes");
Node* AllNodes = &AllNodesBody;

Node* FreeListNodes = NULL;
Node* FreeAtomNodes = NULL;

static inline Node* NewList(Node* nd1, Node* nd2)
{
	if (FreeListNodes == NULL) {
		return new List(nd1, nd2);
	} else {
		Node* nd = FreeListNodes;
		FreeListNodes = nd->nextnode;
		nd->nextnode = AllNodes->nextnode;
		AllNodes->nextnode = nd;
		((List*)nd)->Set(nd1, nd2);
		return nd;
	}
}

Node* NewAtom(char const* s)
{
	if (FreeAtomNodes == NULL) {
		Atom* atm = new Atom(s);
		return atm;
	} else {
		Node* nd = FreeAtomNodes;
		FreeAtomNodes = nd->nextnode;
		nd->nextnode = AllNodes->nextnode;
		AllNodes->nextnode = nd;
		((Atom*)nd)->Set(s);
		return nd;
	}
}

Node* NewAtom(char* s)
{
	if (FreeAtomNodes == NULL) {
		Atom* atm = new Atom(s);
		return atm;
	} else {
		Node* nd = FreeAtomNodes;
		FreeAtomNodes = nd->nextnode;
		nd->nextnode = AllNodes->nextnode;
		AllNodes->nextnode = nd;
		((Atom*)nd)->Set(s);
		return nd;
	}
}

Node* NewAtom(std::string &s)
{
	if (FreeAtomNodes == NULL) {
		Atom* atm = new Atom(s);
		return atm;
	} else {
		Node* nd = FreeAtomNodes;
		FreeAtomNodes = nd->nextnode;
		nd->nextnode = AllNodes->nextnode;
		AllNodes->nextnode = nd;
		((Atom*)nd)->Set(s);
		return nd;
	}
}

Node* NewAtom(long long n)
{
	if (FreeAtomNodes == NULL) {
		return (Node*)(new Atom(n));
	} else {
		Node* nd = FreeAtomNodes;
		FreeAtomNodes = nd->nextnode;
		nd->nextnode = AllNodes->nextnode;
		AllNodes->nextnode = nd;
		((Atom*)nd)->Set(n);
		return nd;
	}
}

Node* NewAtom(long double n)
{
	if (FreeAtomNodes == NULL) {
		return (Node*)(new Atom(n));
	} else {
		Node* nd = FreeAtomNodes;
		FreeAtomNodes = nd->nextnode;
		nd->nextnode = AllNodes->nextnode;
		AllNodes->nextnode = nd;
		((Atom*)nd)->Set(n);
		return nd;
	}
}

Node* NewAtom(std::complex<long double> c)
{
	if (FreeAtomNodes == NULL) {
		return (Node*)(new Atom(c));
	} else {
		Node* nd = FreeAtomNodes;
		FreeAtomNodes = nd->nextnode;
		nd->nextnode = AllNodes->nextnode;
		AllNodes->nextnode = nd;
		((Atom*)nd)->Set(c);
		return nd;
	}
}


Node* Cons(Node* nd1, Node* nd2) 
{
	return NewList(nd1, nd2);
}


Node* GetLeftNode(Node* goals, Node* &goalscdr, int& level)
{
//	goals = goals->Val();
	if (goals->kind() == LIST) {
		level++;
		if (goals->Car()->kind() != LIST) {
			goalscdr = goals->Cdr();
			return goals->Car();
		} else {
			Node* r = GetLeftNode(goals->Car(), goalscdr, 
							level);
			if (goalscdr == Nil) {
				goalscdr = goals->Cdr();
			} else {
				goalscdr = Cons(goalscdr, goals->Cdr());
			}
			return r;
		}
	}
	goalscdr = Nil;
	return goals;
}

Node* EraseLevelNode(Node* goals, int level)
{
	goals = goals->Val();

	if (level < 2) {
		return Nil;
	}
		
	if (goals->kind() == LIST) {
		if (level == 2) {
			return goals->Cdr();
		} else {
			Node* r = EraseLevelNode(goals->Car(), --level);
			
			return Cons(r, goals->Cdr());
		}
	}

	return goals;
}



Node* Append(Node* nd1, Node* nd2)
{
	if (nd1 == Nil) {
		return nd2;
	} else if (nd1->kind() == ATOM) {
		syserr("Append ATOM\n");
		return nd1;
	}

	Node* n;
	for (n = nd1; ; n = n->Cdr()) {
		if (n->Cdr()->kind() == ATOM) {
			break;
		}
		if (n->Cdr()->kind() != LIST) {
			break;
		}
	}

	((List*)n)->SetCdr(nd2);
	return nd1;
}

Node* Append_rc(Node* nd1, Node* nd2)
{
	if (nd1->Cdr() == Nil) {
		return Cons(nd1->Car(), nd2);
	} else if (nd1->kind() != LIST) {
		return Cons(nd1, nd2);
	}
	return Cons(nd1->Car(), Append_rc(nd1->Cdr(), nd2));
}

// Node functions
Node* Node::Cons(Node* nd)
{
	return NewList(this, nd);
}

// Atom functions
long long Atom::toInt(long long& n)
{
	char* ep;
	int	len, j;
	len = str->length();
	
	for (j = 0; j < len; j++) {
		int	c = str->c_str()[j];
		if (isdigit(c) || c == '+' || c == '-') {
			continue;
		} else {
			return 0;
		}
	}
	long long i 
		= (unsigned long long)strtoll(str->c_str(), &ep, 0);
/*
	if (errno != 0) {
		return 0;
	} 
*/
	if (*ep != 0) {
		return 0;
	} else if (str->c_str() == ep) {
		return 0;
	}else {
		n = i;
		return 1;
	}

}

int Atom::toFloat(long double& f)
{
	char* ep;
	long double d;
	errno = 0;

	if (floatvalue_flag) {
		f = floatvalue;
		return 1;
	}

#if defined(__CYGWIN__) && !defined(__MINGW32__)
#define	strtold(x, y)	strtod(x, y)
#endif
	d = strtold(str->c_str(), &ep);

	if (errno == ERANGE) {
		return 0;
	} else if (str->c_str() == ep) {
		return 0;
	} else if (*ep != 0) {
		return 0;
	} else {
		f = d;

		floatvalue = d;
		floatvalue_flag = 1;

		return 1;
	}
}

int Atom::toImage(long double& i)
{
	char* ep;
	long double d;

	i = 0.0;
	if (floatvalue_flag) {
		i = imagevalue;
		return 1;
	} else if (*str == "i") {
		i = imagevalue = 1.0;
	} else if (*str == "-i") {
		i = imagevalue = -1.0;
	}

	return 0;
}

int Atom::toComplex(std::complex<long double>& c)
{
	if (floatvalue_flag) {
		c = std::complex<long double>(floatvalue, imagevalue);
		return 1;
	}
	if (*str == "i") {
		floatvalue = 0.0;
		imagevalue = 1.0;
		floatvalue_flag = 1;
		c = std::complex<long double>(floatvalue, imagevalue);
		return 1;		
	} else if (*str == "-i") {
		floatvalue = 0.0;
		imagevalue = -1.0;
		floatvalue_flag = 1;
		c = std::complex<long double>(floatvalue, imagevalue);
		return 1;		
	}

	long double f;

	if(!toFloat(f)) {
		return 0;
	}

	floatvalue = f;
	imagevalue = 0.0;
	floatvalue_flag = 1;

	c = std::complex<long double>(floatvalue, imagevalue);
	return 1;
}


int Atom::Eq(Node* nd)
{
	nd = nd->Val();
	if (nd->kind() != ATOM) {
		return 0;
	}

//PrintNode("Atom::Eq ", this);
//printf("Atom::Eq 1: %s \n", str->c_str()); fflush(stdout);
//PrintNode("Atom::Eq ", nd);
//printf("Atom::Eq 2: %s \n", ((Atom*)nd)->str->c_str()); fflush(stdout);

	if (*str == *((Atom*)nd)->str) {
		return 1;
	} else {
		return 0;
	}
}

int Atom::EqStr(std::string s)
{
	if (*str != s) {
		return 0;
	} else {
		return 1;
	}
}

void Atom::printatom(FILE* fd, char* s)
{
	int i;
	int flg=0;

	if (strlen(s) == 0) {
//		printf("\"\"");
		return;
	} 
#if 1
	if (printstrflag) {
		for (i = 0; i < strlen(s); i++) {
			if (s[i] == '\"') {
				fprintf(fd, "\'%s\'", s);
				return;
			}
		}
		if ((s[0] == '(') && (s[1] == ')') && (s[2] == 0)) {
			fprintf(fd, "()");
		} else {
			fprintf(fd, "\"%s\"", s);
		}
		return;
	} else {
		fprintf(fd, "%s", s);
		return;
	}
#else	
	if (strlen(s) == 1) {
		switch (s[0]) {
		case '!':
		case '#':
		case '&':
		case '\%':
		case '(':
		case ')':
		case '*':
		case '+':
		case '-':
		case '/':
		case ':':
		case ';':
		case '<':
		case '=':
		case '>':
		case '?':
		case '[':
		case ']':
		case '{':
		case '}':
		case '|':
		case '\'':
			fprintf(fd, "%s", s);
			return;
		}
	}
	if (strncmp("()", s, 2) == 0) {
		fprintf(fd, "()");
		return;
	} else if (strncmp("==", s, 2) == 0) {
		fprintf(fd, "==");
		return;
	} else if (strncmp("!=", s, 2) == 0) {
		fprintf(fd, "!=");
		return;
	} else if (strncmp(">=", s, 2) == 0) {
		fprintf(fd, ">=");
		return;
	} else if (strncmp("<=", s, 2) == 0) {
		fprintf(fd, "<=");
		return;
	}

	for (i=0; i<strlen(s); i++) {
		switch (s[i]) {
		case ' ':
		case '\t':
		case '!':
		case '#':
		case '&':
		case '%':
		case '(':
		case ')':
		case '*':
//		case '+':
//		case '-':
		case '/':
		case ':':
		case ';':
		case '<':
		case '=':
		case '>':
		case '?':
		case '[':
		case ']':
		case '{':
		case '}':
		case '|':
		case '\'':
			if (flg == 0){
				flg = 1;
			}
		}
		if (s[i] == '\"') {
			if (flg <= 1){
				flg = 2;
			}
		}
		
	}
	if (flg == 0) {
		fprintf(fd, "%s", s);
	} else if (flg == 1) {
		fprintf(fd, "\"%s\"", s);
	} else {
		fprintf(fd, "\'%s\'", s);
	}
#endif

}


// List functions
#ifdef __CYGWIN__
inline Node* List::Val()
{
	Node* nd = Nil->Cons(Nil);
	Node* ndp = nd;
	Node* nl = this;
	Node* newndp = Nil;

	while (nl->Cdr()->kind() == LIST) {
		newndp = Nil->Cons(Nil);
		((List*)ndp)->Set(nl->Car()->Val(), newndp);
		ndp = newndp;
		nl = nl->Cdr();
	}

	((List*)ndp)->Set(nl->Car()->Val(), nl->Cdr()->Val());
	return nd;

	return Car()->Val()->Cons(Cdr()->Val());
}
#endif

int List::Eq(Node* nd)
{
	nd = nd->Val();
	if (nd->kind() != LIST) {
		return 0;
	}
	if ((nd->Car()->Eq(car)) 
	   && (nd->Cdr()->Eq(cdr))) {
		return 1;
	} else {
		return 0;
	}
		
}

// insert nd after List
Node* List::Insert(Node* nd)
{
	Node* n = nd->Cons(Cdr());
	SetCdr(n);
	return this;
}

// delete one List
// if cdr is Atom, set a Nil
void List::Del()
{
	if (Cdr()->kind() != LIST) {
		SetCdr(Nil);
	}
	SetCdr(Cdr()->Cdr());
}

Node* List::Find(Node* nd)
{
	Node* n=this->Val();
	for ( ; ; n=n->Cdr()) {
		if (n->Car()->Eq(nd)) {
			return n;
		}
		if (n->Cdr()->kind() == ATOM) {
			break;
		}
	}
	return Nil; 
	
}



Node* MkList(Node* n1)
{
	return Cons(n1, Nil);
}

Node* MkList(Node* n1, Node* n2)
{
	return Cons(n1, Cons(n2, Nil));
}

Node* MkList(Node* n1, Node* n2, Node* n3)
{
	return Cons(n1, Cons(n2, Cons(n3, Nil)));
}

Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4)
{
	return Cons(n1, Cons(n2, Cons(n3, Cons(n4, Nil))));
}

Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4, Node* n5)
{
	return Cons(n1, Cons(n2, Cons(n3, Cons(n4, Cons(n5, Nil)))));
}

Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4, Node* n5, Node* n6)
{
	return Cons(n1, Cons(n2, Cons(n3, Cons(n4, Cons(n5, Cons(n6, Nil))))));
}

Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4, Node* n5, Node* n6, Node* n7)
{
	return Cons(n1, Cons(n2, Cons(n3, Cons(n4, Cons(n5, Cons(n6, Cons(n7, Nil)))))));
}

#if 1
void PrintNode(Node* nd)
{
	nd->print();
	printf("\n");
}

void PrintNode(const char* s, Node* nd)
{
	printf("%s", s);
	nd->print();
	printf("\n");
}

void PrintNode(const char* s1, Node* nd, const char* s2)
{
	printf("%s", s1);
	nd->print();
	printf("%s\n", s2);
}
#endif
