#ifndef EXPRESSION_HPP_
#define EXPRESSION_HPP_

#include <iostream>
#include <memory>

// TODO: you may add more classes, functions, etc. however you want
class Value {
public:
    using pointer = std::unique_ptr<Value>;

    Value() = default;

    virtual ~Value() noexcept = default;

    Value(const Value&) = delete;
    Value& operator=(const Value&) = delete;

    Value& operator=(Value&&) noexcept;
    Value(Value&&) noexcept;

    friend pointer operator+(const Value& lhs, const Value& rhs) {
        return lhs.add(rhs);
    }

    friend pointer operator*(const Value& lhs, const Value& rhs) {
        return lhs.mul(rhs);
    }

    friend std::ostream& operator<<(std::ostream& os, const Value& v) {
        v.print(os);
        return os;
    }

protected:
    virtual pointer add(const Value& rhs) const = 0;
    virtual pointer mul(const Value& rhs) const = 0;
    virtual void print(std::ostream& os) const = 0;
};

class Expression
{
public:
    using pointer = std::unique_ptr<Expression>;

    Expression() = default;

    virtual ~Expression() noexcept = default;

    Expression(const Expression&) = delete;
    Expression& operator=(const Expression&) = delete;

    Expression& operator=(Expression&&) noexcept;
    Expression(Expression&&) noexcept;

    virtual Value::pointer evaluate() const = 0;

    friend std::ostream& operator<<(std::ostream& os, const Expression& e) {
        auto value = e.evaluate();

        return os << *value;
    }
};

class ValueExpression : public Expression
{
public:
    // TODO: Implement ValueExpression; you can change it to a template class if you want
    // represents a value (literal) in an expression
};

class AddExpression : public Expression
{
public:
    // TODO: Implement AddExpression
    // represents an addition operation in an expression
};

// etc.

// dummy functions that you want to replace with your own implementation:
inline Expression::pointer make_value(int) {
    return nullptr;
}

inline Expression::pointer make_value(double) {
    return nullptr;
}

inline Expression::pointer make_addition(Expression::pointer, Expression::pointer) {
    return nullptr;
}

inline Expression::pointer make_multiplication(Expression::pointer, Expression::pointer) {
    return nullptr;
}

#endif // EXPRESSION_HPP_