#include "loops_splitter.h"

#include <string>
#include <vector>

#include "../LoopAnalyzer/loop_analyzer.h"
#include "expr_transform.h"
#include "errors.h"
#include "CFGraph/CFGraph.h"
#include "../SageAnalysisTool/OmegaForSage/include/lang-interf.h"
#include "../DirectiveProcessing/directive_parser.h"
#include "../DirectiveProcessing/directive_omp_parser.h"

using std::string;
using std::vector;
using std::map;
using std::set;
using std::pair;
using std::make_pair;
using std::wstring;
using std::stack;
using SAPFOR::CFG_Settings;

#define PRINT_SPLITTED_FRAGMENTS 0

static map<FuncInfo*, vector<SAPFOR::BasicBlock*>> currIR;

static inline bool lineInsideBorder(int lineNumber, const pair<SgStatement*, SgStatement*>& border)
{
    return lineNumber >= border.first->lineNumber() && lineNumber < border.second->lineNumber();
}

static vector<SAPFOR::BasicBlock*> filterByBlock(SgStatement* block)
{
    int blockStart = block->lineNumber();
    int blockEnd = block->lastNodeOfStmt()->lineNumber();
    if (blockEnd < blockStart)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    if (blockEnd <= 0 || blockStart <= 0)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SgStatement* funcStat = getFuncStat(block);
    int lineStart = funcStat->lineNumber();

    vector<SAPFOR::BasicBlock*> currBlocks;
    for (auto& func : currIR)
    {
        if (func.first->linesNum.first == lineStart)
        {
            currBlocks = func.second;
            break;
        }
    }

    if (currBlocks.size() == 0)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    vector<SAPFOR::BasicBlock*> filtered;
    for (auto& block : currBlocks)
    {
        bool needToAdd = false;
        for (auto& ir : block->getInstructions())
        {
            int line = ir->getInstruction()->getOperator()->lineNumber();
            if (blockStart <= line && blockEnd >= line)
                needToAdd = true;
        }
        if (needToAdd)
            filtered.push_back(block);
    }

    return filtered;
}

static int getIntervalIdx (const vector<pair<int, int>>& intervals, int line)
{
    int id = -1;
    for (int z = 0; z < intervals.size(); ++z)
    {
        if (intervals[z].first <= line && line <= intervals[z].second)
        {
            id = z;
            break;
        }
    }
    return id;
}

/**
 * Построить map с зависимостями:
 * <Оператор Х : пара<сет зависящих от Х операторов : сет используюемых в Х операторов>>
 * с since включительно, по till не включительно
 */
static map<const LoopGraph*, map<SgStatement*, pair<set<SgStatement*>, set<SgStatement*>>>> cacheDeps;

//TODO: added lines for function calls
static map<SgStatement*, pair<set<SgStatement*>, set<SgStatement*>>> 
       buildReachMapForLoop(const LoopGraph* parentGraphUpest, const LoopGraph* parentGraph, SgStatement* since, SgStatement* till,
                            const set<string>& privates, const vector<depGraph*>& depGraphs)
{
    if (cacheDeps.count(parentGraph))
        return cacheDeps.at(parentGraph);

    const int sinceLine = since->lineNumber();
    const int tillLine = till->lineNumber();

    map<SgStatement*, pair<set<SgStatement*>, set<SgStatement*>>> result;

    set<string> privArrays;
    for (auto& elem : parentGraph->usedArraysAll)
        if (privates.find(elem->GetShortName()) != privates.end())
            privArrays.insert(elem->GetShortName());
    
    map<string, set<SgStatement*>> defInBlocks;
    map<string, set<SgStatement*>> useInBlocks;

    vector<SgStatement*> blocks;
    for (SgStatement* cur = since; cur != till; cur = cur->lexNext())
    {
        blocks.push_back(cur);
        cur = cur->lastNodeOfStmt();
    }

    //TODO: необходимо реализовать нормальный алгоритм определения зависимостей по приватному массиву. 
    if (privArrays.size() != 0)
    {
        for (auto& block : blocks)
        {
            auto filtered = filterByBlock(block);
            int blockStart = block->lineNumber();
            int blockEnd = block->lastNodeOfStmt()->lineNumber();

            for (auto& blockIR : filtered)
            {
                for (auto& ir : blockIR->getInstructions())
                {
                    auto inst = ir->getInstruction();
                    int inLine = inst->getOperator()->lineNumber();
                    if (inLine < blockStart || inLine > blockEnd)
                        continue;

                    if (inst->getOperation() == SAPFOR::CFG_OP::LOAD ||
                        inst->getOperation() == SAPFOR::CFG_OP::STORE)
                    {
                        auto name = getNameByArg(inst->getArg1());
                        if (privArrays.find(name) != privArrays.end())
                        {
                            if (inst->getOperation() == SAPFOR::CFG_OP::LOAD)
                                useInBlocks[name].insert(block);
                            else
                                defInBlocks[name].insert(block);
                        }
                    }
                }
            }
        }

        // сделать очередность блоков по IR, сейчас предполагается линейная очередность блоков, что не вседа верно,
        // но возможно другого варианта не может быть, кроме случаев с goto.
        for (int z = 0; z < blocks.size(); ++z)
        {
            for (auto& data : useInBlocks)
            {
                const string array = data.first;
                if (data.second.find(blocks[z]) != data.second.end())
                {
                    if (defInBlocks.find(array) != defInBlocks.end())
                    {
                        bool defFound = false;
                        for (int t = z - 1; t >= 0; --t)
                        {
                            if (defInBlocks[array].find(blocks[t]) != defInBlocks[array].end())
                            {
                                result[blocks[z]].first.insert(blocks[t]);
                                result[blocks[t]].second.insert(blocks[z]);
                                defFound = true;
                            }

                            if (defFound && useInBlocks[array].find(blocks[t]) != useInBlocks[array].end())
                                break;
                        }
                    }
                }
            }
        }
    }
    
    vector<pair<pair<string, string>, vector<pair<int, int>>>> data;
    const LoopGraph* lp = parentGraphUpest;
    while (lp)
    {
        auto attrsTr = getAttributes<SgStatement*, SgStatement*>(lp->loop->GetOriginal(), set<int>{ SPF_PARALLEL_DIR });
        for (auto attr : attrsTr)
            fillShadowAcrossFromComment(ACROSS_OP, new Statement(attr), data);
        if (lp == parentGraph)
            break;
        lp = lp->children[0];
    }

    set<string> arraysWithAcross;
    for (auto& elem : data)
        arraysWithAcross.insert(elem.first.first);

    for (depGraph* dependencyGraph : depGraphs)
    {
        if (dependencyGraph == NULL)
            continue;

        for (depNode* node : dependencyGraph->getNodes())
        {
            if (node->typedep == PRIVATEDEP)
                continue;
            if (node->typedep == REDUCTIONDEP)
                continue;

            if (node->varin != node->varout)
            {
                bool isPrivateArray = false;
                if (node->typedep == ARRAYDEP)
                    if (privates.find(node->varin->symbol()->identifier()) != privates.end() ||
                        privates.find(node->varout->symbol()->identifier()) != privates.end())
                        isPrivateArray = true;

                if (isPrivateArray)
                    continue;

                SgStatement* statIn = node->stmtin;
                SgStatement* statOut = node->stmtout;

                checkNull(statIn, convertFileName(__FILE__).c_str(), __LINE__);
                checkNull(statOut, convertFileName(__FILE__).c_str(), __LINE__);

                if (statOut != statIn)
                {
                    int inLine = node->stmtin->lineNumber();
                    int outLine = node->stmtout->lineNumber();

                    if (inLine == outLine)
                        continue;

                    const string nameIn = node->varin->symbol()->identifier();
                    const string nameOut = node->varout->symbol()->identifier();
                    bool isAcross = arraysWithAcross.count(nameIn) || arraysWithAcross.count(nameOut);

                    bool hasDependency = false;
                    for (int i = 1; i < node->knowndist.size(); ++i)
                    {
                        //ANTI and REDUCE
                        hasDependency |= (node->knowndist[i] == 0) || 
                                                ((node->knowndist[i] == 1) && 
                                                        (node->distance[i] != 0 || (isAcross && (node->kinddep == ddanti))));
                    }

                    if (hasDependency)
                    {
                        bool inIncluded = false, outIncluded = false;
                        if (lineInsideBorder(inLine, make_pair(since, till)))
                            inIncluded = true;
                        if (lineInsideBorder(outLine, make_pair(since, till)))
                            outIncluded = true;

                        if (!inIncluded && !outIncluded)
                            continue;

                        if (inIncluded && outIncluded)
                        {
                            result[node->stmtin].first.insert(node->stmtout);
                            result[node->stmtout].second.insert(node->stmtin);
                        }                         
                    }
                }
            }
        }
    }

    // added scalar dependencies
    vector<SAPFOR::BasicBlock*> bblocks;
    vector<pair<int, int>> startEndBlock;

    for (auto& block : blocks)
    {
        auto filtered = filterByBlock(block);
        bblocks.insert(bblocks.end(), filtered.begin(), filtered.end());

        int blockStart = block->lineNumber();
        int blockEnd = block->lastNodeOfStmt()->lineNumber();
        startEndBlock.push_back(make_pair(blockStart, blockEnd));
    }

    map<FuncInfo*, set<FuncInfo*>> callDeps;
    map<string, FuncInfo*> funcByName;
    map<FuncInfo*, map<SAPFOR::Argument*, set<int>>> outForFunc;

    // create gen kill for blocks
    map<SAPFOR::BasicBlock*, map<SAPFOR::Argument*, set<int>>> gen, kill;
    map<SAPFOR::Instruction*, map<SAPFOR::Argument*, set<int>>> genForIR, killForIR;
    map<SAPFOR::BasicBlock*, set<SAPFOR::Argument*>> notInitedGlobals;

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

    vector<set<FuncInfo*>> ssc;
    vector<set<FuncInfo*>> callLvls = groupByCallDependencies(callDeps, ssc);

    //TODO: take into account ssc structure
    for (auto& byLvl : callLvls)
    {
        for (auto& byFunc : byLvl)
        {
            auto itCFG = currIR.find(byFunc);
            if (itCFG == currIR.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            map<SAPFOR::Argument*, set<int>> outForCurr;
            buildGenKillForCFG(itCFG->second, funcByName, outForFunc, gen, kill, &genForIR, &killForIR, notInitedGlobals, SAPFOR::CFG_Settings());
            
            if (outForFunc.count(byFunc))
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            outForFunc[byFunc] = outForCurr;           
        }
    }

    for (auto& bblock : bblocks)
    {
        auto rd_in = bblock->getRD_In();
        for (auto& ir : bblock->getInstructions())
        {
            auto istr = ir->getInstruction();
            int line = istr->getOperator()->lineNumber();
            if (line <= 0)
                continue;

            if (line < sinceLine || line >= tillLine)
                continue;

            int idOperator = getIntervalIdx(startEndBlock, line);
            if (idOperator == -1)
                continue;

            set<SAPFOR::Argument*> use;
            if (istr->getArg1() && istr->getArg1()->getType() == SAPFOR::CFG_ARG_TYPE::VAR)
                use.insert(istr->getArg1());

            if (istr->getArg2() && istr->getArg2()->getType() == SAPFOR::CFG_ARG_TYPE::VAR)
                use.insert(istr->getArg2());

            for (auto& varUse : use)
            {
                auto it = rd_in.find(varUse);
                if (it != rd_in.end())
                {
                    for (auto& num : it->second)
                    {
                        if (num == SAPFOR::UNINIT)
                            continue;

                        auto whereByNum = getInstructionAndBlockByNumber(currIR, num);
                        if (whereByNum.first == NULL)
                            continue;

                        int line = whereByNum.first->getOperator()->lineNumber();
                        if (idOperator != getIntervalIdx(startEndBlock, line))
                        {
                            if (line >= sinceLine && line < tillLine)
                            {
                                result[istr->getOperator()].first.insert(whereByNum.first->getOperator());
                                result[whereByNum.first->getOperator()].second.insert(istr->getOperator());
                            }
                        }
                    }
                }
                //else err?
            }

            auto itGen = genForIR.find(istr);
            if (itGen != genForIR.end())
                for (auto& elem : itGen->second)
                    if (elem.first->getType() == SAPFOR::CFG_ARG_TYPE::VAR)
                        rd_in[elem.first] = elem.second;
        }
    }
    
    if (0) // deb print
    {
        for (auto& array : defInBlocks)
        {
            printf("%s defs in:\n", array.first.c_str());
            for (auto& place : array.second)
                printf("  -- %d\n", place->lineNumber());
            printf("\n");
        }

        for (auto& array : useInBlocks)
        {
            printf("%s uses in:\n", array.first.c_str());
            for (auto& place : array.second)
                printf("  -- %d\n", place->lineNumber());
            printf("\n");
        }

        for (auto& it : result)
        {
            printf("st: %d:\n", it.first->lineNumber());
            printf("Depends on:\n");
            for (auto& itt : it.second.first)
                printf("%d ", itt->lineNumber());
            printf("\n");

            printf("Used in:\n");
            for (auto& itt : it.second.second)
                printf("%d ", itt->lineNumber());
            printf("\n");
            printf("\n");
        }
    }

    cacheDeps[parentGraph] = result;
    return result;
}

static SgForStmt* createNewLoop(const LoopGraph *globalLoop)
{
    SgStatement *insertBeforeThis = globalLoop->loop->GetOriginal();
    SgStatement *newLoop = NULL;
    const LoopGraph *curLoopGraph = globalLoop;
    vector<const LoopGraph*> graphs(globalLoop->perfectLoop);

    for (int i = 0; i < globalLoop->perfectLoop; ++i)
    {
        graphs[i] = curLoopGraph;
        if (curLoopGraph->children.size())
            curLoopGraph = curLoopGraph->children[0];
    }

    for (int i = graphs.size() - 1; i > 0; --i)
    {
        SgForStmt* curForStmt = (SgForStmt*)graphs[i]->loop->GetOriginal();
        newLoop = new SgForStmt(curForStmt->doName(), curForStmt->start(), curForStmt->end(), curForStmt->step(), newLoop);
    }

    SgForStmt *curForStmt = (SgForStmt*)graphs[0]->loop->GetOriginal();
    SgForStmt *newGlobalLoop = new SgForStmt(curForStmt->doName(), curForStmt->start(), curForStmt->end(), curForStmt->step(), newLoop);
        
    insertBeforeThis->insertStmtBefore(*newGlobalLoop, *insertBeforeThis->controlParent());
    for (int z = 0; z < insertBeforeThis->numberOfAttributes(); ++z)
        newGlobalLoop->addAttribute(new SgAttribute(*insertBeforeThis->getAttribute(z)));

    SgStatement *lowestInsertedFor = insertBeforeThis;
    for (int i = 0; i < globalLoop->perfectLoop; ++i) //пройти по всем enddo
        lowestInsertedFor = lowestInsertedFor->lexPrev();

    return (SgForStmt*)lowestInsertedFor->lexPrev(); //самый внутренний цикл
}

static void setupOpenDependencies(set<int>& openDependencies, const vector<pair<SgStatement*, SgStatement*>>& borders,
                                  const set<string>& loopsPrivates, const vector<depGraph*>& depGraphs)
{
    for (depGraph* dependencyGraph : depGraphs)
    {
        if (dependencyGraph == NULL)
            continue;

        for (depNode* node : dependencyGraph->getNodes())
        {
            if (node->typedep == PRIVATEDEP)
                continue;
            if (node->typedep == REDUCTIONDEP)
                continue;

            if (node->varin != node->varout)
            {
                bool isPrivateArray = false;
                if (node->typedep == ARRAYDEP)
                    if (loopsPrivates.find(node->varin->symbol()->identifier()) != loopsPrivates.end() ||
                        loopsPrivates.find(node->varout->symbol()->identifier()) != loopsPrivates.end())
                        isPrivateArray = true;

                if (isPrivateArray)
                    continue;

                SgStatement* statIn = node->stmtin;
                SgStatement* statOut = node->stmtout;

                checkNull(statIn, convertFileName(__FILE__).c_str(), __LINE__);
                checkNull(statOut, convertFileName(__FILE__).c_str(), __LINE__);
                
                SgStatement* sInFor = statIn;
                while (sInFor && sInFor->variant() != FOR_NODE)
                    sInFor = sInFor->controlParent();
                SgStatement* sOutFor = statOut;
                while (sOutFor && sOutFor->variant() != FOR_NODE)
                    sOutFor = sOutFor->controlParent();

                if (statOut != statIn)
                {
                    bool hasDependency = false;
                    for (int i = 1; i < node->knowndist.size(); ++i)
                    {
                        //ANTI and REDUCE
                        hasDependency |= (node->knowndist[i] == 0) || ((node->knowndist[i] == 1) && (node->distance[i] != 0));
                    }

                    if (hasDependency)
                    {
                        int inLine = node->stmtin->lineNumber();
                        int outLine = node->stmtout->lineNumber();

                        bool inIncluded = false, outIncluded = false;
                        for (const auto& border : borders)
                        {
                            if (lineInsideBorder(inLine, border))
                                inIncluded = true;
                            if (lineInsideBorder(outLine, border))
                                outIncluded = true;
                        }

                        if (!inIncluded && !outIncluded)
                            continue;

                        if (!inIncluded && openDependencies.find(inLine) == openDependencies.end())
                            openDependencies.insert(inLine);
                        if (!outIncluded && openDependencies.find(outLine) == openDependencies.end())
                            openDependencies.insert(outLine);
                    }
                }
            }
        }
    }
}

static void addReachingDefinitionsDependencies(set<int>& openDependencies, const vector<pair<SgStatement*, SgStatement*>>& borders, 
                                               const map<SgStatement*, pair<set<SgStatement*>, set<SgStatement*>>>& requireReachMap)
{
    for (const auto& border : borders)
    {
        for (SgStatement* current = border.first; current != border.second; current = current->lexNext())
        {
            auto found = requireReachMap.find(current);
            if (found != requireReachMap.end())
            {
                for (auto it = found->second.first.begin(); it != found->second.first.end(); ++it)
                {
                    int lineNumber = (*it)->lineNumber();
                    bool included = false;
                    for (const auto& b : borders)
                    {
                        if (lineInsideBorder(lineNumber, b))
                        {
                            included = true;
                            break;
                        }
                    }
                    if (!included && openDependencies.find(lineNumber) == openDependencies.end())
                        openDependencies.insert(lineNumber);
                }

                for (auto it = found->second.second.begin(); it != found->second.second.end(); ++it)
                {
                    int lineNumber = (*it)->lineNumber();
                    bool included = false;
                    for (const auto& b : borders)
                    {
                        if (lineInsideBorder(lineNumber, b))
                        {
                            included = true;
                            break;
                        }
                    }

                    if (!included && openDependencies.find(lineNumber) == openDependencies.end())
                        openDependencies.insert(lineNumber);
                }
            }
        }
    }
}

static bool dependencyAlreadyEnclosed(int lineNum, const vector<pair<SgStatement*, SgStatement*>>& borders)
{
    for (const auto& border : borders)
        if (lineInsideBorder(lineNum, border))
            return true;
    
    return false;
}

// добавить зависимости из openDependencies в borders
static void expandCopyBorders(SgStatement *globalSince, SgStatement *globalTill, vector<pair<SgStatement*, SgStatement*>> &borders,
                              const set<int>& openDependencies)
{
    for (int lineNumOfDependency : openDependencies)
    {
        if (dependencyAlreadyEnclosed(lineNumOfDependency, borders))
            continue;

        SgStatement *since = NULL, *till = NULL;
        since = globalSince;
        for (since = globalSince; since != globalTill; since = since->lastNodeOfStmt()->lexNext())
        {
            if (since->lineNumber() <= lineNumOfDependency && since->lastNodeOfStmt()->lineNumber() >= lineNumOfDependency)
            {
                till = since->lastNodeOfStmt()->lexNext();
                break;
            }
        }

        if (since == globalTill) //зависимости вне основного цикла? ну уж нет.
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        borders.push_back(make_pair(since, till));
    }
}

// склеить соседние границы в borders
static void glueBorders(vector<pair<SgStatement*, SgStatement*>> &borders) 
{
    if (borders.size() <= 1)
        return;

    map<pair<int, int>, pair<SgStatement*, SgStatement*>> bordersMap;
    for (int z = 0; z < borders.size(); ++z)
        bordersMap[make_pair(borders[z].first->lineNumber(), borders[z].second->lineNumber())] = borders[z];

    borders.clear();
    for (auto &elem : bordersMap)
        borders.push_back(elem.second);

    bool needToUpdate = true;    
    while (needToUpdate)
    {
        needToUpdate = false;
        vector<pair<SgStatement*, SgStatement*>> newBorders;
        newBorders.push_back(borders[0]);
        int lastIdx = 0;
        for (int z = 1; z < borders.size(); ++z)
        {
            if (newBorders[lastIdx].second == borders[z].first)
                newBorders[lastIdx].second = borders[z].second;
            else
            {
                newBorders.push_back(borders[z]);
                lastIdx++;
            }
        }
        if (newBorders.size() != borders.size())
        {
            borders = newBorders;
            needToUpdate = (borders.size() > 1);
        }
    }
}

static vector<LoopGraph*> getLoopsFrom(const vector<pair<SgStatement*, SgStatement*>>& borders, LoopGraph *parentGraph, LoopGraph* parentGraphUpest)
{
    vector<LoopGraph*> result;

    while (parentGraphUpest != parentGraph)
    {
        result.push_back(parentGraphUpest);
        if (parentGraphUpest->children.size() == 0 || parentGraphUpest->children.size() > 1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        parentGraphUpest = parentGraphUpest->children[0];
    }
    result.push_back(parentGraph);

    for (LoopGraph *loop : parentGraph->children)
        for (const auto& frag : borders)
            if (loop->lineNum >= frag.first->lineNumber() && loop->lineNumAfterLoop <= frag.second->lineNumber())
                result.push_back(loop);

    return result;
}

static vector<depGraph*> getDepGraphsFor(const vector<LoopGraph*> &loops, LoopGraph *parentGraph, const map<LoopGraph*, depGraph*>& depInfoForLoopGraph)
{
    vector<depGraph*> result;
    for (auto& loop : loops)
    {
        if (depInfoForLoopGraph.count(loop))
            result.push_back(depInfoForLoopGraph.at(loop));
        else
            result.push_back(NULL);
    }
    return result;
}

static bool continueSplitting(SgStatement* globalSince, SgStatement* globalTill, const vector<pair<SgStatement*, SgStatement*>>& borders)
{
    //Если вырежем опять, исходный цикл останется пустым
    return !(borders.size() == 1 && borders[0].first == globalSince && borders[0].second == globalTill);
}

// построить borders - множество операторов, которые будут вынесены в отдельный цикл
static bool setupSplitBorders(LoopGraph* parentGraphUpest, LoopGraph* parentGraph, SgStatement* globalSince, SgStatement* globalTill,
                              vector<pair<SgStatement*, SgStatement*>>& borders,
                              const set<string>& privates, 
                              const map<LoopGraph*, depGraph*>& depInfoForLoopGraph)
{
    //Каким-то образом, мы вырезали всё из цикла и хотим продолжать.
    if (globalSince == globalTill)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    borders.clear();
    SgStatement *since, *till;

    since = globalSince;
    till = since->lastNodeOfStmt()->lexNext();

    // инициализация borders - первый оператор тела цикла
    borders.push_back(make_pair(since, till));

    vector<LoopGraph*> loops = getLoopsFrom(borders, parentGraph, parentGraphUpest);
    vector<depGraph*> depGraphs = getDepGraphsFor(loops, parentGraph, depInfoForLoopGraph);

    __spf_print(PRINT_SPLITTED_FRAGMENTS, "Initial fragment: %d - %d\n", since->lineNumber(), till->lineNumber());

    const auto requireReachMap = buildReachMapForLoop(parentGraphUpest, parentGraph, globalSince, globalTill, privates, depGraphs);

    // множество номеров строк операторов, с которыми есть зависимость и которые не включены в borders
    set<int> openDependencies;
    
    setupOpenDependencies(openDependencies, borders, privates, depGraphs);
    addReachingDefinitionsDependencies(openDependencies, borders, requireReachMap);

    while (openDependencies.size() > 0)
    {
#if PRINT_SPLITTED_FRAGMENTS == 1
        __spf_print(PRINT_SPLITTED_FRAGMENTS, "Line dependencies:\n", globalSince->lineNumber(), globalTill->lineNumber());
        for (auto& it : openDependencies)
            __spf_print(PRINT_SPLITTED_FRAGMENTS, " %d,", it);
        __spf_print(PRINT_SPLITTED_FRAGMENTS, "\n");
#endif

        expandCopyBorders(globalSince, globalTill, borders, openDependencies);
        openDependencies.clear();

        loops = getLoopsFrom(borders, parentGraph, parentGraphUpest);
        depGraphs = getDepGraphsFor(loops, parentGraph, depInfoForLoopGraph);
        setupOpenDependencies(openDependencies, borders, privates, depGraphs);
        addReachingDefinitionsDependencies(openDependencies, borders, requireReachMap);
    }

#if PRINT_SPLITTED_FRAGMENTS == 1
    for (auto &fragment : borders)
        __spf_print(PRINT_SPLITTED_FRAGMENTS, "fragment %d - %d\n", fragment.first->lineNumber(), fragment.second->lineNumber());
#endif


    glueBorders(borders);

    return continueSplitting(globalSince, globalTill, borders);
}

static void filterSpfAttributes(SgStatement* stat)
{
    int count = stat->numberOfAttributes();
    if (count)
    {
        SgStatement* lastNode = stat->lastNodeOfStmt();
        //TODO: added filter for private, reduction and ...
        for (int z = 0; z < count; ++z)
        {
            SgAttribute* attr = stat->getAttribute(z);
            if (attr->getAttributeType() == SPF_ANALYSIS_DIR)
            {
                SgStatement* data = (SgStatement*)attr->getAttributeData();
                if (data->localLineNumber() != SPF_USER_DIR)
                    continue;
                //__spf_print(1, "%s", data->unparse());

                set<string> privates;
                fillPrivatesFromComment(new Statement(data), privates);

                if (privates.size())
                {
                    set<string> assignTo;
                    for (SgStatement* st = stat; st != lastNode; st = st->lexNext())
                    {
                        if (st->variant() == ASSIGN_STAT)
                        {
                            auto ex = st->expr(0)->symbol();
                            if (ex)
                                assignTo.insert(ex->identifier());
                        }
                    }

                    set<string> actualPrivate;
                    for (auto& elem : privates)
                        if (assignTo.find(elem) != assignTo.end())
                            actualPrivate.insert(elem);

                    if (actualPrivate.size() != privates.size())
                    {
                        __spf_print(1, "actual private size %d, in dir %d\n", actualPrivate.size(), privates.size());
                        SgExpression* exprList = data->expr(0);
                        vector<SgExpression*> newData;
                        while (exprList)
                        {
                            if (exprList->lhs()->variant() == ACC_PRIVATE_OP)
                            {
                                if (actualPrivate.size())
                                {
                                    SgExpression* list = exprList->lhs()->lhs();
                                    vector<SgExpression*> newList;
                                    while (list)
                                    {
                                        if (actualPrivate.find(list->lhs()->symbol()->identifier()) != actualPrivate.end())
                                            newList.push_back(list->lhs());
                                        list = list->rhs();
                                    }

                                    if (newList.size())
                                    {
                                        exprList->lhs()->setLhs(makeExprList(newList));
                                        newData.push_back(exprList->lhs());
                                    }
                                }
                            }
                            else
                                newData.push_back(exprList->lhs());
                            exprList = exprList->rhs();
                        }

                        if (newData.size() == 0)
                        {
                            __spf_print(1, "remove attribute on line %d\n", stat->lineNumber());
                            stat->deleteAttribute(z);
                            count--;
                        }
                        else
                        {
                            __spf_print(1, "set new data size %d\n", newData.size());
                            data->setExpression(0, makeExprList(newData));
                        }
                    }
                }
            }
        }
    }
}

static void moveStatements(SgForStmt *newLoop, const vector<pair<SgStatement*, SgStatement*>> &fragments, int deep)
{
    //newLoop is lowest
    SgStatement* lastInserted = newLoop->lastNodeOfStmt();
    SgStatement* cp = lastInserted->controlParent();

    for (const auto& fragment : fragments)
    {
        SgStatement *toMoveStmt = fragment.first;
        while (toMoveStmt != fragment.second)
        {
            SgStatement *st = toMoveStmt;
            toMoveStmt = toMoveStmt->lastNodeOfStmt()->lexNext();

            lastInserted->insertStmtBefore(*st->extractStmt(), *cp);
        }
    }

    for (int z = 0; z < deep - 1; ++z)
    {
        newLoop = isSgForStmt(newLoop->controlParent());
        checkNull(newLoop, convertFileName(__FILE__).c_str(), __LINE__);
    }

    filterSpfAttributes(newLoop);
}

static bool hasIndirectChildLoops(const LoopGraph* parentGraph, vector<Messages> &messages)
{
    SgStatement* st = parentGraph->loop->GetOriginal()->lexNext();
    int directLoops = 0;
    while (st->variant() != CONTROL_END)
    {
        if (st->variant() == FOR_NODE)
            directLoops++;
        st = st->lastNodeOfStmt()->lexNext();
    }
       
    if (directLoops != parentGraph->children.size())
    {
        messages.push_back(Messages(ERROR, parentGraph->loop->GetOriginal()->lineNumber(), R105, 
                           L"This loop has indirect child loops and can not be splitted", 2010));
        __spf_print(1, "This loop has indirect child loops and can not be splitted on line %d\n", parentGraph->lineNum);
        return true;
    }
    else
        return false;
}

static bool hasUnexpectedDependencies(const LoopGraph* parentGraph, const depGraph* parentDepGraph, 
                                      const set<string>& privateVars, vector<Messages>& messages)
{
    bool has = false;
    int countOfMessages = 10;
    int idxOfMessages = 0;

    if (parentDepGraph == NULL)
        return false;

    for (depNode* node : parentDepGraph->getNodes())
    {
        if (node->typedep != ARRAYDEP)
        {
            bool privateInChild = false;
            for (LoopGraph* childGraph : parentGraph->children)
            {
                SgStatement *childLoop = childGraph->loop->GetOriginal();
                if (lineInsideBorder(node->stmtin->lineNumber(), make_pair(childLoop, childLoop->lastNodeOfStmt()->lexNext())))
                    privateInChild = (node->typedep == PRIVATEDEP);
            }

            // is it private for this loop
            if (!privateInChild)
                if (node->varin->symbol() && privateVars.find(node->varin->symbol()->identifier()) != privateVars.end())
                    privateInChild = true;

            has |= !privateInChild;
            if (!privateInChild)
            {
                if (idxOfMessages < countOfMessages)
                {
                    idxOfMessages++;
                    string str;
                    __spf_printToBuf(str, "Can not split this loop because of dependency: %s", node->displayDepToStr().c_str());
                    __spf_print(1, "%s on line %d\n", str.c_str(), parentGraph->lineNum);

                    wstring strR, strE;
                    __spf_printToLongBuf(strE, L"Can not split this loop because of dependency: %s", 
                                         to_wstring(node->displayDepToStr()).c_str());

                    __spf_printToLongBuf(strR, R104, to_wstring(node->displayDepToStr()).c_str());
                    messages.push_back(Messages(WARR, parentGraph->lineNum, strR, strE, 2009));
                }
            }
        }
    }
    return has;
}

static int splitLoop(LoopGraph *loopGraph, vector<Messages> &messages, int deep, const map<LoopGraph*, depGraph*>& depInfoForLoopGraph)
{
    LoopGraph *lowestParentGraph = loopGraph;
    for (int i = 0; i < std::min(loopGraph->perfectLoop, deep) - 1; ++i)
    {
        if (lowestParentGraph->children.size() == 1)
            lowestParentGraph = lowestParentGraph->children[0];
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }

    if (hasIndirectChildLoops(lowestParentGraph, messages))
        return -1;

    if (lowestParentGraph->hasLimitsToSplit())
    {
        messages.push_back(Messages(ERROR, loopGraph->lineNum,
                            R106 + std::to_wstring(lowestParentGraph->lineNum),
                            L"This loop has limits (goto, print, stops or not DO-ENDDO) for splitting (reason: loop on line " + std::to_wstring(lowestParentGraph->lineNum) + L")",
                            2010));

        __spf_print(1, "%d loop has limits (goto, print, stops or not DO-ENDDO) for splitting (reason: loop on line %d)\n", loopGraph->lineNum, lowestParentGraph->lineNum);
        return -1;
    }

    set<string> privates;
    tryToFindPrivateInAttributes(loopGraph->loop, privates);

    const set<string> privVars;
    const depGraph* lowestParentDepGraph = NULL; 
    if (depInfoForLoopGraph.count(lowestParentGraph))
        lowestParentDepGraph = depInfoForLoopGraph.at(lowestParentGraph);

    if (hasUnexpectedDependencies(lowestParentGraph, lowestParentDepGraph, privates, messages))
    {
        messages.push_back(Messages(ERROR, loopGraph->lineNum, 
                           R107 + std::to_wstring(lowestParentGraph->lineNum), 
                           L"This loop has unexpected dependencies and can not be splitted (reason: loop on line " + std::to_wstring(lowestParentGraph->lineNum) + L")",
                           2010));

        __spf_print(1, "%d loop has unexpected dependencies and can not be splitted (reason: loop on line %d)\n", 
                    loopGraph->lineNum, lowestParentGraph->lineNum);
        return -1;
    }

    SgStatement *globalSince, *globalTill;
    globalSince = lowestParentGraph->loop->GetOriginal()->lexNext();
    globalTill = lowestParentGraph->loop->GetOriginal()->lastNodeOfStmt();

    vector<pair<SgStatement*, SgStatement*>> borders;
    bool setup = setupSplitBorders(loopGraph, lowestParentGraph, globalSince, globalTill, borders, privates, depInfoForLoopGraph);

    SgStatement* prev = loopGraph->loop->lexPrev();
    bool hasSplited = false;
    while (setup && borders.size() > 0) // пока цикл можно делить
    {
        hasSplited = true;
#if PRINT_SPLITTED_FRAGMENTS == 1
        __spf_print(PRINT_SPLITTED_FRAGMENTS, "global since %d, global till %d\n", globalSince->lineNumber(), globalTill->lineNumber());
        __spf_print(PRINT_SPLITTED_FRAGMENTS, "result fragment: ");
        for (auto& it : borders)
            __spf_print(PRINT_SPLITTED_FRAGMENTS, "%d - %d, ", it.first->lineNumber(), it.second->lineNumber());
        __spf_print(PRINT_SPLITTED_FRAGMENTS, "\n");
#endif
        moveStatements(createNewLoop(loopGraph), borders, deep);
        globalSince = lowestParentGraph->loop->GetOriginal()->lexNext();

        setup = setupSplitBorders(loopGraph, lowestParentGraph, globalSince, globalTill, borders, privates, depInfoForLoopGraph);
    }

    //move comment to uppest loop
    if (hasSplited)
    {
        if (loopGraph->loop->comments())
        {
            prev->lexNext()->addComment(loopGraph->loop->comments());
            loopGraph->loop->delComments();
        }
    }
    return hasSplited ? 0 : 1;
}

int splitLoops(SgFile *file, vector<LoopGraph*> &loopGraphs, vector<Messages> &messages, 
               const map<LoopGraph*, depGraph*>& depInfoForLoopGraph,
               const map<string, CommonBlock*>& commonBlocks,
               const map<string, vector<FuncInfo*>>& allFuncInfo, int &countOfTransform)
{
    cacheDeps.clear();
    map<int, LoopGraph*> mapLoopGraph;
    createMapLoopGraph(loopGraphs, mapLoopGraph);
    int totalErr = 0;

    for (auto &loopPair : mapLoopGraph)
    {
        if (!loopPair.second->isFor())
            continue;

        LoopGraph *loop = loopPair.second;
        auto attrsTr = getAttributes<SgStatement*, SgStatement*>(loop->loop->GetOriginal(), set<int>{ SPF_TRANSFORM_DIR });
        for (auto attr : attrsTr)
        {
            SgExpression *list = attr->expr(0);
            if (list->lhs()->variant() == SPF_FISSION_OP)
            {
                checkNull(list->lhs()->lhs(), convertFileName(__FILE__).c_str(), __LINE__);

                SgExprListExp *listExp = isSgExprListExp(list->lhs()->lhs());
                checkNull(listExp, convertFileName(__FILE__).c_str(), __LINE__);
                int deep = listExp->length();
                
                const auto settings = CFG_Settings({ CFG_Settings::CFG_atLeastOneIterInLoop, CFG_Settings::CFG_withRD, CFG_Settings::CFG_withCallFrom, CFG_Settings::CFG_withDominators });
                currIR = buildCFGforCurrentFunc(loop->loop, settings, commonBlocks, allFuncInfo);
                totalErr = splitLoop(loop, messages, deep, depInfoForLoopGraph);

                if (totalErr > 0)
                {
                    messages.push_back(Messages(ERROR, loop->lineNum, R196, L"The loop transformation cannot be performed due to dependencies between operators", 2022));
                    __spf_print(1, "%d The loop transformation cannot be performed due to dependencies between operators\n", loop->lineNum);
                }
                else if (totalErr == 0)
                {
                    filterSpfAttributes(loop->loop->GetOriginal());
                    countOfTransform++;
                }

                deleteCFG(currIR);
            }
        }
    }
    
    cacheDeps.clear();
    return totalErr;
}
