#pragma once
#ifndef CALC_AST_H
#define CALC_AST_H

#include <deque>
#include <map>
#include <memory>
#include <string>

namespace calc {

class AST;
using Variables = std::map<std::string, double>;
using Functions = std::map<std::string, std::unique_ptr<AST>>;

class AST {
    public:
        virtual double eval(const Variables&, const Functions&) const = 0;
};

class Number : public AST {
    public:
        Number(double d);
        double eval(const Variables&, const Functions&) const override;

    private:
        double m_val;
};

class Var : public AST {
    public:
        Var(std::string name);
        double eval(const Variables& ctx, const Functions&) const override;

        std::string getName() const;

    private:
        std::string m_name;
};

class Binop : public AST {
    public:
        Binop(std::unique_ptr<AST>&& left, std::string op, std::unique_ptr<AST>&& right);
        double eval(const Variables& ctx, const Functions&) const override;

    private:
        std::unique_ptr<AST> m_left;
        std::unique_ptr<AST> m_right;
        std::string m_op;
};

class Unop : public AST {
    public:
        Unop(std::string op, std::unique_ptr<AST>&& operand);
        double eval(const Variables&, const Functions&) const override;

    private:
        std::string m_op;
        std::unique_ptr<AST> m_operand;
};

class FormalParameters : public AST {
    public:
        double eval(const Variables&, const Functions&) const override;

        void push_back(std::unique_ptr<AST>&& arg);

        std::deque<std::unique_ptr<AST>>::const_iterator begin();
        std::deque<std::unique_ptr<AST>>::const_iterator end();

    private:
        std::deque<std::unique_ptr<AST>> m_args;
};

class FunctionArguments : public AST {
    public:
        double eval(const Variables&, const Functions&) const override;

        std::unique_ptr<AST>& operator[](std::size_t idx);
        void push_back(std::unique_ptr<AST>&& arg);
        std::size_t size() const;

    private:
        std::deque<std::unique_ptr<AST>> m_args;
};

class FunctionCall : public AST {
    public:
        FunctionCall(std::string name, std::unique_ptr<FunctionArguments>&& arguments);
        double eval(const Variables&, const Functions&) const override;

    private:
        std::string m_name;
        std::unique_ptr<FunctionArguments> m_arguments;
};

class Function : public AST {
    public:
        Function(std::deque<std::string> formalParams, std::unique_ptr<AST>&& body);
        double eval(const Variables&, const Functions&) const override;

        const std::deque<std::string>& getParams() const;

    private:
        std::deque<std::string> m_formalParams;
        std::unique_ptr<AST> m_body;
};

} /* calc  */ 

#endif //CALC_AST_H