#include <iostream>
#include <cstdlib>
#include <sstream>
#include "Nodo.hpp" 

using namespace std;

const char *name[] = {
	"NUM", "VARX", "VARY", "ADD", "SUB", "MUL", "DIV", "POW", 
	"LPAR", "RPAR", "SEMI", "SIN", "COS", "ATAN", "EXP", "LOG", 
	"DER", "LFT", "RGT", "BAD"
};

class Parser : public std::istringstream {
	private:
		enum {
			NUM, VARX, VARY, ADD, SUB, MUL, DIV, POW, 
			LPAR, RPAR, SEMI, SIN, COS, ATAN, EXP, LOG, 
			DER, RGT, LFT, BAD
		};

		Nodo *lft;
		Nodo *rgt;
		int tok;	// ultimo token leido
		double num;	// ultimo numero leido

		void error(const char mesg[]);
		int gettok(void);
		Nodo* fun(int);
		Nodo* prim(void);
		Nodo* factor(void);
		Nodo* term(void);
		Nodo* expr(void);
	public:
		Parser(std::string, Nodo* lf= NULL, Nodo* rg= NULL);
		Nodo* line(void);
};


void Parser::error(const char mesg[])
{
	std::cerr << "ERROR - " << mesg << std::endl;
	exit(EXIT_FAILURE);
}

int Parser::gettok(void)
{
	*this >> std::ws;	// se come los blancos
	int ch= get();
	if (isdigit(ch)) {
		putback(ch);
		*this >> num;
		tok= NUM;
	}
	else
	if (isalpha(ch)) {
		string name;
		do {
			name += char(ch);	
			ch= get();
		} while(isalpha(ch));
		putback(ch);
		if (name=="x")
			tok= VARX;
		else
		if (name=="sin")
			tok= SIN;
		else
		if (name=="cos")
			tok= COS;
		else
		if (name=="atan")
			tok= ATAN;
		else
		if (name=="exp")
			tok= EXP;
		else
		if (name=="log")
			tok= LOG;
		else
		if (name=="der")
			tok= DER;
		else
		if (name=="rgt")
			tok= RGT;
		else
		if (name=="lft")
			tok= LFT;
		else
			tok= BAD;
	} else
	switch (ch) {
		case 'x':
		case 'X':	tok= VARX;
					break;
		case '+':	tok= ADD;
					break;
		case '-':	tok= SUB;
					break;
		case '*':	tok= MUL;
					break;
		case '/':	tok= DIV;
					break;
		case '^':	tok= POW;
					break;
		case '(':	tok= LPAR;
					break;
		case ')':	tok= RPAR;
					break;
		case ';':	tok= SEMI;
					break;
		case EOF:	tok= SEMI;
					break;
		default:	tok= BAD;
					break;
	}
//	std::cerr << name[tok] << std::endl;
	return tok;
}

Nodo* Parser::fun(int id)
{
	Nodo* p;
	gettok();
	if (tok==LPAR) {
		gettok();
		p= expr();
		if (tok==RPAR) {
			gettok();
			switch(id) {
				case SIN:	p= new Sin(p);
							break;
				case COS:	p= new Cos(p);
							break;
				case ATAN:	p= new Atan(p);
							break;
				case EXP:	p= new Exp(p);
							break;
				case LOG:	p= new Log(p);
							break;
				case DER:	{
							Nodo* d= p->deriv();
							delete p;
							p= d;
							break;
							}
				case LFT:	p= lft->copy();
							break;
				case RGT:	p= rgt->copy();
							break;
			}
		}
	}
	else
		error("falta ')'");
	return p;
}

Nodo* Parser::prim(void)
{
	Nodo* p;
	switch(tok) {
		case NUM:	p= new Const(num);
					gettok();
					break;
		case VARX:
					p= new VarX();
					gettok();
					break;

		case SIN:	p= fun(SIN);
					break;

		case COS:	p= fun(COS);
					break;

		case ATAN:	p= fun(ATAN);
					break;

		case EXP:	p= fun(EXP);
					break;

		case LOG:	p= fun(LOG);
					break;

		case DER:	p= fun(DER);
					break;

		case LFT:	p= fun(LFT);
					break;

		case RGT:	p= fun(RGT);
					break;

		case LPAR:	gettok();
					p= expr();
			 		if (tok==RPAR) {
						gettok();
						break;
					} 
					else
						error("falta ')'");
		default:	error("se espera <prim>");
	}
	return p;
}

Nodo* Parser::factor(void)
{
	Nodo* p= prim();
	while (tok==POW) {
		gettok();
		p= new Pow(p, factor());
	}
	return p;
}

Nodo* Parser::term(void)
{
	Nodo* p= factor();
	for (;;) 
		switch (tok) {
			case MUL:	gettok();
						p= new Mul(p, factor());
						break;
			case DIV:	gettok();
						p= new Div(p, factor());
						break;
			default:	return p;
		}
	return p;
}

Nodo* Parser::expr(void)
{
	Nodo* p; 
	if (tok==ADD) {
		gettok();
		p= term();
	}
	else
	if (tok==SUB) {
		gettok();
		p= new Chs(term());
	}
	else
		p= term();
	for (;;) 
		switch (tok) {
			case ADD:	gettok();
						p= new Add(p, term());
						break;
			case SUB:	gettok();
						p= new Sub(p, term());
						break;
			default:	return p;
		}
	return p;
}

Nodo* Parser::line(void)
{
	gettok();
	Nodo* p=expr();
	if (tok==SEMI) 
		std::cerr << "La expresion es correcta\n";
	else 
		error("falta ';'");
	return p;
}

Parser::Parser(std::string str, Nodo* lf, Nodo* rg) : istringstream(str)
{
	lft= lf;
	rgt= rg;
}


int main(int argc, char** argv) 
{
	std::string linea;
	char ch;
	do {
		std::cin >> ch;
		linea+= ch;
	} while (ch!=';' && ch!='\n');
	Parser p(linea);
	Nodo* e= p.line();
	std::cout << "la expresion: " << e->print() << std::endl;
	Nodo* es= e->simp();
	std::cout << "la expresion simplificada:" << es->print() << std::endl;
	Nodo* d= e->deriv();
	std::cout << "la derivada : " << d->print() << std::endl;
	Nodo* sd= d->simp();
	std::cout << "la derivada simplificada: " << sd->print() << std::endl;
	delete sd;
	delete d;
	delete es;
	delete e;
	return EXIT_SUCCESS;
}
