#include "Lexesis/backends/cpp.h"

#include <cassert>

namespace lxs { namespace backends {

    CppBackend::CppBackend() : Backend()
    {}

    CppBackend::~CppBackend()
    {}

    bool CppBackend::canProcessLang(std::string lang) {
        for (char& c : lang)
            c = std::tolower(c);
        return lang == "c++" || lang == "cpp" || lang == "cxx";
    }

    void CppBackend::generateLexer(
            std::function<std::unique_ptr<std::ostream>(std::string)> getOstreamForFileName,
            std::string lexerName,
            const DFA& dfa)
    {
        assert(lexerName.length());

        std::unique_ptr<std::ostream> headerStream = getOstreamForFileName(lexerName + ".h");
        std::unique_ptr<std::ostream> implementationStream  = getOstreamForFileName(lexerName + ".cpp");

        std::map<const std::string, templ::TemplateContext> topLevel;
        
        lexerName[0] = std::toupper(lexerName[0]);
        topLevel["name"] = templ::make_string(lexerName);

        //The DEADSTATE gets a brand new state: dfa.numStates
        topLevel["reject_state"] = templ::make_string(std::to_string(dfa.numStates));
        topLevel["num_states"] = templ::make_string(std::to_string(dfa.numStates + 1));

        auto transition_indices = buildTransitionIndices(dfa);
        topLevel["trans_idx"] = transformTransitionIndices(transition_indices.first);
        topLevel["num_transitions_per_state"] = templ::make_string(std::to_string(transition_indices.second));

        topLevel["table"] = buildTable(dfa, transition_indices.first, transition_indices.second);

        topLevel["token_types"] = buildTokenList(dfa);

        templ::TemplateContext topLevelMap = templ::make_map(topLevel);

        doTemplate(*headerStream, "c++/lexer.h", topLevelMap);
        doTemplate(*implementationStream, "c++/lexer.cpp", topLevelMap);
    }

    templ::TemplateContext CppBackend::buildTable(const DFA& dfa, const std::vector<char>& transition_idx, int num_transitions_per_state) const {
        std::map<char, char> reverse_trans;
        for (int i = 0; i < 256; i++) {
            reverse_trans[transition_idx[i]] = i;
        }

        std::vector<templ::TemplateContext> table;
        
        for (State s = 0; s < dfa.numStates; s++) {
            std::vector<templ::TemplateContext> row;
            for (int i = 0; i < num_transitions_per_state; i++) {
                State to = dfa.delta.find(s)->second.find(reverse_trans[i])->second;
                if (to == deadState)
                    to = dfa.numStates; //The new Dead state name
                row.push_back(templ::make_map({{"state", templ::make_string(std::to_string(to))}}));
            }
        }
        // NOTE: there is no transition table entry for the dead state
        //       since the algorithm should never loop in the dead state

        return templ::make_array(table);
    }

    templ::TemplateContext CppBackend::buildTokenList(const DFA& dfa) const {
        std::set<std::string> tokens;
        for (const auto& pr : dfa.acceptingToken) {
            tokens.insert(pr.second);
        }
        tokens.insert("ignore");

        std::vector<templ::TemplateContext> tokenList;
        for (const std::string& s : tokens) {
            tokenList.push_back(templ::make_map({{"type", templ::make_string(s)}}));
        }
        return templ::make_array(tokenList);
    }

    std::pair<std::vector<char>, int> CppBackend::buildTransitionIndices(const DFA& /* dfa */) const {
        //FIXME: this is not really optimal ;-)
        std::vector<char> transition_idx;
        for (int i = 0; i < 256; i++)
            transition_idx.push_back(i);
        return std::make_pair(transition_idx, 256);
    }

    templ::TemplateContext CppBackend::transformTransitionIndices(std::vector<char> transition_indices) const {
        std::vector<templ::TemplateContext> new_trans;
        for (auto& i : transition_indices) {
            new_trans.push_back(templ::make_map({{"trans", templ::make_string("(char)" + std::to_string(i))}}));
        }
        return templ::make_array(new_trans);
    }

} } //namespace lxs::backends
