#include "context.hpp"
#include "expression.hpp"
#include "variable.hpp"
#include "function.hpp"
#include "scalar.hpp"
#include "array.hpp"
#include <iostream>

std::map<std::string,std::string> read_arguments(int argc, char** argv) {
    std::vector<std::string> args(argc-1);
    for (int i = 1; i < argc; ++i)
        args[i-1] = argv[i];

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

    std::vector<std::string>::iterator iter;
    for (iter = args.begin(); iter != args.end(); ++iter) {
        std::string s = *iter;
        if (s[0] != '-') {
            std::cout << "warning: parsing '" << s << "'\n"
                         "warning: arguments must be of the form : '-argname=value' or '-argname'" << std::endl;
            continue;
        }

        s.erase(0,1);

        size_t eqpos = s.find("=");
        if (eqpos != s.npos)
            map[s.substr(0, eqpos)] = s.substr(eqpos+1);
        else
            map[s] = "";
    }

    return map;
}

template<class T>
bool set_argument(T& arg, const std::string& name, const std::map<std::string,std::string>& args) {
    std::map<std::string,std::string>::const_iterator iter = args.find(name);
    if (iter != args.end()) {
        std::istringstream ss(iter->second);
        return ss >> arg;
    }

    return false;
}

bool argument_is_provided(const std::string& name, const std::map<std::string,std::string>& args) {
    return args.find(name) != args.end();
}

interpreter_context::interpreter_context(int argc, char* argv[]) :
    allow_undefined(true),
    allow_complex(true),
    high_precision(true),
    digits(6) {
    auto args = read_arguments(argc, argv);
    set_argument(allow_undefined, "allow-undefined", args);
    set_argument(allow_complex, "allow-complex", args);
    set_argument(high_precision, "high-precision", args);
    set_argument(digits, "digits", args);

    if (high_precision) {
        scalar_hp::reg(*this);
        if (allow_complex)
            complex_hp::reg(*this);
    } else {
        scalar::reg(*this);
        if (allow_complex)
            complex::reg(*this);
    }

    array::reg(*this);
}

void interpreter_context::msg(const std::string& s) const {
    msg_stack.push_back(s);
}

std::string interpreter_context::flush_msg() const {
    if (msg_stack.empty())
        return "";

    std::string m = msg_stack.front();
    msg_stack.pop_front();

    while (!msg_stack.empty()) {
        m += '\n' + msg_stack.front();
        msg_stack.pop_front();
    }

    return m;
}

symbol_impl* interpreter_context::evaluate_identifier(const std::string& name) const {
    auto iter = vars.find(name);
    if (iter == vars.end()) {
        auto iter2 = funcs.find(name);
        if (iter2 == funcs.end()) {
            if (allow_undefined)
                return new variable(name);
            else {
                msg("error: '" + name + "' is undefined");
                throw parser_exception();
            }
        }

        auto iter3 = iter2->second.find("");
        if (iter3 == iter2->second.end()) {
            msg("error: '" + name + "' is a function");
            throw parser_exception();
        }

        return iter3->second({});
    }

    return iter->second.evaluate(*this).release();
}

std::string interpreter_context::serialize_variable(const std::string& name) const {
    auto iter = vars.find(name);
    if (iter == vars.end())
        return "'" + name + "' is undefined";
    else
        return iter->second.serialize(*this) + " (" + iter->second.type() + ")";
}

void interpreter_context::set_variable(const std::string& name, symbol&& s) {
    vars[name] = std::move(s);
}

void interpreter_context::erase_variable(const std::string& name) {
    vars.erase(name);
}

void interpreter_context::erase_variables() {
    vars.clear();
}

symbol_impl* interpreter_context::read_complex(const std::string& s) const {
    if (high_precision) {
        complex_hp::ntype v;
        std::stringstream ss(s);
        ss >> v;

        return new complex_hp(0.0, v);
    } else {
        complex::ntype v;
        std::stringstream ss(s);
        ss >> v;

        return new complex(0.0, v);
    }
}

symbol_impl* interpreter_context::read_scalar(const std::string& s) const {
    if (high_precision) {
        scalar_hp::ntype v;
        std::stringstream ss(s);
        ss >> v;

        return new scalar_hp(v);
    } else {
        scalar::ntype v;
        std::stringstream ss(s);
        ss >> v;

        return new scalar(v);
    }
}

symbol_impl* interpreter_context::call_function(const std::string& name,
                                                const std::deque<symbol>& args) const {
    auto iter_name = funcs.find(name);
    if (iter_name == funcs.end()) {
        if (allow_undefined) {
            function* f = new function(name);
            for (const symbol& p : args)
                f->push(p.clone());
            return f;
        } else {
            msg("error: '" + name + "' is undefined");
            throw parser_exception();
        }
    }

    std::vector<symbol> eval; eval.reserve(args.size());
    for (const auto& p : args)
        eval.push_back(p.evaluate(*this));

    std::string arg_key;
    for (const auto& s : eval)
        arg_key += s.type() + ", ";

    const auto& func = iter_name->second;
    auto iter = func.find(arg_key);
    if (iter == func.end()) {
        if (!arg_key.empty())
            arg_key.erase(arg_key.size()-2, 2);

        no_function_match_exception e;
        e.prototype = arg_key;

        for (auto p : func) {
            std::string s = p.first;
            if (!s.empty())
                s.erase(s.size()-2, 2);

            e.available.push_back(s);
        }

        throw e;
    }

    return iter->second(eval);
}

symbol_impl* interpreter_context::binary_operator(const std::string& op, const symbol& ts1, const symbol& ts2) const {
    symbol s1 = ts1.evaluate(*this);
    symbol s2 = ts2.evaluate(*this);

    if (!s1.is_litteral() || !s2.is_litteral())
        return new binary_expression(std::move(s1), op, std::move(s2));

    auto iter_op = bin_ops.find(op);
    if (iter_op == bin_ops.end())
        throw no_binary_operator_match_exception(s1, op, s2);

    std::string t1 = s1.type();
    std::string t2 = s2.type();

    const binary_operator_t* bop = nullptr;

    auto iter_o1 = iter_op->second.find(t1);
    if (iter_o1 != iter_op->second.end()) {
        auto iter_o2 = iter_o1->second.find(t2);
        if (iter_o2 != iter_o1->second.end()) {
            bop = &iter_o2->second; // t1 op t2
        } else {
            iter_o2 = iter_o1->second.find(any_type::type_name);
            if (iter_o2 != iter_o1->second.end()) {
                bop = &iter_o2->second; // t1 op any
            } else {
                iter_o1 = iter_op->second.find(any_type::type_name);
                if (iter_o1 != iter_op->second.end()) {
                    iter_o2 = iter_o1->second.find(t2);
                    if (iter_o2 != iter_o1->second.end())
                        bop = &iter_o2->second; // any op t2
                }
            }
        }
    } else {
        iter_o1 = iter_op->second.find(any_type::type_name);
        if (iter_o1 != iter_op->second.end()) {
            auto iter_o2 = iter_o1->second.find(t2);
            if (iter_o2 != iter_o1->second.end()) {
                bop = &iter_o2->second; // any op t2
            } else {
                iter_o2 = iter_o1->second.find(any_type::type_name);
                if (iter_o2 != iter_o1->second.end())
                    bop = &iter_o2->second; // any op any
            }
        }
    }

    if (!bop)
        throw no_binary_operator_match_exception(s1, op, s2);

    return (*bop)(s1, s2);
}

symbol_impl* interpreter_context::unary_operator(const std::string& op, const symbol& ts) const {
    symbol s = ts.evaluate(*this);

    if (!s.is_litteral())
        return new unary_expression(op, std::move(s));

    auto iter_op = un_ops.find(op);
    if (iter_op == un_ops.end())
        throw no_unary_operator_match_exception(op, s);

    std::string t = s.type();

    auto iter_o1 = iter_op->second.find(t);
    if (iter_o1 == iter_op->second.end())
        throw no_unary_operator_match_exception(op, s);

    return iter_o1->second(s);
}
