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

math_parser_impl::math_parser_impl(args_t& args) :
    context(new interpreter_context(args)) {
}

math_parser_impl::~math_parser_impl() {
    delete context;
}

void math_parser_impl::clear() {
    while (!symbol_stack.empty()) {
        pop_symbol();
    }

    while (!collection_stack.empty()) {
        delete collection_stack.top();
        collection_stack.pop();
    }

    while (!function_stack.empty()) {
        delete function_stack.top();
        function_stack.pop();
    }
}

void math_parser_impl::binary_operator(const std::string& op) {
    symbol s2 = pop_symbol();
    symbol s1 = pop_symbol();

    binary_expression* e = new binary_expression(std::move(s1), op, std::move(s2));
    if (s1.is_litteral() && s2.is_litteral()) {
        make_leaf(symbol(e).evaluate(*context).release());
    } else {
        make_leaf(e);
    }
}

void math_parser_impl::unary_operator(const std::string& op) {
    symbol s = pop_symbol();

    unary_expression* e = new unary_expression(op, std::move(s));
    if (s.is_litteral()) {
        make_leaf(symbol(e).evaluate(*context).release());
    } else {
        make_leaf(e);
    }
}

symbol math_parser_impl::pop_symbol() {
    symbol s(symbol_stack.top());
    symbol_stack.pop();
    return s;
}

void math_parser_impl::make_leaf(symbol_impl* s) {
    symbol_stack.push(s);
}

void math_parser_impl::make_string(const std::string& s) {
    std::string tmp = s;
    replace(tmp, "\\\\", "\\");
    replace(tmp, "\\\"", "\"");
    replace(tmp, "\\n", "\n");
    replace(tmp, "\\r", "\r");
    replace(tmp, "\\t", "\t");

    make_leaf(new string(tmp));
}

void math_parser_impl::make_scalar(const std::string& s) {
    if (s.back() == 'i') {
        make_leaf(context->read_complex(s));
    } else {
        make_leaf(context->read_scalar(s));
    }
}

void math_parser_impl::make_variable(const std::string& s) {
    make_leaf(new variable(s));
}

void math_parser_impl::error(const std::string& s) {
    context->error(s);
    throw parser_exception();
}

void math_parser_impl::warning(const std::string& s) {
    context->warning(s);
}

void math_parser_impl::note(const std::string& s) {
    context->note(s);
}

void math_parser_impl::array_end() {
    collection_end();
    symbol s = pop_symbol();
    collection& c = s.as<collection>();

    std::vector<symbol> args;
    args.push_back(c.pop_front());
    args.push_back(std::move(s));
    bool old = context->allow_undefined;
    try {
        context->allow_undefined = false;
        make_leaf(context->call_function("array", args));
        context->allow_undefined = old;
    } catch (no_function_match_exception& e) {
        context->error("cannot create an array of '"+args[0].type_name()+"'");
        context->allow_undefined = old;
        throw parser_exception();
    } catch (...) {
        context->allow_undefined = old;
        throw;
    }
}

void math_parser_impl::index_star_push() {
    if (context->high_precision) {
    collection_stack.top()->push_back(symbol(new range(
        symbol(new scalar_hp(numeric_traits<scalar_hp::ntype>::zero)),
        symbol(new scalar_hp(-numeric_traits<scalar_hp::ntype>::one))
    )));
    } else {
    collection_stack.top()->push_back(symbol(new range(
        symbol(new scalar(numeric_traits<scalar::ntype>::zero)),
        symbol(new scalar(-numeric_traits<scalar::ntype>::one))
    )));
    }
}

void math_parser_impl::index_end() {
    collection* c = collection_stack.top();
    if (c->size() == 1) {
        make_leaf(c->pop_back().release());
    } else {
        make_leaf(c);
    }

    collection_stack.pop();
}

void math_parser_impl::collection_begin() {
    collection_stack.push(new collection());
}

void math_parser_impl::collection_push() {
    collection_stack.top()->push_back(pop_symbol());
}

void math_parser_impl::collection_end() {
    make_leaf(collection_stack.top());
    collection_stack.pop();
}

void math_parser_impl::function_begin(const std::string& id) {
    function_stack.push(new function(id));
}

void math_parser_impl::function_param() {
    function_stack.top()->push(pop_symbol());
}

void math_parser_impl::function_end() {
    make_leaf(function_stack.top());
    function_stack.pop();
}

void math_parser_impl::assign_begin(const std::string& id) {
    assign_stack.push(id);
}

void math_parser_impl::assign_begin_no_eval() {
    context->evaluate_mode = false;
}

void math_parser_impl::assign_end() {
    std::string assign_id = assign_stack.top(); assign_stack.pop();
    try {
        context->set_variable(assign_id, pop_symbol().evaluate(*context));
        make_variable(assign_id);
    } catch (parser_exception) {
        context->erase_variable(assign_id);
    }
}

void math_parser_impl::assign_end_no_eval() {
    std::string assign_id = assign_stack.top(); assign_stack.pop();
    context->set_variable(assign_id, pop_symbol());
    make_variable(assign_id);
}

bool isvoid(const symbol& s) {
    if (s.type_is<no_type>()) return true;
    if (s.type_is<collection>()) {
        const collection& a = s.as<collection>();
        for (auto& st : a) {
            if (!isvoid(st)) return false;
        }

        return true;
    }

    return false;
}

void math_parser_impl::print_expression() {
    if (symbol_stack.empty()) return;

    try {
        symbol s = pop_symbol();
        symbol st = s.evaluate(*context);
        if (!isvoid(st)) {
            std::string out = st.serialize(*context);
            context->format_for_output(out);
            std::cout << out << std::endl;
            context->set_variable("_", std::move(st));
        }
    } catch (parser_exception) {}
}

void math_parser_impl::finish() {
    std::string s = context->flush_msg();

    if (!s.empty()) {
        std::cout << s << std::endl;
    }

    clear();
}

bool math_parser_impl::allow_complex() const {
    return context->allow_complex;
}

const std::string& math_parser_impl::prompt() const {
    return context->prompt;
}

bool math_parser_impl::ask_exit(int& retcode) const {
    if (context->ask_exit) {
        retcode = context->retcode;
    }

    return context->ask_exit;
}

void math_parser_impl::set_parse_function(const std::function<bool(const std::string&)>& p) {
    parse = p;
    context->evaluate_function = [this](const std::string& s) -> symbol_impl* {
        std::size_t stack_size = symbol_stack.size();
        try {
            if (parse(s)) {
                int diff = symbol_stack.size() - stack_size;
                if (diff == 0) {
                    return new no_type();
                } else if (diff == 1) {
                    return pop_symbol().release();
                } else {
                    collection* a = new collection();
                    while (diff != 0) {
                        a->push_front(pop_symbol());
                        --diff;
                    }
                    return a;
                }
            }
        } catch (parser_exception&) {}

        return new no_type();
    };
}
