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

math_parser_impl::math_parser_impl(int argc, char* argv[]) : context(new interpreter_context(argc, argv)) {
    align = "  ";
}

math_parser_impl::~math_parser_impl() {
    delete context;

    while (!symbol_stack.empty()) {
        pop_symbol();
    }

    while (!array_stack.empty()) {
        delete array_stack.top();
        array_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->msg("error: "+s);
    throw parser_exception();
}

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

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

void math_parser_impl::array_begin() {
    array_stack.push(new array());
}

void math_parser_impl::array_push() {
    array_stack.top()->push(pop_symbol());
}

void math_parser_impl::array_end() {
    make_leaf(array_stack.top());
    array_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);
}

void math_parser_impl::destroy(const std::string& id) {
    context->erase_variable(id);
}

void math_parser_impl::destroy_all() {
    context->erase_variables();
}

void math_parser_impl::print_identifier(const std::string& id) {
    std::cout << format_output(context->serialize_variable(id)) << std::endl;
}

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

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

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

std::string math_parser_impl::format_output(const std::string& s) {
    std::string tmp = s;

    size_t pos = tmp.find("\n");
    while (pos != tmp.npos) {
        tmp.replace(pos, 1, "\n"+align);
        pos = tmp.find("\n", pos+1+align.size());
    }

    return align + tmp;
}

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

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

    while (!symbol_stack.empty())
        pop_symbol();

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

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

    context->evaluate_mode = true;
}

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

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* {
        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 {
                    array* a = new array();
                    while (diff != 0) {
                        a->push(pop_symbol());
                        --diff;
                    }
                    a->reverse();
                    return a;
                }
            }
        } catch (parser_exception&) {}

        return new no_type();
    };
}
