#ifndef CONTEXT_HPP
#define CONTEXT_HPP

#include "symbol.hpp"
#include <functional>
#include <map>
#include <deque>
#include <vector>

class owned_reference;

struct parser_exception {};

struct no_function_match_exception {
    std::string              prototype;
    std::vector<std::string> available;
};

struct no_binary_operator_match_exception {
    symbol s1;
    symbol s2;
    std::string op;

    no_binary_operator_match_exception(const symbol& s1, const std::string& op, const symbol& s2) :
        s1(s1.clone()), s2(s2.clone()), op(op) {}
};

struct no_unary_operator_match_exception {
    symbol s;
    std::string op;

    no_unary_operator_match_exception(const std::string& op, const symbol& s) :
        s(s.clone()), op(op) {}
};

/*struct expression_node {
    static size_t counter;

    expression_node();
    ~expression_node();

    expression_node* p  = nullptr;
    expression_node* c1 = nullptr;
    expression_node* c2 = nullptr;
    std::string op = "";
    symbol s;
};*/

template<typename ...>
struct register_function_helper;

class interpreter_context {
public :
    interpreter_context(int argc, char* argv[]);

    typedef std::function<symbol_impl*(const std::vector<symbol>&)>   function_t;
    typedef std::function<symbol_impl*(const symbol&, const symbol&)> binary_operator_t;
    typedef std::function<symbol_impl*(const symbol&)>                unary_operator_t;

    bool allow_undefined;
    bool allow_complex;
    bool high_precision;
    size_t digits;

private :
    mutable std::deque<std::string> msg_stack;

    std::map<std::string, symbol> vars;

    std::map<std::string, std::map<std::string, function_t>> funcs;

    std::map<std::string, std::map<std::string, std::map<std::string, binary_operator_t>>> bin_ops;
    std::map<std::string, std::map<std::string, unary_operator_t>>                         un_ops;

public :
    void msg(const std::string& s) const;
    std::string flush_msg() const;

    symbol_impl* evaluate_identifier(const std::string& name) const;
    std::string serialize_variable(const std::string& name) const;
    void set_variable(const std::string& name, symbol&& s);
    void erase_variable(const std::string& name);
    void erase_variables();

    template<typename ... Args>
    void register_function(const char* name, const function_t& func) {
        funcs[name][symbol::types<Args...>()] = func;
    }

    template<typename ... Args>
    void register_function(const std::vector<std::string>& names, const function_t& func) {
        for (const auto& name : names)
            funcs[name][symbol::types<Args...>()] = func;
    }

    template<typename T1, typename T2>
    void register_operator(const std::string& op, const binary_operator_t& func) {
        bin_ops[op][T1::type_name][T2::type_name] = func;
    }

    template<typename T1, typename T2>
    void register_commutative_operator(const std::string& op, const binary_operator_t& func) {
        bin_ops[op][T1::type_name][T2::type_name] = func;
        bin_ops[op][T2::type_name][T1::type_name] = std::bind(func, std::placeholders::_2, std::placeholders::_1);
    }

    template<typename T>
    void register_operator(const std::string& op, const unary_operator_t& func) {
        un_ops[op][T::type_name] = func;
    }

    symbol_impl* call_function(const std::string& name, const std::deque<symbol>& args) const;
    symbol_impl* binary_operator(const std::string& op, const symbol& ts1, const symbol& ts2) const;
    symbol_impl* unary_operator(const std::string& op, const symbol& ts) const;

    symbol_impl* read_complex(const std::string& s) const;
    symbol_impl* read_scalar(const std::string& s) const;
};

#endif
