/*=============================================================================
    Copyright (c) 2001-2014 Joel de Guzman

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/
#include "compiler.hpp"
#include "vm.hpp"
#include <boost/variant/apply_visitor.hpp>
#include <boost/assert.hpp>
#include <iostream>
#include <set>
#include <iostream>

namespace client { namespace code_gen
{
    void program::op(int a)
    {
        code.push_back(a);
    }

    void program::op(int a, int b)
    {
        code.push_back(a);
        code.push_back(b);
    }

    void program::op(int a, int b, int c)
    {
        code.push_back(a);
        code.push_back(b);
        code.push_back(c);
    }

    int const* program::find_var(std::string const& name) const
    {
        auto i = variables.find(name);
        if (i == variables.end())
            return 0;
        return &i->second;
    }

    void program::add_var(std::string const& name)
    {
        std::size_t n = variables.size();
        variables[name] = int(n);
    }

    void program::print_variables(std::vector<int> const& stack) const
    {
        for (auto const& p : variables)
        {
            std::cout << "    " << p.first << ": " << stack[p.second] << std::endl;
        }
    }

    void program::print_assembler() const
    {
        auto pc = code.begin();

        std::vector<std::string> locals(variables.size());
        typedef std::pair<std::string, int> pair;
        for (pair const& p : variables)
        {
            locals[p.second] = p.first;
            std::cout << "local       "
                << p.first << ", @" << p.second << std::endl;
        }

        std::map<std::size_t, std::string> lines;
        std::set<std::size_t> jumps;

        while (pc != code.end())
        {
            std::string line;
            std::size_t address = pc - code.begin();

            switch (*pc++)
            {
                case op_neg:
                    line += "      op_neg";
                    break;

                case op_not:
                    line += "      op_not";
                    break;

                case op_add:
                    line += "      op_add";
                    break;

                case op_sub:
                    line += "      op_sub";
                    break;

                case op_mul:
                    line += "      op_mul";
                    break;

                case op_div:
                    line += "      op_div";
                    break;

                case op_eq:
                    line += "      op_eq";
                    break;

                case op_neq:
                    line += "      op_neq";
                    break;

                case op_lt:
                    line += "      op_lt";
                    break;

                case op_lte:
                    line += "      op_lte";
                    break;

                case op_gt:
                    line += "      op_gt";
                    break;

                case op_gte:
                    line += "      op_gte";
                    break;

                case op_and:
                    line += "      op_and";
                    break;

                case op_or:
                    line += "      op_or";
                    break;

                case op_load:
                    line += "      op_load     ";
                    line += locals[*pc++];
                    break;

                case op_store:
                    line += "      op_store    ";
                    line += locals[*pc++];
                    break;

                case op_int:
                    line += "      op_int      ";
                    line += std::to_string(*pc++);
                    break;

                case op_true:
                    line += "      op_true";
                    break;

                case op_false:
                    line += "      op_false";
                    break;

                case op_jump:
                    {
                        line += "      op_jump     ";
                        std::size_t pos = (pc - code.begin()) + *pc++;
                        if (pos == code.size())
                            line += "end";
                        else
                            line += std::to_string(pos);
                        jumps.insert(pos);
                    }
                    break;

                case op_jump_if:
                    {
                        line += "      op_jump_if  ";
                        std::size_t pos = (pc - code.begin()) + *pc++;
                        if (pos == code.size())
                            line += "end";
                        else
                            line += std::to_string(pos);
                        jumps.insert(pos);
                    }
                    break;

                case op_stk_adj:
                    line += "      op_stk_adj  ";
                    line += std::to_string(*pc++);
                    break;
            }
            lines[address] = line;
        }

        std::cout << "start:" << std::endl;
        for (auto const& l : lines)
        {
            std::size_t pos = l.first;
            if (jumps.find(pos) != jumps.end())
                std::cout << pos << ':' << std::endl;
            std::cout << l.second << std::endl;
        }

        std::cout << "end:" << std::endl;
    }

    bool compiler::operator()(unsigned int x) const
    {
        program.op(op_int, x);
        return true;
    }

    bool compiler::operator()(bool x) const
    {
        program.op(x ? op_true : op_false);
        return true;
    }

    bool compiler::operator()(ast::variable const& x) const
    {
        int const* p = program.find_var(x.name);
        if (p == 0)
        {
            error_handler(x, "Undeclared variable: " + x.name);
            return false;
        }
        program.op(op_load, *p);
        return true;
    }

    bool compiler::operator()(ast::operation const& x) const
    {
        if (!boost::apply_visitor(*this, x.operand_))
            return false;
        switch (x.operator_)
        {
            case ast::op_plus: program.op(op_add); break;
            case ast::op_minus: program.op(op_sub); break;
            case ast::op_times: program.op(op_mul); break;
            case ast::op_divide: program.op(op_div); break;

            case ast::op_equal: program.op(op_eq); break;
            case ast::op_not_equal: program.op(op_neq); break;
            case ast::op_less: program.op(op_lt); break;
            case ast::op_less_equal: program.op(op_lte); break;
            case ast::op_greater: program.op(op_gt); break;
            case ast::op_greater_equal: program.op(op_gte); break;

            case ast::op_and: program.op(op_and); break;
            case ast::op_or: program.op(op_or); break;
            default: BOOST_ASSERT(0); return false;
        }
        return true;
    }

    bool compiler::operator()(ast::unary const& x) const
    {
        if (!boost::apply_visitor(*this, x.operand_))
            return false;
        switch (x.operator_)
        {
            case ast::op_negative: program.op(op_neg); break;
            case ast::op_not: program.op(op_not); break;
            case ast::op_positive: break;
            default: BOOST_ASSERT(0); return false;
        }
        return true;
    }

    bool compiler::operator()(ast::expression const& x) const
    {
        if (!boost::apply_visitor(*this, x.first))
            return false;
        for (ast::operation const& oper : x.rest)
        {
            if (!(*this)(oper))
                return false;
        }
        return true;
    }

    bool compiler::operator()(ast::assignment const& x) const
    {
        if (!(*this)(x.rhs))
            return false;
        int const* p = program.find_var(x.lhs.name);
        if (p == 0)
        {
            error_handler(x.lhs, "Undeclared variable: " + x.lhs.name);
            return false;
        }
        program.op(op_store, *p);
        return true;
    }

    bool compiler::operator()(ast::variable_declaration const& x) const
    {
        int const* p = program.find_var(x.assign.lhs.name);
        if (p != 0)
        {
            error_handler(x.assign.lhs, "Duplicate variable: " + x.assign.lhs.name);
            return false;
        }
        bool r = (*this)(x.assign.rhs);
        if (r) // don't add the variable if the RHS fails
        {
            program.add_var(x.assign.lhs.name);
            program.op(op_store, *program.find_var(x.assign.lhs.name));
        }
        return r;
    }

    bool compiler::operator()(ast::statement const& x) const
    {
        return boost::apply_visitor(*this, x);
    }

    bool compiler::operator()(ast::statement_list const& x) const
    {
        for (auto const& s : x)
        {
            if (!(*this)(s))
                return false;
        }
        return true;
    }

    bool compiler::operator()(ast::if_statement const& x) const
    {
        if (!(*this)(x.condition))
            return false;
        program.op(op_jump_if, 0);                      // we shall fill this (0) in later
        std::size_t skip = program.size()-1;            // mark its position
        if (!(*this)(x.then))
            return false;
        program[skip] = int(program.size()-skip);       // now we know where to jump to (after the if branch)

        if (x.else_)                                    // We got an alse
        {
            program[skip] += 2;                         // adjust for the "else" jump
            program.op(op_jump, 0);                     // we shall fill this (0) in later
            std::size_t exit = program.size()-1;        // mark its position
            if (!(*this)(*x.else_))
                return false;
            program[exit] = int(program.size()-exit);   // now we know where to jump to (after the else branch)
        }

        return true;
    }

    bool compiler::operator()(ast::while_statement const& x) const
    {
        std::size_t loop = program.size();              // mark our position
        if (!(*this)(x.condition))
            return false;
        program.op(op_jump_if, 0);                      // we shall fill this (0) in later
        std::size_t exit = program.size()-1;            // mark its position
        if (!(*this)(x.body))
            return false;
        program.op(op_jump,
            int(loop-1) - int(program.size()));         // loop back
        program[exit] = int(program.size()-exit);       // now we know where to jump to (to exit the loop)
        return true;
    }

    bool compiler::start(ast::statement_list const& x) const
    {
        program.clear();
        // op_stk_adj 0 for now. we'll know how many variables we'll have later
        program.op(op_stk_adj, 0);

        if (!(*this)(x))
        {
            program.clear();
            return false;
        }
        program[1] = int(program.nvars());              // now store the actual number of variables
        return true;
    }
}}
