#include "context.hpp"
#include "expression.hpp"
#include "variable.hpp"
#include "function.hpp"
#include "scalar.hpp"
#include "array.hpp"
#include "collection.hpp"
#include "range.hpp"
#include "string.hpp"
#include <iostream>
#include <algorithm>

interpreter_context::interpreter_context(args_t& args) :
    prompt("> "),
    ask_exit(false),
    retcode(0),
    allow_undefined(true),
    allow_complex(true),
    high_precision(true),
    digits(6),
    max_cycles(20),
    evaluate_mode(true),
    cycle_detection(false) {

    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);
    set_argument(max_cycles,      "max-cycles", args);
    set_argument(prompt,          "prompt", args);
    check_arguments(std::cout, args);

    align = std::string(prompt.size(), ' ');

    bin_ops_priority = {{"[]"}, {"^"}, {"%", "/", "*"}, {"-", "+"}, {":"}};

    string::reg(*this);
    array<std::string>::reg(*this);

    if (high_precision) {
        scalar_hp::reg(*this);
        array<scalar_hp::ntype>::reg(*this);
        if (allow_complex) {
            complex_hp::reg(*this);
            array<complex_hp::ctype>::reg(*this);
        }
    } else {
        scalar::reg(*this);
        array<scalar::ntype>::reg(*this);
        if (allow_complex) {
            complex::reg(*this);
            array<complex::ctype>::reg(*this);
        }
    }

    collection::reg(*this);
    range::reg(*this);

    register_function("exit", [this](const std::vector<symbol>& args) {
        ask_exit = true;
        return new no_type;
    });

    register_function<scalar_hp>("exit", [this](const std::vector<symbol>& args) {
        retcode = numeric_traits<scalar_hp::ntype>::to_int(args[0].as<scalar_hp>().value);
        ask_exit = true;
        return new no_type;
    });

    register_function("reset", [this](const std::vector<symbol>& args) {
        erase_variables();
        return new no_type;
    });

    register_function<variable>("reset", [this](const std::vector<symbol>& args) {
        erase_variable(args[0].as<variable>().name);
        return new no_type;
    });
}

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

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

void interpreter_context::error(const no_function_match_exception& e) const {
    error("no matching function call to '" + e.name + "(" + e.prototype + ")'");
    note("candidates are:");
    for (const auto& s : e.available) {
        note("'" + e.name + "(" + s + ")'");
    }
}

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

void interpreter_context::note(const std::string& s) const {
    msg_stack.push_back("note: " + 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;
}

void interpreter_context::format_for_output(std::string& s) const {
    std::size_t pos = s.find("\n");
    while (pos != s.npos) {
        s.replace(pos, 1, "\n"+align);
        pos = s.find("\n", pos+1+align.size());
    }

    s.insert(0, align);
}

void interpreter_context::print_help() const {
    std::cout << "Help : WIP" << std::endl;
}

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 {
                error("'" + name + "' is undefined");
                throw parser_exception();
            }
        }

        try {
            return call_function(name, {});
        } catch (no_function_match_exception& e) {
            error(e);
            throw parser_exception();
        } catch (...) {
            throw;
        }
    }

    if (!iter->second.is_litteral())
    {
        auto iter = cycle_eval_list.find(name);
        if (iter == cycle_eval_list.end()) {
            cycle_eval_list[name] = 0u;
        } else {
            ++iter->second;
            if (iter->second >= max_cycles) {
                error("circular dependency detected on '" + name + "' (" + to_string(iter->second) + " accesses detected)");
                note("you can increase the detection threshold with 'max_cycles(n)'");
                throw parser_exception();
            }
        }
    }

    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_name() + ")";
    }
}

void interpreter_context::set_variable(const std::string& name, symbol&& s) {
    auto iter = vars.find(name);
    if (iter == vars.end()) {
        auto iter2 = funcs.find(name);
        if (iter2 != funcs.end()) {
            auto iter3 = iter2->second.find(signature_t::none);
            if (iter3 != iter2->second.end()) {
                error("'" + name + "' is a function and cannot be assigned to");
                throw parser_exception();
            }
        }

        vars.insert(std::make_pair(name, std::move(s)));
    } else {
        iter->second = 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::find_signature(
    std::map<std::string, std::map<signature_t, function_t>>::const_iterator iter_name,
    const signature_t& sig, const std::vector<symbol>& args) const {

    // Try to find a function with the exact same signature.
    const auto& flist = iter_name->second;
    auto iter = flist.find(sig);
    if (iter != flist.end()) {
        return iter->second(args);
    }

    // If it fails, first try to see if the first argument is a collection,
    // so we can try to call a function on each of its elements.
    if (args.size() != 0 && args[0].type_is<collection>()) {
        std::vector<symbol> eval(args.size());
        for (std::size_t i = 1; i < args.size(); ++i) {
            eval[i] = args[i].clone();
        }

        const collection& a = args[0].as<collection>();
        collection* ret = new collection();

        try {
            std::size_t n = a.size();
            for (std::size_t i = 0; i < n; ++i) {
                eval[0] = symbol(a[i].clone());
                ret->push_back(symbol(call_function_no_eval(iter_name->first, eval)));
            }
        } catch (...) {
            delete ret;
            throw;
        }

        return ret;
    }

    // If it fails, first try to see if the first argument is a range,
    // so we can try to call a function on each of its elements.
    if (args.size() != 0 && args[0].type_is<range>()) {
        std::vector<symbol> eval(args.size());
        for (std::size_t i = 1; i < args.size(); ++i) {
            eval[i] = args[i].clone();
        }

        const range& r = args[0].as<range>();

        eval[0] = symbol(r.begin.clone());
        symbol nbegin(call_function_no_eval(iter_name->first, eval));
        eval[0] = symbol(r.end.clone());
        symbol nend(call_function_no_eval(iter_name->first, eval));
        return new range(std::move(nbegin), std::move(nend));
    }

    // If that fails too, try to see if there is a function with
    // 'any' or 'variadic' type in its signature that would match.
    for (auto& func : iter_name->second) {
        const signature_t& test = func.first;
        if (test.size() > sig.size()+1) continue;

        // Check if the provided arguments can be found in any signature
        bool found = true;
        for (std::size_t i = 0; i < sig.size(); ++i) {
            if (i != test.size()) {
                if (test.is<any_type>(i) || test[i] == sig[i]) {
                    continue;
                }
                if (test.is<variadic_type>(i)) {
                    break;
                }
            }

            found = false;
            break;
        }

        if (!found) continue;
        if (test.size() == sig.size()+1 && !test.is<variadic_type>(test.size()-1)) continue;

        return func.second(args);
    }

    // No match
    return nullptr;
}

symbol_impl* interpreter_context::call_function(const std::string& name,
                                                const std::vector<symbol>& args) const {
    // Try to find a function with that name.
    auto iter_name = funcs.find(name);
    if (iter_name == funcs.end()) {
        // No available candidate
        if (allow_undefined) {
            function* f = new function(name);
            for (const symbol& p : args) {
                f->push(p.clone());
            }
            return f;
        } else {
            error("'" + name + "' is undefined");
            throw parser_exception();
        }
    }

    // Find an early match with non evaluated parameters first
    signature_t sig_noeval;
    for (const auto& s : args) {
        sig_noeval.push(s);
    }

    symbol_impl* ret = find_signature(iter_name, sig_noeval, args);
    if (ret) return ret;

    // If no match, evaluate all the arguments and try to match their signature
    std::vector<symbol> eval; eval.reserve(args.size());
    for (const auto& p : args) {
        eval.push_back(p.evaluate(*this));
    }

    signature_t sig;
    for (const auto& s : eval) {
        sig.push(s);
    }

    ret = find_signature(iter_name, sig, eval);
    if (ret) return ret;

    // In the end, see if any argument is not of litteral type,
    // so we simply generate an unevaluated function.
    for (const auto& s : eval) {
        if (!s.is_litteral()) {
            function* f = new function(name);
            for (const symbol& p : args) {
                f->push(p.clone());
            }
            return f;
        }
    }

    // There are no available candidates : error.
    no_function_match_exception e;
    e.name = name;
    e.prototype = sig.serialize();

    for (auto& p : iter_name->second) {
        e.available.push_back(p.first.serialize());
    }

    std::sort(e.available.begin(), e.available.end());

    throw e;
}

symbol_impl* interpreter_context::call_function_no_eval(const std::string& name,
                                                const std::vector<symbol>& args) const {
    // Try to find a function with that name.
    auto iter_name = funcs.find(name);
    if (iter_name == funcs.end()) {
        // If none is found, there are no available candidates : error.
        if (allow_undefined) {
            function* f = new function(name);
            for (const symbol& p : args) {
                f->push(p.clone());
            }
            return f;
        } else {
            error("'" + name + "' is undefined");
            throw parser_exception();
        }
    }

    signature_t sig;
    for (const auto& s : args) {
        sig.push(s);
    }

    symbol_impl* ret = find_signature(iter_name, sig, args);
    if (ret) return ret;

    // In the end, see if any argument is not of litteral type,
    // so we simply generate an unevaluated function.
    for (const auto& s : args) {
        if (!s.is_litteral()) {
            function* f = new function(name);
            for (const symbol& p : args) {
                f->push(p.clone());
            }
            return f;
        }
    }

    // There are no available candidates : error.
    no_function_match_exception e;
    e.name = name;
    e.prototype = sig.serialize();

    for (auto& p : iter_name->second) {
        e.available.push_back(p.first.serialize());
    }

    std::sort(e.available.begin(), e.available.end());

    throw e;
}

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

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

        type_t t1 = s1.type_id();
        type_t t2 = s2.type_id();

        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::static_type_id());
                if (iter_o2 != iter_o1->second.end()) {
                    bop = &iter_o2->second; // t1 op any
                } else {
                    iter_o1 = iter_op->second.find(any_type::static_type_id());
                    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::static_type_id());
            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::static_type_id());
                    if (iter_o2 != iter_o1->second.end()) {
                        bop = &iter_o2->second; // any op any
                    }
                }
            }
        }

        if (!bop) {
            if (!s1.is_litteral() || !s2.is_litteral()) {
                return new binary_expression(std::move(s1), op, std::move(s2));
            } else {
                throw no_binary_operator_match_exception(s1, op, s2);
            }
        }

        return (*bop)(s1, s2);
    } else {
        binary_expression* e = new binary_expression(ts1.clone(), op, ts2.clone());
        if (ts1.is_litteral() && ts2.is_litteral()) {
            evaluate_mode = true;
            symbol_impl* res = e->evaluate(*this);
            evaluate_mode = false;
            return res;
        } else {
            return e;
        }
    }
}

symbol_impl* interpreter_context::unary_operator(const std::string& op, const symbol& ts) const {
    if (evaluate_mode) {
        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);
        }

        type_t t = s.type_id();

        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);
    } else {
        unary_expression* e = new unary_expression(op, ts.clone());
        if (ts.is_litteral()) {
            evaluate_mode = true;
            symbol_impl* res = e->evaluate(*this);
            evaluate_mode = false;
            return res;
        } else {
            return e;
        }
    }
}

std::pair<std::size_t,std::size_t> interpreter_context::binary_operator_priority(const std::string& op) const {
    for (auto iter1 = bin_ops_priority.begin(); iter1 != bin_ops_priority.end(); ++iter1) {
        for (auto iter2 = iter1->begin(); iter2 != iter1->end(); ++iter2) {
            if (*iter2 == op) {
                return std::make_pair(iter1 - bin_ops_priority.begin() + 1, iter2 - iter1->begin());
            }
        }
    }

    return std::make_pair(bin_ops_priority.size(), 0);
}

std::pair<std::size_t,std::size_t> interpreter_context::unary_operator_priority(const std::string& op) const {
    return std::pair<std::size_t,std::size_t>(1, 0);
}

symbol_impl* interpreter_context::evaluate(const std::string& s) const {
    return evaluate_function(s);
}

cycle_detector interpreter_context::detect_cycles() const {
    if (cycle_detection) {
        return cycle_detector(*this, false);
    } else {
        cycle_detection = true;
        return cycle_detector(*this, true);
    }
}

void interpreter_context::end_cycle_detection() const {
    cycle_eval_list.clear();
    cycle_detection = false;
}

cycle_detector::cycle_detector(const interpreter_context& context, bool detect) :
    context(context), detect(detect) {}

cycle_detector::~cycle_detector() {
    if (detect) {
        context.end_cycle_detection();
    }
}
