#include "parser_impl.hpp"
#include "symbol.hpp"
#include "context.hpp"
#include "scalar.hpp"
#include "array.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();
    if (s1.is_litteral() && s2.is_litteral())
        make_leaf(context->binary_operator(op, s1, s2));
    else
        make_leaf(new binary_expression(std::move(s1), op, std::move(s2)));
}

void math_parser_impl::unary_operator(const std::string& op) {
    symbol s = pop_symbol();
    if (s.is_litteral())
        make_leaf(context->unary_operator(op, s));
    else
        make_leaf(new unary_expression(op, std::move(s)));
}

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_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::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_id = id;
}

void math_parser_impl::assign_end() {
    try {
        context->set_variable(assign_id, pop_symbol());
        symbol s = symbol(new variable(assign_id)).evaluate(*context);

        std::string c = s.serialize(*context);
        std::string t = s.type();
        std::cout << align << assign_id << " = " << c << " (" << t << ")" << std::endl;
    } catch (parser_exception) {
        context->erase_variable(assign_id);
    }
}

void math_parser_impl::assign_end_no_eval() {
    context->set_variable(assign_id, pop_symbol());
}

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 << align << 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);
        std::string t = st.type();
        if (t != no_type::type_name) {
            std::string c = st.serialize(*context);
            std::cout << align << c << " (" << t << ")" << 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;

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

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