/*
 * Created by Brett on 17/11/23.
 * Licensed under GNU General Public License V3.0
 * See LICENSE file for license detail
 */
#include <bf_mips_codegen.h>
#include <iostream>

std::string preamble = ".data\n"
                       "\tdata_address: \t.word \t0\n"
                       "\tdata_pointer:\t.word\t0\n"
                       "\tnewline: .asciiz \"\\n\"\n"
                       ".text\n"
                       "setup:\n"
                       "\tli $v0, 9\n"
                       "\tli $a0, 30000\n"
                       "\tsyscall\n"
                       "\t\n"
                       "\tla $t1, data_address\n"
                       "\tsw $v0, ($t1)\n"
                       "\t\n"
                       "\t# t0 - current address (data_pointer)\n"
                       "\t# t1 -\n"
                       "\t# t2 - temp\n"
                       "\tmove $t0, $v0\n"
                       "bf:\n";

void process_print(const std::vector<token_t>& tokens, size_t index);

void codegen(const std::vector<token_t>& tokens, std::ostream& out)
{
    out << preamble;
    size_t index = 0;
    // skip past comments
    if (tokens[index].token == bf_token::OPEN)
        while (tokens[index].token != bf_token::CLOSE)
            index++;
    
    process_print(tokens, index);
    
    size_t sp = 0;
    while (index < tokens.size())
    {
        auto& token = tokens[index++];
        std::string name{"UNNAMED"};
        if (token.name.has_value())
            name = token.name.value();
        switch (token.token)
        {
            case bf_token::INC_DP:
                out       << "\taddi  $t0, $t0, 1\n";
                break;
            case bf_token::DEC_DP:
                out       << "\tsubi  $t0, $t0, 1\n";
                break;
            case bf_token::INC_DV:
                out       << "\tlb    $t1, ($t0)\n"
                          << "\taddi  $t1, $t1, 1\n"
                          << "\tsb    $t1, ($t0)\n";
                break;
            case bf_token::DEC_DV:
                out << "\tlb    $t1, ($t0)\n"
                          << "\tsubi  $t1, $t1, 1\n"
                          << "\tsb    $t1, ($t0)\n";
                break;
            case bf_token::PRINT:
                out       << "\tli    $v0, 11\n"
                          << "\tlb    $a0, ($t0)\n"
                          << "\tsyscall\n";
                break;
            case bf_token::READ:
                out       << "\tli    $v0, 12\n"
                          << "\tsyscall\n"
                          << "\tsb    $v0, ($t0)\n";
                 break;
            case bf_token::OPEN:
                out       << "\tlb    $t1, ($t0)\n"
                          << "\tbeqz  $t1, BF_CLOSE_" << name << "_" << std::to_string(sp) << '\n'
                          << "BF_OPEN_" << name << "_" << std::to_string(sp) << ":\n";
                sp++;
                break;
            case bf_token::CLOSE:
                sp--;
                out       << "\tlb    $t1, ($t0)\n"
                          << "\tbnez  $t1, BF_OPEN_" << name << "_" << std::to_string(sp) << '\n'
                          << "BF_CLOSE_" << name << "_" << std::to_string(sp) << ":\n";
                break;
        }
    }
}

inline void tabulate(size_t v)
{
    for (size_t i = 0; i < v; i++)
        std::cout << '\t';
}

void process_print(const std::vector<token_t>& tokens, size_t index)
{
    size_t sp = 0;
    while (index < tokens.size())
    {
        auto& token = tokens[index++];
        switch (token.token)
        {
            case bf_token::INC_DP:
                tabulate(sp);
                std::cout << "Increase DP\n";
                break;
            case bf_token::DEC_DP:
                tabulate(sp);
                std::cout << "Decrease DP\n";
                break;
            case bf_token::INC_DV:
                tabulate(sp);
                std::cout << "Increase DV\n";
                break;
            case bf_token::DEC_DV:
                tabulate(sp);
                std::cout << "Decrease DV\n";
                break;
            case bf_token::PRINT:
                tabulate(sp);
                std::cout << "Print\n";
                break;
            case bf_token::READ:
                tabulate(sp);
                std::cout << "Read\n";
                break;
            case bf_token::OPEN:
                tabulate(sp);
                std::cout << "If(";
                if (token.name.has_value())
                    std::cout << token.name.value() << "\n";
                else
                    std::cout << "UNNAMED" << "\n";
                sp++;
                break;
            case bf_token::CLOSE:
                sp--;
                tabulate(sp);
                if (token.name.has_value())
                    std::cout << token.name.value();
                else
                    std::cout << "UNNAMED";
                std::cout << ")\n";
                break;
        }
    }
}