#include "lexer.hpp"
#include <chrono>

static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;

using json = nlohmann::json;

static std::deque<std::string> tokenNames;
std::deque<std::string> L_getTokenNames(){
    return tokenNames;
}
// static std::string tokenNames[] = {"char","string","real","hex","int",",",":",";","(",")","[","]","{","}",".","->","<-","=>","+","-","*","/","==","!=","<=","<",">=",">","&&","||","=","if","then","else","from","to","break","inttype","realtype","continue","return","type","void","nul","true","false","boolean","chartype","%","&","shorttype","function","loop","jsload","sizeof","class","private","public","protected","repeat","jsexport","id","uminus","lower_than_else"};
static std::map<std::string, unsigned> operatorsPrecedence;
std::map<std::string, unsigned> L_getOperators(){
    return operatorsPrecedence;
}

static std::map<std::string, unsigned> keywordNames;
std::map<std::string, unsigned> L_getKeywords(){
    return keywordNames;
}

std::wstring excludeKeywordRegex = L"";

std::vector<std::vector<int>> lettersInLines;
static bool tabSensitive = false;
std::string languageCode = "";

L_errorPos L_getErrorPos(int cursorPos){
    L_errorPos result;
    // std::cout << cursorPos << std::endl;
    for(int i = 0; i < lettersInLines.size(); i++){
        for(int j = 0; j < lettersInLines.at(i).size(); j++){
            if(cursorPos - lettersInLines.at(i).at(j) > 0){
                cursorPos -= lettersInLines.at(i).at(j);
            }
            else{
                result.fileNum = i;
                result.fileName = PP_getFilename(i);
                // std::cout << i << " line55"<< std::endl;
                result.lineNum = PP_getLinesInFile(j + 1).lineNum;
                result.columnNum = cursorPos + 1;
                return result;
                break;
            }
        }
    }
}


std::vector<std::pair<std::string, std::wregex>> L_genTokenNames(const std::string &inputFname){
    std::ifstream input(inputFname);
    json j;
    input >> j;
    json keywords = j["tokens"]["keywords"];
    json ops = j["tokens"]["ops"];
    tabSensitive = j["config"]["tabsensitive"].get<bool>();
    languageCode = j["config"]["name"].get<std::string>();
    std::wstring charRegex = converter.from_bytes(j["tokens"]["char"].get<std::string>());
    std::wstring intRegex = converter.from_bytes(j["tokens"]["int"].get<std::string>());
    std::wstring separatorRegex = L"[^" + charRegex.substr(1, charRegex.size() - 2) + intRegex.substr(1, intRegex.size() - 2) + L"]";
    std::vector<std::pair<std::string, std::wregex>> regexMap;
    regexMap.push_back(std::make_pair("comment", std::wregex(converter.from_bytes(j["tokens"]["comment"].get<std::string>()))));
    unsigned keywordIndex = 0;
    for(const auto &item: keywords.items()){
        std::wstring reg = converter.from_bytes(item.value()["value"].get<std::string>());
        regexMap.push_back(std::make_pair(item.value()["name"], std::wregex(L"(?:^|"+separatorRegex+L")("+reg+L")(?:$|"+separatorRegex+L")")));
        // std::wcout << L"(?:^|"+separatorRegex+L")("+reg+L")(?:$|"+separatorRegex+L")" << std::endl;
        keywordNames[item.value()["name"]] = keywordIndex;
        keywordIndex += 1;
        tokenNames.push_back(item.value()["name"]);
        std::wstring word = L"";
        std::wstringstream wordStr(converter.from_bytes(item.value()["value"].get<std::string>()));
        while(wordStr >> word){
            // std::cout << converter.to_bytes(word) << std::endl;
            excludeKeywordRegex += word + L"|";
        }
    }
    excludeKeywordRegex.pop_back();
    excludeKeywordRegex = L"(" + excludeKeywordRegex + L")";
    {
        regexMap.push_back(std::make_pair("char", std::wregex(L"('(...|..|.)')")));
        tokenNames.push_back("char");

        std::wstring stringRegex = L"(" + converter.from_bytes(j["tokens"]["string"].get<std::string>()) + L")";
        regexMap.push_back(std::make_pair("string", std::wregex(stringRegex)));
        tokenNames.push_back("string");
    }
    for(const auto &item: ops.items()){
        std::wstring reg = converter.from_bytes(item.value()["regex"].get<std::string>());
        if(item.value()["name"] == "."){
            reg = converter.from_bytes(item.value()["regex"].get<std::string>() + "\\s*($|[^0-9])");
        }
        // std::wcout << reg << std::endl;
        regexMap.push_back(std::make_pair(item.value()["name"], std::wregex(reg)));
        tokenNames.push_back(item.value()["name"]);
        operatorsPrecedence[item.value()["name"]] = item.value()["precedence"];
    }
    // token hardcode
    {
        std::wstring idSeparatorStart = L"(?:^|[^"+charRegex.substr(1, charRegex.size() - 2) + intRegex.substr(1, intRegex.size() - 2)+L"\\.\'\"])";
        std::wstring idSeparatorEnd = L"(?:$|[^"+charRegex.substr(1, charRegex.size() - 2) + intRegex.substr(1, intRegex.size() - 2)+L"\\.\'\"])";
        std::wstring idRegex = idSeparatorStart+L"((" + charRegex + L"[" + charRegex.substr(1, charRegex.size() - 2) + intRegex.substr(1, intRegex.size() - 2) + L"]*))"+idSeparatorEnd;
        regexMap.push_back(std::make_pair("id", std::wregex(idRegex)));
        tokenNames.push_back("id");

        regexMap.push_back(std::make_pair("real", std::wregex(idSeparatorStart+L"((" + intRegex + L"+\\." + intRegex + L"+f?))"+idSeparatorEnd)));
        tokenNames.push_back("real");

        std::wstring hexRegex = converter.from_bytes(j["tokens"]["hex"].get<std::string>());
        regexMap.push_back(std::make_pair("int", std::wregex(idSeparatorStart+L"((" + hexRegex + L"|" + intRegex + L"+))"+idSeparatorEnd)));
        tokenNames.push_back("int");

        tokenNames.push_back("eof");
        tokenNames.push_back("");
    }
    return regexMap;
}

L_tokenList L_Lexer(const char* filename1, const char* filename2, std::string mode)
{
    L_tokenList tokenList;
    std::vector<std::pair<std::string, std::wregex>> regexMap = L_genTokenNames(filename2);

    std::ifstream programFile(filename1);
    std::wregex space(L"^((?:\\s)+)");
    std::wsmatch match;
    std::stringstream tempStream;
    tempStream << programFile.rdbuf();
    std::wstring program = converter.from_bytes(tempStream.str());
    std::wstringstream wprogramStream(program);
    std::wstring programLine = L"";
    int linePos = 0;
    while(std::getline(wprogramStream, programLine)){
        linePos += 1;
        if(PP_getLinesInFile(linePos).fileNum == lettersInLines.size()){
            lettersInLines.push_back(std::vector<int>());
        }
        lettersInLines.at(PP_getLinesInFile(linePos).fileNum).push_back(programLine.size() + 1);
    }
    std::wstring::const_iterator programIt(program.cbegin());
    while(std::regex_search(programIt, program.cend(), match, std::wregex(L"(\\$(.*)\\$)"))){
        // std::cout << "aaaaaaaa" << std::endl;
        L_token token = std::make_shared<L_token_>();
        token -> kind = "id";
        token -> start = match[1].first - program.cbegin();
        token -> end = match[1].second - program.cbegin();
        programIt = match[1].second;
        if(mode == "compile")
        {
            std::stringstream idStream(converter.to_bytes(match[2].str()));
            json idJson;
            idStream >> idJson;
            if(idJson[languageCode].is_string()){
                token -> u.id = (char *)malloc(idJson[languageCode].get<std::string>().size() + 1);
                std::strcpy(token -> u.id, idJson[languageCode].get<std::string>().c_str());
                // std::cout << token -> u.id << std::endl;
            }
            else if(idJson["default"].is_string()){
                token -> u.id = (char *)malloc(idJson["default"].get<std::string>().size() + 1);
                std::strcpy(token -> u.id, idJson[languageCode].get<std::string>().c_str());
            }
            else{
                EM_error(token -> start, "id.lang");
            }
        }
        else{
            token -> u.id = (char *)malloc(converter.to_bytes(match[2].str()).size() + 3);
            std::strcpy(token -> u.id, converter.to_bytes(match[2].str()).c_str());
            // std::cout << token -> u.id << std::endl;
        }
        std::fill(program.begin() + (match[1].first - program.cbegin()), program.begin() + (match[1].second - program.cbegin()), L' ');
        tokenList.push_back(token);
    }
    for(const auto &regex:regexMap){
        // std::cout << regex.first << std::endl;
        std::wstring::const_iterator programIt(program.cbegin());
        while(std::regex_search(programIt, program.cend(), match, regex.second)){
            L_token token = std::make_shared<L_token_>();
            token -> kind = regex.first;
            token -> start = match[1].first - program.cbegin();
            token -> end = match[1].second - program.cbegin();
            programIt = match[1].second;
            // replace match with same space
            if(regex.first == "char"){
                std::strcpy(token -> u.charr, converter.to_bytes(match[2].str()).c_str());
            }
            else if(regex.first == "int"){
                if(std::stoll(match[2].str()) == 0 && match[2].str() != L"0"){
                    token -> u.intt = std::stoul(match[2].str(), nullptr, 16);
                }
                else{
                    token -> u.intt = std::stoll(match[2].str());
                }
            }
            else if(regex.first == "real"){
                token -> u.real = std::stod(match[2].str());
            }
            else if(regex.first == "string"){
                token -> u.stringg = (char *)malloc(converter.to_bytes(match[2].str()).size() + 1);
                std::strcpy(token -> u.stringg, converter.to_bytes(match[2].str()).c_str());
            }
            else if(regex.first == "id"){
                token -> u.id = (char *)malloc(converter.to_bytes(match[2].str()).size() + 1);
                std::strcpy(token -> u.id, converter.to_bytes(match[2].str()).c_str());
            }
            if(regex.first != "comment"){
                tokenList.push_back(token);
            }
            std::fill(program.begin() + (match[1].first - program.cbegin()), program.begin() + (match[1].second - program.cbegin()), L' ');
        }
        // std::cout << converter.to_bytes(program) << std::endl;
    }
    std::sort(tokenList.begin(), tokenList.end(), [](L_token a, L_token b){
        return a -> start < b -> start;
    });
    int indentSize = 0;
    L_token eof = std::make_shared<L_token_>();
    eof -> kind = "eof";
    eof -> start = program.size();
    eof -> end = program.size();
    tokenList.push_back(eof);
    return tokenList;
}

L_token L_delimToken(delimDataTy data){
    L_token p = std::make_shared<L_token_>();
    p -> kind = "^^^";
    p -> delimData = data;
    return p;
}