/*
 * Binary node 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.
 */

#ifndef __BIN_NODE_H
#define __BIN_NODE_H

// kind of node
enum Knode { UNDEF, LIST, ATOM, VAR, PRED };

extern Node* AllNodes;
extern Node* FreeListNodes;
extern Node* FreeAtomNodes;
extern Node* NewAtom(char const* s);
extern Node* NewAtom(char* s);
extern Node* NewAtom(std::string &s);
extern Node* NewAtom(const char* s);
extern Node* NewAtom(long long n);
extern Node* NewAtom(long double n);
extern Node* NewAtom(std::complex<long double> c);

extern Node* Cons(Node* nd1, Node* nd2);
extern Node* GetLeftNode(Node* goals, Node* &goalscdr, int& level);
extern Node* EraseLevelNode(Node* goals, int level);
extern Node* Append(Node* nd1, Node* nd2);
extern Node* Append_rc(Node* nd1, Node* nd2);

extern unsigned int PrCount;
#define	PRCOUNTLIMIT	(PrCount+1000)

class Node {
private:

public:
	unsigned int	ref;	// counter for GC

	Knode		k;	// kind of Node

	Node*		dupp;	// A pointer to prevent repetition of Dup.
				// It points at a new node copied in Dup.
				// use a Dup() function of unify.c.
				

	Node*		nextnode;	// link of all nodes

	unsigned int   prcount;

	inline Node() { nextnode = AllNodes->nextnode; 
			AllNodes->nextnode = this; ref = 1; 
			prcount = 0; }
	virtual ~Node() {};

	inline Knode	kind() { return k; }
	
	// function return a value that extended a variable.
	virtual Node* Val() = 0;

	// lisp functions
	virtual Node* Car() = 0;
	virtual Node* Cdr() = 0;
	virtual Node* Cons(Node* nd);
	virtual int   Eq(Node* nd) = 0;
	virtual int   Nul() = 0;

	virtual Node* Dup() = 0;
	virtual	void  DuppClr() = 0;		// clear dupp
	virtual Node* Insert(Node* nd) = 0;
	virtual void  Del() = 0;
	virtual Node* Find(Node* nd) = 0;

	// print nodes
	virtual void print() {
		print(stdout);
	}
	
	virtual void print(FILE* fd) {
		PrCount++;
		printsub(fd);
	}
	
	virtual void printcdr() {
		printcdr(stdout);
	}

	virtual void printcdr(FILE* fd) {
		PrCount++;
		printcdrsub(fd);
	}
	
	virtual void printsub(FILE* fd){}
	virtual void printcdrsub(FILE* fd){}

	unsigned int	Ref() { return ref; }

	// GC
	virtual void gcmark() = 0;
	
};

// True, Nil
extern Node* True;
extern Node* Nil;

extern int printstrflag;

// Atom is strings
class Atom : public Node {
private:
	std::string*	str;
	long double	floatvalue;
	long double	imagevalue;
	int		floatvalue_flag;

public:
	Atom(const char* s) {k = ATOM; str = new std::string(s);
				floatvalue_flag=0;}
	Atom(char* s) {	k = ATOM; str = new std::string(s); 
				floatvalue_flag=0;}
	Atom(std::string &s) {k = ATOM; str = new std::string(s);
				floatvalue_flag=0;}
	Atom(long long n) 
		{
			char* m = (char*)malloc(256);
			if (m == NULL) {
				syserr("can't get memory of Atom");
			}
			snprintf(m, 256-1, "%lld", n); 
			k = ATOM; 
			str = new std::string(m); 
			free(m);
			floatvalue_flag = 0;
			floatvalue = 0.0;
			imagevalue = 0.0;
		}
	Atom(long double n)
		{
			char* m = (char*)malloc(256);
			if (m == NULL) {
				syserr("can't get memory of Atom");
			}
			snprintf(m, 256-1, "%Lg", n); 
			k = ATOM; 
			str = new std::string(m); 
			free(m);
			floatvalue_flag = 1;
			floatvalue = n;
			imagevalue = 0.0;
		}

	Atom(std::complex<long double> c)
		{
			char* m = (char*)malloc(256);
			if (m == NULL) {
				syserr("can't get memory of Atom");
			}

			if (c.imag() == 0.0) {
				if (c.real() == 0.0) {
					snprintf(m, 256-1, "0");
				} else {
					snprintf(m, 256-1, "%Lg",  c.real());
				}
			} else if (c.imag() == 1.0) {
				if (c.real() == 0.0) {
					snprintf(m, 256-1, "i");
				} else {
					snprintf(m, 256-1, "%Lg+i", c.real());
				}
			} else if (c.imag() == -1.0) {
				if (c.real() == 0.0) {
					snprintf(m, 256-1, "-i");
				} else {
					snprintf(m, 256-1, "%Lg-i", c.real());
				}
			} else if (c.real() == 0.0) {
				if (c.imag() == 0.0) {
					snprintf(m, 256-1, "0");
				} else {
					snprintf(m, 256-1, "%Lg*i", c.imag()); 
				}
			} else {
				if (c.imag() < 0.0) {
					snprintf(m, 256-1, "%Lg%Lg*i", 
						c.real(), c.imag()); 
				} else {
					snprintf(m, 256-1, "%Lg+%Lg*i", 
						c.real(), c.imag()); 
				}
			}

			k = ATOM; 
			str = new std::string(m); 
			free(m);
			floatvalue_flag = 1;
			floatvalue = c.real();
			imagevalue = c.imag();
		}
			
	
	~Atom() { delete str; str = NULL; }

	Node*	Val() { return this; };

	int	toString(std::string& s) {s = *str; return 1;};
	std::string* string() { return str; }
	
	long long toInt(long long& n);

	int	FloatFlag() { return floatvalue_flag; };
	
	int	toFloat(long double& f);
	int	toImage(long double& i);
	int	toComplex(std::complex<long double>& c);

	// lisp functions
	Node* Car() { return this; }
	Node* Cdr() { return this; }
	// cdr of Nil is Nil .

	int   Eq(Node* nd);
	int   EqStr(std::string s);
	int   Nul() { return (this == Nil); }

	Node* Dup() { return this; }
	void  DuppClr() { dupp = NULL; }

	Node* Insert(Node* nd) {
		if (this == Nil) {
			return nd;
		}
		syserr("Insert Node to Atom"); 
		return Nil; 
	}
	void  Del() { syserr("Del Node of Atom"); }
	Node* Find(Node* nd) { syserr("Find Node for Atom"); return Nil; }

	
	// print nodes
	void printatom(FILE* fd, char* s);

	void printsub(FILE* fd) {
		if (prcount >= PRCOUNTLIMIT) {
			fprintf(fd, " ...");
			return;
		} else {
			if (prcount < PrCount) {
				prcount = PrCount;
			} else {
				prcount++;
			}
		}
		printatom(fd, (char*)str->c_str());
	}

	void printcdrsub(FILE* fd) {
		if (this != Nil) {
			if (prcount >= PRCOUNTLIMIT) {
				fprintf(fd, " ...");
				return;
			} else {
				if (prcount < PrCount) {
					prcount = PrCount;
				} else {
					prcount++;
				}
			}
			fprintf(fd, " :");
			printatom(fd, (char*)str->c_str());
		}
	}

	// GC
	void gcmark() { ref++; }

	void Set(const char* s) {*str = s; floatvalue_flag=0;}
	void Set(char* s) {*str = s; floatvalue_flag=0;}
	void Set(std::string &s) {*str = s; floatvalue_flag=0;}
	void Set(long long n) 
		{
			char* m = (char*)malloc(256);
			if (m == NULL) {
				syserr("can't get memory of Atom");
			}
			snprintf(m, 256-1, "%lld", n); 
			*str = m; 
			free(m);
			floatvalue_flag = 0;
			floatvalue = 0.0;
			imagevalue = 0.0;
		}
	void Set(long double n)
		{
			char* m = (char*)malloc(256);
			if (m == NULL) {
				syserr("can't get memory of Atom");
			}
			snprintf(m, 256-1, "%Lg", n); 
			*str = m; 
			free(m);
			floatvalue_flag = 1;
			floatvalue = n;
			imagevalue = 0.0;
		}

	void Set(std::complex<long double> c)
		{
			char* m = (char*)malloc(256);
			if (m == NULL) {
				syserr("can't get memory of Atom");
			}

			if (c.imag() == 0.0) {
				if (c.real() == 0.0) {
					snprintf(m, 256-1, "0");
				} else {
					snprintf(m, 256-1, "%Lg",  c.real());
				}
			} else if (c.imag() == 1.0) {
				if (c.real() == 0.0) {
					snprintf(m, 256-1, "i");
				} else {
					snprintf(m, 256-1, "%Lg+i", c.real());
				}
			} else if (c.imag() == -1.0) {
				if (c.real() == 0.0) {
					snprintf(m, 256-1, "-i");
				} else {
					snprintf(m, 256-1, "%Lg-i", c.real());
				}
			} else if (c.real() == 0.0) {
				if (c.imag() == 0.0) {
					snprintf(m, 256-1, "0");
				} else {
					snprintf(m, 256-1, "%Lg*i", c.imag()); 
				}
			} else {
				if (c.imag() < 0.0) {
					snprintf(m, 256-1, "%Lg%Lg*i", 
						c.real(), c.imag()); 
				} else {
					snprintf(m, 256-1, "%Lg+%Lg*i", 
						c.real(), c.imag()); 
				}
			}

			*str = m; 
			free(m);
			floatvalue_flag = 1;
			floatvalue = c.real();
			imagevalue = c.imag();
		}

};


// binary list
class List : public Node {
private:
	Node* car;
	Node* cdr;

public:
	inline List() { k = LIST; car = Nil; cdr = Nil; }
	inline List(Node* n1, Node* n2) { k = LIST; car = n1; cdr = n2; }
	inline ~List() {}

#ifdef __CYGWIN__
	Node* Val();
#else
	inline Node* Val() { return Car()->Val()->Cons(Cdr()->Val()); }
#endif

	// Node* Val();

	// lisp functions
	inline Node* Car() { return car; }
	inline Node* Cdr() { return cdr; }
	int   Eq(Node* nd);
	int   Nul() { return 0; }

	Node* Dup() {
		if (dupp == NULL) 
			dupp = Car()->Dup()->Cons(Cdr()->Dup());
		return dupp; 
	}
	void  DuppClr() {
		dupp = NULL; 
		Car()->DuppClr();
		Cdr()->DuppClr(); 
	}
	Node* Insert(Node* nd);
	void  Del();
	Node* Find(Node* nd);

	// print nodes
	void printsub(FILE* fd) {
		if (prcount >= PRCOUNTLIMIT) {
			fprintf(fd, "( ...)");
			return;
		} else {
			if (prcount < PrCount) {
				prcount = PrCount;
			} else {
				prcount++;
			}
		}
		fprintf(fd, "(");
		Car()->printsub(fd);
		Cdr()->printcdrsub(fd);
		fprintf(fd, ")"); 
	}

	void printcdrsub(FILE* fd) {
		if (prcount >= PRCOUNTLIMIT) {
			fprintf(fd, " ...");
			return;
		} else {
			if (prcount < PrCount) {
				prcount = PrCount;
			} else {
				prcount++;
			}
		}
		if (this != Nil) {
			fprintf(fd, " ");
			Car()->printsub(fd);
			Cdr()->printcdrsub(fd);
		}
	}

	void Set(Node* n1, Node* n2) { car = n1; cdr = n2; }
	void SetCar(Node* n) { car = n; }
	void SetCdr(Node* n) { cdr = n; }

	// GC
	inline void gcmark() { 
		//if (ref) return;
		ref++; car->gcmark(); cdr->gcmark(); 
	}
};

extern Node* MkList(Node* n1);
extern Node* MkList(Node* n1, Node* n2);
extern Node* MkList(Node* n1, Node* n2, Node* n3);
extern Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4);
extern Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4, Node* n5);
extern Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4, Node* n5, Node* n6);
extern Node* MkList(Node* n1, Node* n2, Node* n3, Node* n4, Node* n5, Node* n6, Node* n7);


extern void PrintNode(Node* nd);
extern void PrintNode(const char* s, Node* nd);
extern void PrintNode(const char* s1, Node* nd, const char* s2);

#endif // __BIN_NODE_H
