#ifndef EXPRESSION_H
#define EXPRESSION_H
#include <map>
#include <vector>
#include <stdexcept>
#include <cmath>
class CalcNode
{
public:
    
    
    virtual ~CalcNode()
    {
    }
    
    
    virtual double	evaluate() const = 0;
    
    
    virtual void	print(std::ostream &os, unsigned int depth=0) const = 0;
    
    static inline std::string indent(unsigned int d)
    {
	return std::string(d * 2, ' ');
    }
};
class CNConstant : public CalcNode
{
    
    double	value;
    
public:
    
    explicit CNConstant(double _value)
	: CalcNode(), value(_value)
    {
    }
    virtual double evaluate() const
    {
	return value;
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << value << std::endl;
    }
};
class CNNegate : public CalcNode
{
    
    CalcNode* 	node;
public:
    explicit CNNegate(CalcNode* _node)
	: CalcNode(), node(_node)
    {
    }
    virtual ~CNNegate()
    {
	delete node;
    }
    virtual double evaluate() const
    {
	return - node->evaluate();
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << "- negate" << std::endl;
	node->print(os, depth+1);
    }
};
class CNAdd : public CalcNode
{
    
    CalcNode* 	left;
    
    CalcNode* 	right;
    
public:
    explicit CNAdd(CalcNode* _left, CalcNode* _right)
	: CalcNode(), left(_left), right(_right)
    {
    }
    virtual ~CNAdd()
    {
	delete left;
	delete right;
    }
    virtual double evaluate() const
    {
	return left->evaluate() + right->evaluate();
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << "+ add" << std::endl;
	left->print(os, depth+1);
	right->print(os, depth+1);
    }
};
class CNSubtract : public CalcNode
{
    
    CalcNode* 	left;
    
    CalcNode* 	right;
    
public:
    explicit CNSubtract(CalcNode* _left, CalcNode* _right)
	: CalcNode(), left(_left), right(_right)
    {
    }
    virtual ~CNSubtract()
    {
	delete left;
	delete right;
    }
    virtual double evaluate() const
    {
	return left->evaluate() - right->evaluate();
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << "- subtract" << std::endl;
	left->print(os, depth+1);
	right->print(os, depth+1);
    }
};
class CNMultiply : public CalcNode
{
    
    CalcNode* 	left;
    
    CalcNode* 	right;
    
public:
    explicit CNMultiply(CalcNode* _left, CalcNode* _right)
	: CalcNode(), left(_left), right(_right)
    {
    }
    virtual ~CNMultiply()
    {
	delete left;
	delete right;
    }
    virtual double evaluate() const
    {
	return left->evaluate() * right->evaluate();
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << "* multiply" << std::endl;
	left->print(os, depth+1);
	right->print(os, depth+1);
    }
};
class CNDivide : public CalcNode
{
    
    CalcNode* 	left;
    
    CalcNode* 	right;
    
public:
    explicit CNDivide(CalcNode* _left, CalcNode* _right)
	: CalcNode(), left(_left), right(_right)
    {
    }
    virtual ~CNDivide()
    {
	delete left;
	delete right;
    }
    virtual double evaluate() const
    {
	return left->evaluate() / right->evaluate();
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << "/ divide" << std::endl;
	left->print(os, depth+1);
	right->print(os, depth+1);
    }
};
class CNModulo : public CalcNode
{
    
    CalcNode* 	left;
    
    CalcNode* 	right;
    
public:
    explicit CNModulo(CalcNode* _left, CalcNode* _right)
	: CalcNode(), left(_left), right(_right)
    {
    }
    virtual ~CNModulo()
    {
	delete left;
	delete right;
    }
    virtual double evaluate() const
    {
	return std::fmod(left->evaluate(), right->evaluate());
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << "% modulo" << std::endl;
	left->print(os, depth+1);
	right->print(os, depth+1);
    }
};
class CNPower : public CalcNode
{
    
    CalcNode* 	left;
    
    CalcNode* 	right;
    
public:
    explicit CNPower(CalcNode* _left, CalcNode* _right)
	: CalcNode(), left(_left), right(_right)
    {
    }
    virtual ~CNPower()
    {
	delete left;
	delete right;
    }
    virtual double evaluate() const
    {
	return std::pow(left->evaluate(), right->evaluate());
    }
    virtual void print(std::ostream &os, unsigned int depth) const
    {
	os << indent(depth) << "^ power" << std::endl;
	left->print(os, depth+1);
	right->print(os, depth+1);
    }
};
class CalcContext
{
public:
    
    typedef std::map<std::string, double> variablemap_type;
    
    variablemap_type		variables;
    
    
    std::vector<CalcNode*>	expressions;
    
    ~CalcContext()
    {
	clearExpressions();
    }
    
    void	clearExpressions()
    {
	for(unsigned int i = 0; i < expressions.size(); ++i)
	{
	    delete expressions[i];
	}
	expressions.clear();
    }
    
    bool	existsVariable(const std::string &varname) const
    {
	return variables.find(varname) != variables.end();
    }
    
    
    
    double	getVariable(const std::string &varname) const
    {
	variablemap_type::const_iterator vi = variables.find(varname);
	if (vi == variables.end())
	    throw(std::runtime_error("Unknown variable."));
	else
	    return vi->second;
    }
};
#endif