#ifndef CONTEXT_HPP
#define CONTEXT_HPP

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

template<typename ...>
struct register_function_helper;

class cycle_detector;

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;

    std::vector<std::vector<std::string>> bin_ops_priority;

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::vector<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;

    std::pair<size_t,size_t> binary_operator_priority(const std::string& op) const;
    std::pair<size_t,size_t> unary_operator_priority(const std::string& op) const;

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

    cycle_detector detect_cycles() const;

private :
    friend class cycle_detector;

    mutable std::set<std::string> cycle_eval_list;
    void end_cycle_detection() const;
};

class cycle_detector {
    const interpreter_context& context;

public :
    cycle_detector(const interpreter_context& context);
    ~cycle_detector();
};

#endif
