#define _LEAK_
#include "leak_detector.h"

#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <set>
#include <chrono>
#include <functional>

#include "SgUtils.h"
#include "CommonBlock.h"
#include "graph_calls.h"

#include "dvm.h"
#include "IR.h"
#include "CFGraph.h"

#define DEB_PRINT 1

using namespace std;
using namespace SAPFOR;

using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::milliseconds;

typedef SAPFOR::BasicBlock BBlock;

int BBlock::lastNumBlock = 0;

BBlock::BasicBlock(IR_Block* item)
{
    num = lastNumBlock++;
    instructions.push_back(item);
    item->setBasicBlock(this);
}

BBlock::BasicBlock(const BBlock& copyFrom)
{
    num = lastNumBlock++;
    for (auto& ir : copyFrom.getInstructions())
    {
        instructions.push_back(new IR_Block(*ir));
        instructions.back()->setBasicBlock(this);
    }

    //need to replace with actual BB
    next = copyFrom.next;
    prev = copyFrom.prev;
}

void BBlock::addInstruction(IR_Block* item)
{
    instructions.push_back(item);
    item->setBasicBlock(this);
}

int BBlock::removePrev(BBlock* removed)
{
    auto it = std::remove(prev.begin(), prev.end(), removed);
    auto r = prev.end() - it;
    prev.erase(it, prev.end());
    return r;
}

int BBlock::removeNext(BBlock* removed)
{
    auto it = std::remove(next.begin(), next.end(), removed);
    auto r = next.end() - it;
    next.erase(it, next.end());
    return r;
}

BBlock::~BasicBlock()
{
    for (auto& instr : instructions)
        delete instr;
}

static void dumpSets(const map<SAPFOR::Argument*, set<int>>& sets)
{
    map<string, set<int>> byName;
    map<string, string> types;
    for (auto& elem : sets)
    {
        /*if (elem.first->getType() == CFG_ARG_TYPE::REG)
            continue;*/

        byName[elem.first->getValue()] = elem.second;
        types[elem.first->getValue()] = elem.first->getMemTypeStr();
    }

    if (byName.size() != sets.size())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (auto& elem : byName)
    {
        printf("  %s (%s): ", elem.first.c_str(), types[elem.first].c_str());
        for (auto& d : elem.second)
            printf("%d ", d);
        printf("\n");
    }
}

static void debPrint(BBlock* curr, bool withRD)
{
    printf(" -> [next BB]: ");
    for (auto& next : curr->getNext())
        printf("%d ", next->getNumber());
    printf("\n");
    printf(" -> [prev BB]: ");
    for (auto& next : curr->getPrev())
        printf("%d ", next->getNumber());
    printf("\n");

    if (withRD)
    {
        printf("IN sets:\n");
        dumpSets(curr->getRD_In());

        printf("OUT sets:\n");
        dumpSets(curr->getRD_Out());
    }
    else
    {
        printf("IN sets %d\n", (int)curr->getRD_In().size());
        printf("OUT sets %d\n", (int)curr->getRD_Out().size());
    }

}

static void debPrint(const string& fName, const vector<BBlock*>& bblocks, const vector<IR_Block*>& blocks, bool withRD)
{
    printf("============================================\n");
    printf("IR and CFG for '%s' function\n", fName.c_str());
    printf("============================================\n");

    BBlock* curr = blocks[0]->getBasicBlock();
    for (auto& block : blocks)
    {
        auto instr = block->getInstruction();
        auto oper = instr->getOperator();

        if (curr != block->getBasicBlock())
        {
            debPrint(curr, withRD);
            printf("\n");

            curr = block->getBasicBlock();
        }
        printf("[%d] [line %d] [BB %d] %s\n", instr->getNumber(), oper ? oper->lineNumber() : -1, block->getBasicBlock()->getNumber(), instr->dump().c_str());
    }
    debPrint(curr, withRD); // for last block
    printf("\n");
}

vector<pair<const Variable*, CommonBlock*>> getCommonsByFunction(SgFile* file, SgStatement* function, const map<string, CommonBlock*>& commonBlocks)
{
    vector<pair<const Variable*, CommonBlock*>> retVal;

    for (auto& block : commonBlocks)
    {
        auto vars = block.second->getVariables(file, function);
        for (auto& var : vars)
            retVal.push_back(make_pair(var, block.second));
    }
    return retVal;
}

template<typename T>
bool intersectAndAdd(set<T>& s1, const set<T>& s2)
{
    bool retVal = false;
    set<T> intersect;
    set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), inserter(intersect, intersect.begin()));
    if (intersect.size() != s2.size())
        retVal = true;

    s1.insert(s2.begin(), s2.end());
    return retVal;
}

template bool intersectAndAdd<DIST::Array*>(set<DIST::Array*>&, const set<DIST::Array*>&);

static void addToSet(map<SAPFOR::Argument*, set<int>>& addTo, 
                     SAPFOR::Argument* fromArg, const set<int>& fromSet, bool& changeLoc)
{
    if (addTo.find(fromArg) == addTo.end())
    {
        addTo[fromArg] = fromSet;
        changeLoc = true;
    }
    else
    {
        bool res = intersectAndAdd(addTo[fromArg], fromSet);
        changeLoc = res || changeLoc;
    }
}

static bool bb_cmp(const BBlock* a, const BBlock* b)
{
    return a->getInstructions()[0]->getNumber() < b->getInstructions()[0]->getNumber();
}

static void fillGenKillForGlobal(const pair<SAPFOR::Argument*, set<int>>& arg, 
                                 map<SAPFOR::Argument*, set<int>>& gen_local, 
                                 map<SAPFOR::Argument*, set<int>>& kill_local)
{
    bool notInited = false;
    for (auto& ir : arg.second)
        if (ir == SAPFOR::CFG_VAL::UNINIT)
            notInited = true;

    if (gen_local.count(arg.first))
    {
        if (notInited)
        {
            for (auto& ir : arg.second)
                if (ir != SAPFOR::CFG_VAL::UNINIT)
                    gen_local[arg.first].insert(ir);
        }
        else
        {
            kill_local[arg.first].insert(SAPFOR::CFG_VAL::KILL_ALL);

            gen_local[arg.first].clear();
            gen_local[arg.first] = arg.second;
        }
    }
    else
    {
        for (auto& ir : arg.second)
            if (ir != SAPFOR::CFG_VAL::UNINIT)
                gen_local[arg.first].insert(ir);

        if (!notInited)
            kill_local[arg.first].insert(SAPFOR::CFG_VAL::KILL_ALL);
    }
}

void buildGenKillForCFG(const vector<BBlock*>& CFG, const map<string, FuncInfo*>& funcByName,
                        const map<FuncInfo*, map<SAPFOR::Argument*, set<int>>>& outForFunc, // actually, RD_out of last IR instruction of function
                        map<BBlock*, map<SAPFOR::Argument*, set<int>>>& gen,
                        map<BBlock*, map<SAPFOR::Argument*, set<int>>>& kill, 
                        map<Instruction*, map<SAPFOR::Argument*, set<int>>>* genForIR,
                        map<Instruction*, map<SAPFOR::Argument*, set<int>>>* killForIR,
                        map<BBlock*, set<SAPFOR::Argument*>>& notInitedGlobals,
                        const CFG_Settings settings)
{
    for (auto& block : CFG)
    {
        map<SAPFOR::Argument*, set<int>> kill_local;
        map<SAPFOR::Argument*, set<int>> gen_local;

        vector<Instruction*> lastParamRef;
        for (auto& instr : block->getInstructions())
        {
            auto currInstr = instr->getInstruction();
            auto arg = currInstr->getResult();

            if (currInstr->getOperation() == CFG_OP::ASSIGN ||
                currInstr->getOperation() == CFG_OP::LOAD)
            {
                if (arg->getType() != CFG_ARG_TYPE::REG || 
                    arg->getType() == CFG_ARG_TYPE::REG && settings.withRegisters)
                {
                    kill_local[arg].insert(SAPFOR::CFG_VAL::KILL_ALL);

                    gen_local[arg].clear();
                    gen_local[arg].insert(currInstr->getNumber());
                }
            }
            else if (currInstr->getOperation() == CFG_OP::F_CALL)
            {
                int count = stoi(currInstr->getArg2()->getValue());
                if (lastParamRef.size() != count)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                map<SAPFOR::Argument*, set<int>> onlyGlobal;
                bool hasOut = false;

                const string& fName = currInstr->getArg1()->getValue();
                auto itF = funcByName.find(fName);
                if (itF != funcByName.end())
                {
                    auto itOut = outForFunc.find(itF->second);
                    if (itOut != outForFunc.end())
                    {
                        hasOut = true;
                        for (auto& out : itOut->second)
                            if (out.first->isMemGlobal() || out.first->isParameter())
                                onlyGlobal[out.first] = out.second;
                    }
                }
                // TODO: do it better for user's functions! function not found in project
                else if (!isIntrinsicFunctionName(fName.c_str()) && 
                         currInstr->getArg1()->getValue() != "_WRITE" &&
                         currInstr->getArg1()->getValue() != "_PRINT")
                {
                    set<int> whereDef = { SAPFOR::CFG_VAL::UNINIT, currInstr->getNumber() }; // not inited and out 
                    for (auto& par : lastParamRef)
                    {
                        auto arg = par->getArg1();
                        if (arg->getType() == CFG_ARG_TYPE::VAR)
                            fillGenKillForGlobal(make_pair(arg, whereDef), gen_local, kill_local);
                    }
                }

                // return of function always REG
                if (arg && settings.withRegisters)
                {
                    kill_local[arg].insert(SAPFOR::CFG_VAL::KILL_ALL);
                    gen_local[arg].clear();
                }

                if (hasOut)
                {
                    bool hasFoundRes = false;
                    for (auto& out : onlyGlobal)
                    {
                        if (out.first->getMemType() == CFG_MEM_TYPE::FUNC_RES_)
                        {
                            if (arg)
                            {
                                hasFoundRes = true;
                                gen_local[arg].insert(out.second.begin(), out.second.end());
                            }
                        }
                        else if (out.first->isMemGlobal())
                            fillGenKillForGlobal(out, gen_local, kill_local);
                        else if (out.first->getMemType() == CFG_MEM_TYPE::FUNC_PARAM_)
                        {
                            int num = getParamIndex(out.first, lastParamRef.size());

                            auto arg = lastParamRef[num]->getArg1();
                            if (arg->getType() == CFG_ARG_TYPE::VAR)
                                fillGenKillForGlobal(make_pair(arg, out.second), gen_local, kill_local);
                        }
                    }

                    if (arg && !hasFoundRes)
                        gen_local[arg].insert(currInstr->getNumber());
                }
                else if (currInstr->getArg1()->getValue() == "_READ")
                {
                    for (auto& arg: lastParamRef) // all is out
                        if (arg->getArg1()->getType() == CFG_ARG_TYPE::VAR)
                            fillGenKillForGlobal(make_pair(arg->getArg1(), set<int>{ currInstr->getNumber()}), gen_local, kill_local);
                }
            }
            else if (currInstr->getOperation() == CFG_OP::PARAM)
                lastParamRef.push_back(currInstr);

            if (currInstr->getOperation() != CFG_OP::PARAM)
                lastParamRef.clear();

            if (genForIR)
                (*genForIR)[currInstr] = gen_local;
            if (killForIR)
                (*killForIR)[currInstr] = kill_local;
        }

        for (auto& gen : gen_local)
            if (gen.first->isMemGlobal() || gen.first->getMemType() == CFG_MEM_TYPE::FUNC_PARAM_)
                if (gen.second.count(SAPFOR::CFG_VAL::UNINIT))
                    notInitedGlobals[block].insert(gen.first);

        gen[block] = gen_local;
        kill[block] = kill_local;
    }
}

static int buildReachingDefs(const vector<BBlock*>& CFG, const FuncInfo* currF,
                             map<SAPFOR::Argument*, set<int>>& outForCurrFunc,
                             const map<FuncInfo*, map<SAPFOR::Argument*, set<int>>>& outForFunc,
                             const map<string, FuncInfo*>& funcByName, const CFG_Settings settings)
{
    // create gen kill for blocks
    map<BBlock*, map<SAPFOR::Argument*, set<int>>> gen, kill;
    map<BBlock*, set<SAPFOR::Argument*>> notInitedGlobals;

    buildGenKillForCFG(CFG, funcByName, outForFunc, gen, kill, NULL, NULL, notInitedGlobals, settings);
    
    //create not inited vars: U AllGen
    set<SAPFOR::Argument*> allGen;
    for (auto& byBlock : gen)
        for (auto& genB : byBlock.second)
            allGen.insert(genB.first);
   
    // sort blocks in IR order
    vector<BBlock*> sortedByIR = CFG;
    sort(sortedByIR.begin(), sortedByIR.end(), bb_cmp);

    //set to first block all not inited vars to IN set
    if (sortedByIR.size())
    {
        int firstN = 0;
        auto stF = currF->funcPointer->GetOriginal();
        if (stF->variant() == ENTRY_STAT)
        {
            int z = 0;
            for (auto& block : sortedByIR)
            {
                for (auto& ir : block->getInstructions())
                    if (ir->getInstruction()->getOperation() == CFG_OP::ENTRY && ir->getInstruction()->getOperator() == stF)
                        firstN = z;
                z++;
            }

            if (sortedByIR[firstN]->getInstructions()[0]->getInstruction()->getOperation() != CFG_OP::ENTRY && z == 0)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }

        auto& firstIn = sortedByIR[firstN]->getModRD_In();
        for (auto& noInited : allGen)
            firstIn[noInited] = set<int>{ SAPFOR::CFG_VAL::UNINIT };
    }

    bool changed = true;
    int iter = 0;
    while (changed)
    {
        changed = false;

        //update IN = U OUTp 
        //update OUT = GEN U (IN \ KILL) 
        for (const auto& block : sortedByIR)
        {
            bool changeLoc = false;
            map<SAPFOR::Argument*, set<int>>& in_loc = block->getModRD_In();
            const auto& prevBlocks = block->getPrev();

            // IN = U OUTp
            for (auto& prev : prevBlocks)
            {
                const auto& currOut = prev->getRD_Out();
                for (auto& out : currOut)
                {
                    bool res = intersectAndAdd(in_loc[out.first], out.second);
                    changeLoc = res || changeLoc;
                }
            }

            if (changeLoc)
                changed = true;

            map<SAPFOR::Argument*, set<int>>& out_loc = block->getModRD_Out();
            changeLoc = false;

            const auto& currIn = block->getRD_In();
            const auto& currKill = kill[block];
            const auto& currGen = gen[block];

            // OUT = GEN
            for (auto& gen : currGen)
            {
                bool genUpdated = false;
                if (gen.first->isMemGlobal() || gen.first->getMemType() == CFG_MEM_TYPE::FUNC_PARAM_)
                {
                    if (notInitedGlobals[block].count(gen.first))
                    {
                        auto inLoc = in_loc.find(gen.first);
                        if (inLoc != in_loc.end())
                        {
                            genUpdated = true;
                            set<int> copy;
                            for (auto& elem : gen.second)
                            {
                                if (elem == SAPFOR::CFG_VAL::UNINIT)
                                    copy.insert(inLoc->second.begin(), inLoc->second.end());
                                else
                                    copy.insert(elem);
                            }
                            addToSet(out_loc, gen.first, copy, changeLoc);
                        }
                    }
                }

                if (!genUpdated)
                    addToSet(out_loc, gen.first, gen.second, changeLoc);
            }

            // OUT = GEN U (IN \ KILL)
            for (auto& in : currIn)
            {
                if (currKill.count(in.first))
                {
                    auto s1 = in.second;
                    const auto& s2 = currKill.find(in.first)->second;
                    if (s2.size() && (*s2.begin()) == SAPFOR::CFG_VAL::KILL_ALL)
                        ;// nothing to add, s1 \ s2 = empty
                    else
                    {
                        set<int> diff;
                        set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), inserter(diff, diff.begin()));
                        addToSet(out_loc, in.first, diff, changeLoc);
                    }
                }
                else
                    addToSet(out_loc, in.first, in.second, changeLoc);
            }

            if (changeLoc)
                changed = true;
        }
        iter++;
    }

    if (sortedByIR.size())
        outForCurrFunc = sortedByIR.back()->getRD_Out();
    return iter;
}

//Kosaraju–Sharir algorithm
static vector<int> getStronglyConnectedComps(vector<vector<int>>& g) {
    // 1. For each vertex u of the graph, mark u as unvisited. Let l be empty.
    auto size = g.size();
    vector<bool> vis(size);           // all false by default
    vector<int> l(size);              // all zero by default
    auto x = size;                         // index for filling l in reverse order
    vector<vector<int>> t(size); // transpose graph

    // Recursive subroutine 'visit':
    function<void(int)> visit;
    visit = [&](int u) 
    {
        if (!vis[u]) 
        {
            vis[u] = true;
            for (auto v : g[u]) 
            {
                visit(v);
                t[v].push_back(u); // construct transpose
            }
            l[--x] = u;
        }
    };

    // 2. For each vertex u of the graph do visit(u)
    for (int i = 0; i < g.size(); ++i)
        visit(i);
    
    vector<int> c(size); // used for component assignment

    // Recursive subroutine 'assign':
    function<void(int, int)> assign;
    assign = [&](int u, int root) {
        if (vis[u]) { // repurpose vis to mean 'unassigned'
            vis[u] = false;
            c[u] = root;
            for (auto v : t[u]) {
                assign(v, root);
            }
        }
    };

    // 3: For each element u of l in order, do assign(u, u)
    for (auto u : l)
        assign(u, u);
    return c;

}

static int getFuncNum(FuncInfo* call, map<FuncInfo*, int>& funcToInt, map<int, FuncInfo*>& intTofunc, int &fnum)
{
    auto it = funcToInt.find(call);
    if (funcToInt.find(call) == funcToInt.end())
    {
        it = funcToInt.insert(it, make_pair(call, fnum));
        intTofunc[fnum] = call;
        fnum++;
    }
    return it->second;
}

//return also strongly connected components if has recursive chains call
vector<set<FuncInfo*>> groupByCallDependencies(const map<FuncInfo*, set<FuncInfo*>>& callDeps, vector<set<FuncInfo*>>& scc)
{
    vector<vector<int>> funcGraph;
    funcGraph.resize(callDeps.size());

    map<FuncInfo*, int> funcToInt;
    map<int, FuncInfo*> intTofunc;

    int fnum = 0;
    for (auto& funcPair : callDeps)
    {
        FuncInfo* call = funcPair.first;
        const set<FuncInfo*>& callTo = funcPair.second;

        int callN = getFuncNum(call, funcToInt, intTofunc, fnum);
        for (auto& to : callTo)
        {
            int callToN = getFuncNum(to, funcToInt, intTofunc, fnum);
            if (funcGraph.size() < fnum)
                funcGraph.resize(fnum);
            funcGraph[callN].push_back(callToN);
        }
    }
    vector<int> scc_v = getStronglyConnectedComps(funcGraph);
    map<int, vector<int>> components;
    for (int f = 0; f < scc_v.size(); ++f)
        components[scc_v[f]].push_back(f);
    
    vector<vector<int>> funcWithRecursion;
    for (auto& elem : components)
        if (elem.second.size() > 1)
            funcWithRecursion.push_back(elem.second);

    int countWithRec = funcWithRecursion.size();

    set<FuncInfo*> added;
    size_t added_prev_size = 0;
    size_t added_new_size = 0;
    set<int> doneRec;

    vector<set<FuncInfo*>> runMapForRD;
    while (added.size() != callDeps.size())
    {
        set<FuncInfo*> newLvl;
        if (added.size() == 0)
        {
            for (auto& func : callDeps)
            {
                if (func.second.size() == 0)
                    newLvl.insert(func.first);
                else
                {
                    bool ifHasDep = false;
                    for (auto& dep : func.second)
                        if (callDeps.count(dep))
                            ifHasDep = true;

                    if (!ifHasDep)
                        newLvl.insert(func.first);
                }
            }
        }
        else
        {
            for (auto& func : callDeps)
            {
                bool ifHasDep = false;
                for (auto& dep : func.second)
                    if (callDeps.count(dep) && added.count(dep) == 0)
                        ifHasDep = true;

                if (!ifHasDep && added.count(func.first) == 0)
                    newLvl.insert(func.first);
            }
        }

        added.insert(newLvl.begin(), newLvl.end());

        added_new_size = added.size();
        if (added_new_size == added_prev_size) 
        {            
            if (countWithRec != 0)
            {                
                for (int z = 0; z < funcWithRecursion.size(); ++z)
                {
                    if (doneRec.find(z) != doneRec.end())
                        continue;

                    set<FuncInfo*> candidate;
                    for (int f = 0; f < funcWithRecursion[z].size(); ++f)
                        candidate.insert(intTofunc[funcWithRecursion[z][f]]);

                    bool ok = true;
                    for (auto& elem : candidate)
                    {
                        if (candidate.find(elem) != candidate.end())
                            continue;

                        if (callDeps.count(elem))
                            for (auto& dep : callDeps.at(elem))
                                if (added.find(dep) == added.end())
                                    ok = false;
                    }

                    if (!ok)
                        continue;
                    else
                    {
                        newLvl = candidate;
                        added.insert(newLvl.begin(), newLvl.end());
                        added_new_size = added.size();
                        --countWithRec;
                        doneRec.insert(z);

                        scc.push_back(candidate);
                    }
                }

                if (newLvl.size() == 0)
                {
                    __spf_print(1, "recursive call was found, can not resolve dependencies\n");
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }
            }
        }

        added_prev_size = added_new_size;
        runMapForRD.push_back(newLvl);
    }

    return runMapForRD;
}

static void buildReachingDefs(const map<FuncInfo*, vector<BBlock*>>& CFG, const CFG_Settings& settings)
{
    map<FuncInfo*, set<FuncInfo*>> callDeps;
    map<string, FuncInfo*> funcByName;
    map<FuncInfo*, map<SAPFOR::Argument*, set<int>>> outForFunc;

    for (auto& byFunc : CFG)
    {
        callDeps[byFunc.first].insert(byFunc.first->callsFromV.begin(), byFunc.first->callsFromV.end());
        funcByName[byFunc.first->funcName] = byFunc.first;
    }

    vector<set<FuncInfo*>> scc;
    vector<set<FuncInfo*>> callLvlsForRD = groupByCallDependencies(callDeps, scc);

    //TODO: take into account ssc structure
    __spf_print(DEB_PRINT, "  count of functions %d, count of lvls %d\n", (int)CFG.size(), (int)callLvlsForRD.size());
    for (auto& byLvl : callLvlsForRD)
    {
        for (auto& byFunc : byLvl)
        {
            __spf_print(DEB_PRINT, "     RD time for '%s' function", byFunc->funcName.c_str());
            auto t = high_resolution_clock::now();

            auto itCFG = CFG.find(byFunc);
            if (itCFG == CFG.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            map<SAPFOR::Argument*, set<int>> outForCurr;
            int iter = buildReachingDefs(itCFG->second, byFunc, outForCurr, outForFunc, funcByName, settings);

            if (outForFunc.count(byFunc))
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            outForFunc[byFunc] = outForCurr;

            auto msec = duration_cast<milliseconds>(high_resolution_clock::now() - t).count();
            __spf_print(DEB_PRINT, " is %.3f sec, iters %d\n", msec / 1000., iter);
        }
        __spf_print(DEB_PRINT, "\n");
    }
}

static void createBlock(IR_Block* forIR, map<IR_Block*, BBlock*>& createdBlocks, 
                        vector<BBlock*>& bblocks,
                        const vector<IR_Block*>& instructions)
{
    if (createdBlocks.count(forIR) == 0)
    {
        bblocks.push_back(new BBlock(forIR));
        createdBlocks[forIR] = bblocks.back();
    }
}

static vector<BBlock*> buildCFG(const vector<IR_Block*>& instructions, bool withCalls)
{
    vector<BBlock*> bblocks;

    map<IR_Block*, BBlock*> createdBlocks;
    map<Instruction*, IR_Block*> instrToIr;
    map<int, IR_Block*> instrNumToIr;

    for (auto& ir : instructions)
    {
        instrToIr[ir->getInstruction()] = ir;
        instrNumToIr[ir->getNumber()] = ir;
    }

    if (instructions.size())
    {
        bblocks.push_back(new BBlock(instructions[0]));
        createdBlocks[instructions[0]] = bblocks.back();
    }

    for (int z = 0; z < instructions.size(); ++z)
    {
        if (instructions[z]->getInstruction()->getOperation() == CFG_OP::JUMP ||
            instructions[z]->getInstruction()->getOperation() == CFG_OP::JUMP_IF)
        {
            auto irJump = instrToIr[instructions[z]->getJump()];
            if (irJump == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            createBlock(irJump, createdBlocks, bblocks, instructions);

            if (z + 1 < instructions.size())
                createBlock(instructions[z + 1], createdBlocks, bblocks, instructions);
        }
        else if (instructions[z]->getInstruction()->getOperation() == CFG_OP::ENTRY)
        {
            createBlock(instructions[z], createdBlocks, bblocks, instructions);
            if (z + 1 != instructions.size() - 1) // TODO: check this
                createBlock(instructions[z + 1], createdBlocks, bblocks, instructions);
        }
        else if (instructions[z]->getInstruction()->getOperation() == CFG_OP::DVM_DIR ||
                 (instructions[z]->getInstruction()->getOperation() == CFG_OP::EXIT && withCalls))
        {
            createBlock(instructions[z], createdBlocks, bblocks, instructions);
            if (z + 1 < instructions.size())
                createBlock(instructions[z + 1], createdBlocks, bblocks, instructions);
        }
        else if (instructions[z]->getInstruction()->getOperation() == CFG_OP::F_CALL && withCalls)
        {
            int prev = z - 1;
            while (prev >= 0 && instructions[prev]->getInstruction()->getOperation() == CFG_OP::PARAM)
                --prev;

            createBlock(instructions[prev + 1], createdBlocks, bblocks, instructions);
            if (z + 1 < instructions.size())
                createBlock(instructions[z + 1], createdBlocks, bblocks, instructions);
        }
    }

    BBlock* currBlock = NULL;
    for (auto& ir : instructions)
    {
        if (createdBlocks.count(ir) != 0)
            currBlock = createdBlocks[ir];
        else
        {
            if (currBlock == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            currBlock->addInstruction(ir);
        }
    }

    for (auto& bblock : bblocks)
    {
        auto lastInstr = bblock->getInstructions().back();

        BBlock* fromJump = NULL;
        if (lastInstr->getJump())
        {
            fromJump = instrToIr[lastInstr->getJump()]->getBasicBlock();
            bblock->addNext(fromJump);
        }

        if (lastInstr->getInstruction()->getOperation() != CFG_OP::JUMP)
        {
            int next = lastInstr->getNumber() + 1;
            if (instrNumToIr.count(next) != 0)
            {
                auto toAdd = instrNumToIr[next]->getBasicBlock();
                if (fromJump != toAdd)
                    bblock->addNext(toAdd);
            }
        }
    }

    for (auto& bblock : bblocks)
        for (auto& next : bblock->getNext())
            next->addPrev(bblock);

    auto checkIR = getAllIR(bblocks);

    if (checkIR.size() != instructions.size())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return bblocks;
}

static void relaceArgInEntry(SAPFOR::Instruction* ir, const string& entryName, const vector<string>& params)
{
    for (int z = 0; z < 3; ++z)
    {
        SAPFOR::Argument* arg = NULL;
        if (z == 0)
            arg = ir->getArg1();
        else if (z == 1)
            arg = ir->getArg2();
        else if (z == 2)
            arg = ir->getResult();

        if (arg == NULL)
            continue;

        if (arg->getMemType() == SAPFOR::CFG_MEM_TYPE::FUNC_PARAM_)
        {
            vector<string> splited;
            splitString(arg->getValue(), '%', splited);
            if (splited.size() != 3)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (splited[0] != entryName)
            {
                int p = -1;
                for (int t = 0; t < params.size(); ++t)
                    if (params[t] == splited[1])
                        p = t;

                SAPFOR::Argument* newArg = NULL;
                if (p < 0)
                    newArg = createArg(splited[0] + "%" + splited[1], splited[1], SAPFOR::CFG_MEM_TYPE::LOCAL_);
                else
                    newArg = createArg(entryName + "%" + splited[1] + "%" + to_string(p), splited[1], SAPFOR::CFG_MEM_TYPE::FUNC_PARAM_);

                if (z == 0)
                    ir->setArg1(newArg);
                else if (z == 1)
                    ir->setArg2(newArg);
                else if (z == 2)
                    ir->setResult(newArg);
            }
        }
        else if (arg->getMemType() == SAPFOR::CFG_MEM_TYPE::LOCAL_ && 
                 arg->getType() == SAPFOR::CFG_ARG_TYPE::VAR)
        {
            vector<string> splited;
            splitString(arg->getValue(), '%', splited);
            if (splited.size() != 2)
                continue;

            if (splited[0] != entryName)
            {
                int p = -1;
                for (int t = 0; t < params.size(); ++t)
                    if (params[t] == splited[1])
                        p = t;

                if (p >= 0)
                {
                    auto newArg = createArg(entryName + "%" + splited[1] + "%" + to_string(p), splited[1], SAPFOR::CFG_MEM_TYPE::FUNC_PARAM_);

                    if (z == 0)
                        ir->setArg1(newArg);
                    else if (z == 1)
                        ir->setArg2(newArg);
                    else if (z == 2)
                        ir->setResult(newArg);
                }
            }
        }
    }
}

static void filterEntryArgs(const vector<BBlock*>& bblocks, const string& entryName, const vector<string>& params)
{
    /*set<BBlock*> checked;
    std::stack<BBlock*> q;
    q.push(bblocks[0]);

    checked.insert(bblocks[0]);

    while (!q.empty())
    {
        BBlock* curr = q.top();
        checked.insert(curr);
        q.pop();

        for (auto& elem : curr->getNext())
            if (checked.count(elem) == 0)
                q.push(elem);
    }*/

    for (auto& block : bblocks)
    {
        //if (checked.count(block) == 0) // not checked
            for (auto& ir : block->getInstructions())
                relaceArgInEntry(ir->getInstruction(), "_" + entryName, params);
    }
}

map<FuncInfo*, vector<BBlock*>> buildCFG(const map<string, CommonBlock*>& commonBlocks, const map<string, vector<FuncInfo*>>& allFuncInfo, const CFG_Settings settings)
{
    map<FuncInfo*, vector<BBlock*>> result;

    string oldFile = current_file->filename();

    for (auto& byFile : allFuncInfo)
    {
        if (SgFile::switchToFile(byFile.first) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        map<SgStatement*, FuncInfo*> byPointer;
        for (auto& func : byFile.second)
            byPointer[func->funcPointer->GetOriginal()] = func;

        for (auto& byFunc : byFile.second)
        {
            FuncInfo* currF = byFunc;
            SgStatement* function = currF->funcPointer->GetOriginal();

            if (function->variant() == ENTRY_STAT)
                continue;

            if (currF->isInterface)
                continue;

            if (!isSgProgHedrStmt(function)) {
                __spf_print(1, "var %d on file %s and line %d\n", function->variant(), function->fileName(), function->lineNumber());
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }

            vector<IR_Block*> blocks = buildIR(function, currF, getCommonsByFunction(current_file, function, commonBlocks), settings);
            if (blocks.size())
                blocks[0]->setHeader();

            vector<BBlock*> CFG_forFunc = buildCFG(blocks, settings.withCallsInBlocks);

            if (result.find(currF) != result.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            result[currF] = CFG_forFunc;
        }

        map<SAPFOR::Argument*, SAPFOR::Argument*> argsInc;
        //duplicate graph of parent func for ENTRY points 
        for (auto& byFunc : byFile.second)
        {
            FuncInfo* currF = byFunc;
            SgStatement* function = currF->funcPointer->GetOriginal();

            if (function->variant() != ENTRY_STAT)
                continue;

            auto parent = function->controlParent();
            if (!isSgProgHedrStmt(parent))
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (!byPointer.count(parent))
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            FuncInfo* parentF = byPointer[parent];
            if (!result.count(parentF))
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (result.count(currF))
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            const auto& cfg = result[parentF];

            vector<BBlock*> copy;
            map<BBlock*, BBlock*> oldToNewBB;
            map<Instruction*, Instruction*> oldToNewIR;

            for (auto& block : cfg)
            {
                copy.push_back(new BBlock(*block));
                oldToNewBB[block] = copy.back();

                const auto& newIR = copy.back()->getInstructions();
                const auto& oldIR = block->getInstructions();
                if (newIR.size() != oldIR.size())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                for (int z = 0; z < oldIR.size(); ++z)
                    oldToNewIR[oldIR[z]->getInstruction()] = newIR[z]->getInstruction();
            }

            for (auto& block : copy)
            {
                block->replacePrevNext(oldToNewBB);
                for (auto& ir : block->getInstructions())
                {
                    auto jump = ir->getJump();
                    if (jump)
                    {
                        auto it = oldToNewIR.find(jump);
                        if (it == oldToNewIR.end())
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        ir->setJump(it->second);
                    }
                }
            }

            //need to add goto to ENTRY
            map<FuncInfo*, vector<BBlock*>> tmpInfo;
            tmpInfo[parentF] = copy;

            auto position = getInstructionAndBlockByStatement(tmpInfo, function);
            if (position.first == NULL || position.second == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            if (position.first->getOperation() != CFG_OP::ENTRY)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
             
            const int lastIrNum = SAPFOR::Instruction::getNextInstrNum();
            int maxNum = 0;

            //shift all IR numbers by lastIrNum + 1
            for (auto& block : copy)
            {
                for (auto& instr : block->getInstructions())
                {
                    maxNum = std::max(instr->getInstruction()->getNumber(), maxNum);

                    instr->getInstruction()->shiftNumber(lastIrNum + 1);
                    auto ir = instr->getInstruction();
                    auto type = ir->getOperation();
                    if (type == CFG_OP::JUMP || type == CFG_OP::JUMP_IF)
                    {
                        if (argsInc.find((type == CFG_OP::JUMP) ? ir->getArg1() : ir->getArg2()) == argsInc.end())
                        {
                            int jumpTo = atoi((type == CFG_OP::JUMP) ? ir->getArg1()->getValue().c_str() : ir->getArg2()->getValue().c_str());
                            jumpTo += (lastIrNum + 1);
                            SAPFOR::Argument* newArg = new SAPFOR::Argument((type == CFG_OP::JUMP) ? *(ir->getArg1()) : *(ir->getArg2()));
                            newArg->setValue(to_string(jumpTo));

                            if (type == CFG_OP::JUMP)
                            {
                                ir->setArg1(newArg);
                                argsInc[ir->getArg1()] = newArg;
                            }
                            else
                            {
                                ir->setArg2(newArg);
                                argsInc[ir->getArg2()] = newArg;
                            }
                        }
                        else
                        {
                            if (type == CFG_OP::JUMP)
                                ir->setArg1(argsInc[ir->getArg1()]);
                            else
                                ir->setArg2(argsInc[ir->getArg2()]);
                        }
                    }
                }
            }

            SAPFOR::Instruction::shiftNextInstrNum(maxNum + 1);

            // goto to ENTRY
            BBlock* firstBlock = new BBlock();
            IR_Block* newGoto = new IR_Block(new Instruction(CFG_OP::JUMP, lastIrNum, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(position.first->getNumber()))));
            newGoto->setJump(position.first);
            newGoto->setHeader();

            firstBlock->addInstruction(newGoto);
            firstBlock->addNext(position.second);
            position.second->addPrev(firstBlock);
            copy.insert(copy.begin(), firstBlock);

            filterEntryArgs(copy, function->symbol()->identifier(), currF->funcParams.identificators);

            result[currF] = copy;
        }    
    }

    if (settings.withRD)
        buildReachingDefs(result, settings);

    if (settings.withDominators)
    {
        auto t = high_resolution_clock::now();
        for (auto& [func, bblocks] : result)
            SAPFOR::buildDominatorTree(bblocks);

        auto msec = duration_cast<milliseconds>(high_resolution_clock::now() - t).count();
        __spf_print(1, "    dominator build time is %.3f sec\n", msec / 1000.);
    }

    if (SgFile::switchToFile(oldFile) == -1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    if (!settings.withUnreachable) 
        for (auto& [func, blocks] : result)
            removedUnreachableBlocks(blocks);

    return result;
}

static bool ir_cmp(const IR_Block* a, const IR_Block* b)
{
    return a->getNumber() < b->getNumber();
}

vector<IR_Block*> getAllIR(const vector<BBlock*>& blocks)
{
    vector<IR_Block*> intsrs;
    for (auto& block : blocks)
        for (auto& instr : block->getInstructions())
            intsrs.push_back(instr);

    sort(intsrs.begin(), intsrs.end(), ir_cmp);
    return intsrs;
}

void dumpCFG(const map<FuncInfo*, vector<BBlock*>>& blocks, bool withRD)
{
    map<int, map<FuncInfo*, tuple<FuncInfo*, vector<BBlock*>, vector<IR_Block*>>>> toPrint; // sort by IR number

    for (auto& byFunc : blocks)
    {
        vector<IR_Block*> IR = getAllIR(byFunc.second);
        toPrint[IR[0]->getNumber()][byFunc.first] = make_tuple(byFunc.first, byFunc.second, IR);
    }

    for (auto& dump : toPrint)
        for (auto& byFunc : dump.second)
            debPrint(get<0>(byFunc.second)->funcName, get<1>(byFunc.second), get<2>(byFunc.second), withRD);
}

// buildCFGforCurrentFunc builds and returns CFG for current function, which contains 'stmt'
// and for functions, called from current function.
// CFG is built with reaching definitions analysis
// and with assupmtion, that every loop executes at least one iteration
map<FuncInfo*, vector<SAPFOR::BasicBlock*>>
         buildCFGforCurrentFunc(SgStatement* stmt, const SAPFOR::CFG_Settings settings,
                                const map<string, CommonBlock*>& commonBlocks,
                                const map<string, vector<FuncInfo*>>& allFuncInfo)
{    
    checkNull(stmt, convertFileName(__FILE__).c_str(), __LINE__);
    string file = stmt->fileName();

    SgProgHedrStmt* curFunc = isSgProgHedrStmt(getFuncStat(stmt));
    checkNull(curFunc, convertFileName(__FILE__).c_str(), __LINE__);

    string curFuncName = curFunc->nameWithContains();
    if (allFuncInfo.count(file) == 0)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    const vector<FuncInfo*>& funcInFile = allFuncInfo.at(file);

    FuncInfo* current = NULL;
    for (auto& func : funcInFile)
    {
        if (func->funcName == curFuncName)
        {
            current = func;
            break;
        }
    }
    checkNull(current, convertFileName(__FILE__).c_str(), __LINE__);

    map<string, vector<FuncInfo*>> fileFuncInfoMap;
    fileFuncInfoMap[current->fileName].push_back(current);

    if (settings.withCallFrom)
        for (auto& callFrom : current->callsFromV)
            fileFuncInfoMap[callFrom->fileName].push_back(callFrom);

    return buildCFG(commonBlocks, fileFuncInfoMap, settings);
}


void removedUnreachableBlocks(vector<SAPFOR::BasicBlock*>& blocks)
{
    set<SAPFOR::BasicBlock*> reachable;

    for (auto& b : blocks)
        if (b->getInstructions().front()->isHeader() ||
            b->getInstructions().front()->getInstruction()->getOperation() == SAPFOR::CFG_OP::ENTRY)
            reachable.insert(b);

    set<SAPFOR::BasicBlock*> worklist = reachable;
    while (worklist.size() != 0)
    {
        set<SAPFOR::BasicBlock*> to_insert;

        for (auto& b : worklist)
            for (auto& next : b->getNext())
                if (reachable.insert(next).second)
                    to_insert.insert(next);

        worklist = to_insert;
    }

    auto remove_unreachable_it = remove_if(blocks.begin(), blocks.end(),
        [&reachable](SAPFOR::BasicBlock* b)
        {
            if (reachable.find(b) == reachable.end())
            {
                for (auto& next : b->getNext())
                    if (reachable.find(next) != reachable.end())
                        next->removePrev(b);

                delete b;
                return true;
            }

            return false;
        }
    );

    reachable.clear();

    blocks.erase(remove_unreachable_it, blocks.end());
}