#include <cstdlib>
#include <string>
#include <sstream>
#include <iostream>
#include <cmath>

using namespace std;
#include "Nodo.hpp"


// ------- Const ----------
Const::Const(double v) : Nodo()
{
	val= v;
}
	
Nodo* Const::copy(void) const
{
	return new Const(val);
}

double Const::eval(double x) const
{
	return val;
}

Nodo* Const::deriv(void) const
{
	return new Const(0);
}

string Const::print(void) const
{
	ostringstream out;
	out << val;
	return out.str();
}


// ------- VarX  ----------
VarX::VarX(void) : Nodo()
{
}

Nodo* VarX::copy(void) const
{
	return new VarX();
}

double VarX::eval(double x) const
{
	return x;
}

string VarX::print(void) const
{
	ostringstream out;
	out << 'x';
	return out.str();
}

Nodo* VarX::deriv(void) const
{
	return new Const(1);
}

// ------- OpBin ----------

OpBin::OpBin(Nodo *i, Nodo *d) : Nodo()
{
	izq= i;
	der= d;
}

// ------- Add ----------

Add::Add(Nodo *i, Nodo *d) : OpBin(i, d)
{
}

Nodo* Add::copy(void) const
{
	return new Add(Lft()->copy(), Rgt()->copy());
}

string Add::print(void) const
{
	ostringstream out;
	out << Lft()->print() << "+ " << Rgt()->print();
	return out.str();
}

double Add::eval(double x) const
{
	return Lft()->eval(x)  +   Rgt()->eval(x);
}

Nodo* Add::deriv(void) const
{
	return new Add(Lft()->deriv(), Rgt()->deriv());
}

// ------- Sub ----------

Sub::Sub(Nodo *i, Nodo *d) : OpBin(i, d)
{
}

Nodo* Sub::copy(void) const
{
	return new Sub(Lft()->copy(), Rgt()->copy());
}

string Sub::print(void) const
{
	ostringstream out;
	out << Lft()->print() << "- ";
	if (Rgt()->type()==ADD || Rgt()->type()==SUB) 
		out << "(" << Rgt()->print() << ")";
	else
		out << Rgt()->print();
	return out.str();
}

double Sub::eval(double x) const
{
	return Lft()->eval(x)  -   Rgt()->eval(x);
}

Nodo* Sub::deriv(void) const
{
	return new Sub(Lft()->deriv(), Rgt()->deriv());
}

// ------- Mul ----------

Mul::Mul(Nodo *i, Nodo *d) : OpBin(i, d)
{
}

Nodo* Mul::copy(void) const
{
	return new Mul(Lft()->copy(), Rgt()->copy());
}

string Mul::print(void) const
{
	ostringstream out;
	if (Lft()->type()==ADD || Lft()->type()==SUB) 
		out << "(" << Lft()->print() << ")";
	else
		out << Lft()->print();
	out << "* ";
	if (Rgt()->type()==ADD || Rgt()->type()==SUB || Rgt()->type()==CHS) 
		out << "(" << Rgt()->print() << ")";
	else
		out << Rgt()->print();
	return out.str();
}

double Mul::eval(double x) const
{
	return Lft()->eval(x) * Rgt()->eval(x);
}

Nodo* Mul::deriv(void) const
{
	return  new Add(
				new Mul(Lft()->copy(), Rgt()->deriv()),
				new Mul(Lft()->deriv(), Rgt()->copy())
			);
}

// ------- Div ----------

Div::Div(Nodo *i, Nodo *d) : OpBin(i, d)
{
}

Nodo* Div::copy(void) const
{
	return new Div(Lft()->copy(), Rgt()->copy());
}

Nodo* Div::deriv(void) const
{
	return new Div(
					new Sub(
							new Mul(
									Rgt()->copy(),
									Lft()->deriv()
								   ),
							new Mul(
									Lft()->copy(),
									Rgt()->deriv()
								   )
						   ),
					new Pow(
							Rgt()->copy(),
							new Const(2)
						)
				);
}

string Div::print(void) const
{
	ostringstream out;
	if (Lft()->type()==ADD || Lft()->type()==SUB) 
		out << "(" << Lft()->print() << ")";
	else
		out << Lft()->print();
	out << "/ ";
	if (Rgt()->type()==ADD || Rgt()->type()==SUB ||
		Rgt()->type()==MUL || Rgt()->type()==DIV 
		|| Rgt()->type()==CHS) 
		out << "(" << Rgt()->print() << ")";
	else
		out << Rgt()->print();
	return out.str();
}

double Div::eval(double x) const
{
	return Lft()->eval(x) / Rgt()->eval(x);
}

// ------- Pow ----------

Pow::Pow(Nodo *i, Nodo *d) : OpBin(i, d)
{
}

Nodo* Pow::copy(void) const
{
	return new Pow(Lft()->copy(), Rgt()->copy());
}

string Pow::print(void) const
{
	ostringstream out;
	int t= Lft()->type();
	if (t==ADD || t==SUB || t==MUL || t==DIV || t==POW || t== CHS)
		out << "(" << Lft()->print() << ")";
	else
		out << Lft()->print();
	out << "^";
	t= Rgt()->type();
	if (t==ADD || t==SUB || t==MUL || t==DIV || t== CHS)
		out << "(" << Rgt()->print() << ")";
	else
		out << Rgt()->print();
	return out.str();
}

double Pow::eval(double x) const
{
	int base=  Lft()->eval(x);
	int expo=  Rgt()->eval(x);
	return pow(base, expo);
}

Nodo* Pow::deriv(void) const
{
	return	new Mul(	
				new Mul(
					Rgt()->copy(),	
					new Pow(
							Lft()->copy(), 
							new Sub(
								Rgt()->copy(),
								new Const(1)
							)
						)
				   ),
				Lft()->deriv()
				);
}

// ------- Chs ----------

Chs::Chs(Nodo *d)
{
	der= d;
}

Nodo* Chs::copy(void) const
{
	return new Chs(Rgt()->copy());
}

double Chs::eval(double x) const
{
	return - Rgt()->eval(x);
}

Nodo* Chs::deriv(void) const
{
	return new Chs(Rgt()->deriv());
}

string Chs::print(void) const
{
	ostringstream out;
	out << "-";
	int td= Rgt()->type();
	if (td==ADD || td==SUB || td==CHS) 
		out << "(" << Rgt()->print() << ")";
	else
		out << Rgt()->print();

	return out.str();
}

