#include "leak_detector.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>

#include <map>
#include <vector>
#include <set>
#include <string>

#include "errors.h"
#include "graph_calls.h"
#include "../Distribution/Distribution.h"
#include "utils.h"
#include "../ParallelizationRegions/ParRegions.h"

using std::vector;
using std::map;
using std::string;
using std::wstring;
using std::set;
using std::to_string;
using std::tuple;
using std::pair;

#define DEB 0
void createMapOfFunc(const vector<FuncInfo*> &allFuncInfo, map<pair<string, int>, FuncInfo*> &mapFuncInfo)
{
    for (auto &func : allFuncInfo)
        mapFuncInfo[make_pair(func->fileName, func->linesNum.first)] = func;
}

void createMapOfFunc(const vector<FuncInfo*> &allFuncInfo, map<string, FuncInfo*> &mapFuncInfo)
{
    for (auto &func : allFuncInfo)
        mapFuncInfo[func->funcName] = func;
}

void createMapOfFunc(const map<string, vector<FuncInfo*>> &allFuncInfo, map<string, FuncInfo*> &mapFuncInfo)
{
    for (auto it = allFuncInfo.begin(); it != allFuncInfo.end(); ++it)
        for (int k = 0; k < it->second.size(); ++k)
            mapFuncInfo[it->second[k]->funcName] = it->second[k];
}

FuncInfo* getFuncInfo(const map<string, FuncInfo*> &funcMap, const string &funcName)
{
    auto it = funcMap.find(funcName);
    if (it == funcMap.end())
        return NULL;

    return it->second;
}

string removeString(const string &toRemove, const string &inStr)
{
    string outStr(inStr);
    const size_t found = outStr.find(toRemove);
    if (found != string::npos)
        outStr.erase(found, toRemove.length());
    return outStr;
}

void updateFuncInfo(const map<string, vector<FuncInfo*>> &allFuncInfo) // const here
{
    bool changesDone = false;
    map<string, FuncInfo*> mapFuncInfo;
    createMapOfFunc(allFuncInfo, mapFuncInfo);

    do
    {
        changesDone = false;
        for (auto &it : mapFuncInfo)
        {
            FuncInfo *currInfo = it.second;
            
            for(auto &funcCall : currInfo->funcsCalledFromThis)
            {
                // Find pointer to info of called function
                auto itCalledFunc = mapFuncInfo.find(funcCall.CalledFuncName);

                if (itCalledFunc != mapFuncInfo.end() && !itCalledFunc->second->isInterface)
                {
                    FuncInfo *calledFunc = itCalledFunc->second;

                    // check for pureness
                    if (!calledFunc->isPure && currInfo->isPure) 
                    {
                       currInfo->isPure = false;
                       changesDone = true;
                    }

                    // check for using parameter as index
                    // Iterate through all pars of the call
                    for (int parNo = 0; parNo < funcCall.NoOfParamUsedForCall.size(); ++parNo)
                    {
                        auto& parOfCalled = funcCall.NoOfParamUsedForCall[parNo];
                        // If this par of called func is used as index change
                        if (calledFunc->isParamUsedAsIndex[parNo])
                        {
                            // Then pars of calling func which are used in this par of called
                            // are also used as index
                            for (auto &parOfCalling : parOfCalled)
                            {
                                if (!currInfo->isParamUsedAsIndex[parOfCalling])
                                {
                                    changesDone = true;
                                    currInfo->isParamUsedAsIndex[parOfCalling] = true;
                                }
                            }
                        }
                    }

                    // propagate inout types
                    for (int parNo = 0; parNo < funcCall.NoOfParamUsedForCall.size(); ++parNo)
                    {
                        auto& parOfCalled = funcCall.NoOfParamUsedForCall[parNo];
                        if (parOfCalled.size() == 0)
                            continue;
                        
                        if (calledFunc->funcParams.isArgOut(parNo))
                        {
                            for (auto& num : parOfCalled)
                            {
                                if (!currInfo->funcParams.isArgOut(num))
                                {
                                    currInfo->funcParams.inout_types[num] |= OUT_BIT;
                                    changesDone = true;
                                }
                            }
                        }

                        if (calledFunc->funcParams.isArgIn(parNo))
                        {
                            for (auto& num : parOfCalled)
                            {
                                if (!currInfo->funcParams.isArgIn(num))
                                {
                                    currInfo->funcParams.inout_types[num] |= IN_BIT;
                                    changesDone = true;
                                }
                            }
                        }
                    }
                }
            }
        }
    } while (changesDone);

    // check for io usage
    do
    {
        changesDone = false;
        for (auto &it : mapFuncInfo)
        {
            FuncInfo *currInfo = it.second;
            for (auto &callInfo : currInfo->callsFromDetailed)
            {
                auto& funcCall = callInfo.detailCallsFrom;
                auto itCalledFunc = mapFuncInfo.find(funcCall.first);
                if (itCalledFunc != mapFuncInfo.end())
                {
                    FuncInfo *calledFunc = itCalledFunc->second;
                    if (calledFunc->linesOfIO.size())
                    {
                        const int lineOfCall = funcCall.second;
                        auto it = currInfo->linesOfIO.find(lineOfCall);
                        if (it == currInfo->linesOfIO.end())
                        {
                            currInfo->linesOfIO.insert(lineOfCall);
                            changesDone = true;
                        }
                    }
                }
            }
        }
    } while (changesDone);

    //fill all pars IN, if they have NONE status
    for (auto& it : mapFuncInfo)
    {
        FuncInfo* currInfo = it.second;
        currInfo->funcParams.completeParams();
    }
}

int CreateCallGraphViz(const char *fileName, const map<string, vector<FuncInfo*>> &funcByFile, map<string, CallV> &V, vector<string> &E)
{
    map<string, FuncInfo*> allFuncs;
    createMapOfFunc(funcByFile, allFuncs);

    string graph = "";
    graph += "digraph G{\n";

    auto it = funcByFile.begin();
    int fileNum = 0;
    set<string> inCluster;
    set<string> unknownCluster;

    char buf[1024];
    while (it != funcByFile.end())
    {
        sprintf(buf, "subgraph cluster%d {\n", fileNum);
        graph += buf;

        const int dimSize = (int)it->second.size();
        set<string> uniqNames;
        for (int k = 0; k < dimSize; ++k)
        {
            const string currfunc = it->second[k]->funcName;
            auto it = uniqNames.find(currfunc);
            if (it == uniqNames.end())
            {
                uniqNames.insert(it, currfunc);
                inCluster.insert(currfunc);
                sprintf(buf, "\"%s\"\n", currfunc.c_str());
                graph += buf;
            }
        }
        sprintf(buf, "label = \"file <%s>\"\n", removeString(".\\", it->first).c_str());
        graph += buf;
        graph += "}\n";

        fileNum++;
        it++;
    }

    it = funcByFile.begin();
    while (it != funcByFile.end())
    {
        const char *formatString = "\"%s\" -> \"%s\" [minlen=2.0];\n";
        const int dimSize = (int)it->second.size();
        for (int k = 0; k < dimSize; ++k)
        {
            const string &callFrom = it->second[k]->funcName;
            const FuncInfo *callFromP = it->second[k];

            for (auto &callItem : it->second[k]->callsFrom)
            {
                sprintf(buf, formatString, callFrom.c_str(), callItem.c_str());
                graph += buf;

                if (inCluster.find(callFrom) == inCluster.end())
                    unknownCluster.insert(callFrom);
                if (inCluster.find(callItem) == inCluster.end())
                    unknownCluster.insert(callItem);

                if (V.find(callFrom) == V.end())
                {
                    V[callFrom] = CallV(callFromP->funcName, callFromP->fileName, callFromP->isMain);
                    V[callFrom].inRegion = callFromP->inRegion;
                }

                if (V.find(callItem) == V.end())
                {
                    auto it = allFuncs.find(callItem);
                    if (it == allFuncs.end())
                        V[callItem] = CallV(callItem);
                    else
                    {
                        auto currF = it->second;
                        V[callItem] = CallV(callItem, currF->fileName, currF->isMain);
                        V[callItem].inRegion = currF->inRegion;
                    }
                }


                E.push_back(callFrom);
                E.push_back(callItem);
            }
        }
        it++;
    }

    if (unknownCluster.size() > 0)
    {
        sprintf(buf, "subgraph cluster%d {\n", fileNum);
        graph += buf;

        for (auto &func : unknownCluster)
        {
            sprintf(buf, "\"%s\"\n", func.c_str());
            graph += buf;
        }
        sprintf(buf, "label = \"file <UNKNOWN>\"\n");
        graph += buf;
        graph += "}\n";
    }

    graph += "overlap=false\n";
    graph += "}\n";

    if (fileName)
    {
        FILE *out = fopen(fileName, "w");
        if (out == NULL)
        {
            __spf_print(1, "can not open file %s\n", fileName);
            return -1;
        }

        fprintf(out, graph.c_str());
        fclose(out);
    }
    return 0;
}

int CreateFuncInfo(const char *fileName, const map<string, vector<FuncInfo*>> &funcByFile)
{
    string funcOut = "";
    for (auto &byFile : funcByFile)
    {
        funcOut += "FILE " + byFile.first + ":\n";
        for (auto &func : byFile.second)
        {
            funcOut += (func->isInterface ? ("INTERFACE OF ") : "") + string("  FUNCTION '") + 
                        func->funcName + "' " + (func->isPure ? " is PURE" : "is IMPURE") +  "\n";
            char buf[256];
            sprintf(buf, "    LINES [%d, %d] \n", func->linesNum.first, func->linesNum.second);
            funcOut += buf;
            sprintf(buf, "    PARAMETERS %d:\n", func->funcParams.countOfPars);
            funcOut += buf;
            for (int z = 0; z < func->funcParams.countOfPars; ++z)
            {
                bool in = func->funcParams.isArgIn(z);
                bool out = func->funcParams.isArgOut(z);
                const char *inout = "";
                if (in && out)
                    inout = "IN/OUT";
                else if (in)
                    inout = "IN";
                else if (out)
                    inout = "OUT";

                sprintf(buf, "      %s: type '%s', %s\n", func->funcParams.identificators[z].c_str(),
                    paramNames[func->funcParams.parametersT[z]],
                    inout);
                funcOut += buf;
            }
        }
    }

    if (fileName)
    {
        FILE *out = fopen(fileName, "w");
        if (out == NULL)
        {
            __spf_print(1, "can not open file %s\n", fileName);
            return -1;
        }

        fprintf(out, funcOut.c_str());
        fclose(out);
    }
    return 0;
}

extern map<string, vector<FuncInfo*>> allFuncInfo; // file -> Info  
FuncInfo* isUserFunctionInProject(const string &func)
{
    FuncInfo *ret = NULL;
    for (auto &it : allFuncInfo)
    {
        for (auto &currF : it.second)
        {
            if (currF->funcName == func)
            {
                ret = currF;
                break;
            }
        }

        if (ret)
            break;
    }
    return ret;
}

// Find dead functions and fill callTo / callFrom information
void findDeadFunctionsAndFillCalls(map<string, vector<FuncInfo*>> &allFuncInfo, map<string, vector<Messages>> &allMessages, bool noPrint)
{
    map<string, FuncInfo*> mapFuncInfo;
    createMapOfFunc(allFuncInfo, mapFuncInfo);

    set<string> allExternalCalls;
    set<string> allChildCalls;

    for (auto &it : mapFuncInfo)
    {
        FuncInfo *currInfo = it.second;
        allChildCalls.insert(currInfo->callsFrom.begin(), currInfo->callsFrom.end());
        allExternalCalls.insert(currInfo->externalCalls.begin(), currInfo->externalCalls.end());
    }

    for (auto &it : mapFuncInfo)
    {
        FuncInfo *currInfo = it.second;
        if (allChildCalls.find(it.first) == allChildCalls.end())
            if (!currInfo->isMain && allExternalCalls.find(currInfo->funcName) == allExternalCalls.end())
                currInfo->deadFunction = currInfo->doNotAnalyze = true;
    }

    if (!noPrint)
    {
        for (auto &it : allFuncInfo)
        {
            const string &currF = it.first;
            auto itM = allMessages.find(currF);
            if (itM == allMessages.end())
                itM = allMessages.insert(itM, make_pair(currF, vector<Messages>()));

            for (auto& func : it.second)
                if (func->deadFunction)
                    itM->second.push_back(Messages(WARR, func->linesNum.first, R47, L"This function is not called in current project", 1015));
        }
    }

    for (auto &it : mapFuncInfo)
    {
        FuncInfo *currInfo = it.second;
        for (auto &k : currInfo->callsFrom)
        {
            auto itFound = mapFuncInfo.find(k);
            if (itFound != mapFuncInfo.end())
            {
                FuncInfo *callFrom = itFound->second;
                callFrom->callsTo.push_back(currInfo);
                currInfo->callsFromV.insert(callFrom);
            }
        }
    }
    
    FuncInfo* main = NULL;
    for (auto& it : mapFuncInfo)
        if (it.second->isMain)
            main = it.second;

    checkNull(main, convertFileName(__FILE__).c_str(), __LINE__);

    set<FuncInfo*> liveFunctions;
    liveFunctions.insert(main);
    for (auto& callFrom : main->callsFromV)
        liveFunctions.insert(callFrom);

    //find live functions
    bool changes = true;
    while (changes)
    {
        changes = false;
        for (auto& currInfo : liveFunctions)
        {
            for (auto& callFrom : currInfo->callsFromV) {
                if (liveFunctions.find(callFrom) == liveFunctions.end()) {
                    changes = true;
                    liveFunctions.insert(callFrom);
                }
            }
        }
    }

    // propagate 'deadFunction' status for all 'CallsFrom' from dead functions
    changes = true;
    while (changes)
    {
        changes = false;

        for (auto& it : mapFuncInfo)
        {
            FuncInfo* currInfo = it.second;
            if (currInfo->deadFunction == false)
                continue;

            for (auto& callFrom : currInfo->callsFrom)
            {
                auto itFrom = mapFuncInfo.find(callFrom);
                if (itFrom != mapFuncInfo.end())
                {
                    auto func = itFrom->second;
                    if (!func->deadFunction && liveFunctions.find(func) == liveFunctions.end())
                    {
                        changes = true;
                        func->deadFunction = func->doNotAnalyze = true;
                    }
                }
            }
        }
    }
}

static inline void addLinks(const FuncParam &actual, const FuncParam &formal, map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    if (actual.parameters.size() != formal.parameters.size())
        return;
    else
    {
        for (int i = 0; i < actual.parameters.size(); ++i)
            if (actual.parametersT[i] == formal.parametersT[i] && formal.parametersT[i] == ARRAY_T)
            {
                //printf("add lhs %s -> rhs %s\n", ((DIST::Array*)formal.parameters[i])->GetName().c_str(), ((DIST::Array*)actual.parameters[i])->GetName().c_str());
                if (((DIST::Array*)formal.parameters[i]) == ((DIST::Array*)actual.parameters[i]))
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                arrayLinksByFuncCalls[(DIST::Array*)formal.parameters[i]].insert((DIST::Array*)actual.parameters[i]);
            }
    }
}

static bool propagateUp(DIST::Array *from, set<DIST::Array*> to, DIST::distFlag flag, bool &change, map<string, vector<Messages>> &SPF_messages)
{
    bool globalChange = false;
    if (from->GetDistributeFlagVal() == flag)
    {
        for (auto &realRef : to)
        {
            auto val = realRef->GetDistributeFlagVal();
            if (val != flag)
            {
                //exclude this case
                if (flag == DIST::IO_PRIV && val == DIST::SPF_PRIV ||
                    flag == DIST::NO_DISTR && val == DIST::SPF_PRIV ||
                    flag == DIST::NO_DISTR && val == DIST::IO_PRIV)
                    ;
                else
                {
                    realRef->SetDistributeFlag(flag);
                    if (flag == DIST::IO_PRIV)
                    {
                        wstring messageE, messageR;
                        __spf_printToLongBuf(messageE, L"Array '%s' can not be distributed because of DVM's I/O constraints", to_wstring(realRef->GetShortName()).c_str());
                        __spf_printToLongBuf(messageR, R68, to_wstring(realRef->GetShortName()).c_str());

                        auto places = realRef->GetDeclInfo();
                        for (auto& place : places)
                            getObjectForFileFromMap(place.first.c_str(), SPF_messages).push_back(Messages(WARR, place.second, messageR, messageE, 1037));
                    }
#if DEB
                    printf("up: set %d %s\n", flag, realRef->GetName().c_str());
#endif
                    change = true;
                    globalChange = true;
                }
            }
        }
    }
    return globalChange;
}

static bool propagateFlag(bool isDown, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, 
                          const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>> &declaredArrays,
                          map<string, vector<Messages>> &SPF_messages)
{
    bool globalChange = false;
    bool change = true;
    while (change)
    {
        change = false;
        for (auto &array : declaredArrays)
        {
            set<DIST::Array*> realArrayRefs;
            getRealArrayRefs(array.second.first, array.second.first, realArrayRefs, arrayLinksByFuncCalls);

            bool allNonDistr = true;
            bool allDistr = true;
            bool nonDistrSpfPriv = false;
            bool nonDistrIOPriv = false;
            bool init = false;

            // propagate SPF to down calls
            for (auto &realRef : realArrayRefs)
            {
                if (realRef != array.second.first)
                {
                    bool nonDistr = realRef->IsNotDistribute();
                    if (realRef->GetDistributeFlagVal() == DIST::SPF_PRIV)
                        nonDistrSpfPriv = true;
                    else if (realRef->GetDistributeFlagVal() == DIST::IO_PRIV)
                        nonDistrIOPriv = true;

                    allNonDistr = allNonDistr && nonDistr;
                    allDistr = allDistr && !nonDistr;
                    init = true;
                }
            }

            if (init)
            {
                if (allNonDistr && array.second.first->IsNotDistribute() == false)
                {
                    if (isDown)
                    {
                        if (nonDistrSpfPriv)
                        {
                            array.second.first->SetDistributeFlag(DIST::SPF_PRIV);
                            if (DEB)
                                printf("down: set %d %s\n", DIST::SPF_PRIV, array.second.first->GetName().c_str());
                        }
                        else if (nonDistrIOPriv)
                        {
                            array.second.first->SetDistributeFlag(DIST::IO_PRIV);
                            if (DEB)
                                printf("down: set %d %s\n", DIST::IO_PRIV, array.second.first->GetName().c_str());
                        }
                        else
                        {
                            array.second.first->SetDistributeFlag(DIST::NO_DISTR);
                            if (DEB)
                                printf("down: set %d %s\n", DIST::NO_DISTR, array.second.first->GetName().c_str());
                        }
                        change = true;
                        globalChange = true;
                    }
                }
                else
                {
                    if (!isDown)
                    {
                        bool ret = propagateUp(array.second.first, realArrayRefs, DIST::SPF_PRIV, change, SPF_messages);
                        globalChange = globalChange || ret;
                        ret = propagateUp(array.second.first, realArrayRefs, DIST::IO_PRIV, change, SPF_messages);
                        globalChange = globalChange || ret;
                        ret = propagateUp(array.second.first, realArrayRefs, DIST::NO_DISTR, change, SPF_messages);
                        globalChange = globalChange || ret;
                    }
                }
            }
        }
    }

    return globalChange;
}

void propagateArrayFlags(const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, 
                         const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>> &declaredArrays,
                         map<string, vector<Messages>> &SPF_messages)
{
    bool change = true;
    while (change)
    {
        bool changeD = propagateFlag(true, arrayLinksByFuncCalls, declaredArrays, SPF_messages);
        bool changeU = propagateFlag(false, arrayLinksByFuncCalls, declaredArrays, SPF_messages);

        change = changeD || changeU;
    }
}

static void aggregateUsedArrays(map<string, FuncInfo*> &funcByName, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    //change to real refs
    for (auto &func : funcByName)
    {
        {
            set<DIST::Array*> curr = func.second->allUsedArrays;
            set<DIST::Array*> newRefs;
            for (auto &array : curr)
                getRealArrayRefs(array, array, newRefs, arrayLinksByFuncCalls);
            func.second->allUsedArrays.clear();
            for (auto &newArray : newRefs)
                if (newArray->IsNotDistribute() == false)
                    func.second->allUsedArrays.insert(newArray);
        }

        {
            set<DIST::Array*> curr = func.second->usedArraysWrite;
            set<DIST::Array*> newRefs;
            for (auto &array : curr)
                getRealArrayRefs(array, array, newRefs, arrayLinksByFuncCalls);
            func.second->usedArraysWrite.clear();
            for (auto &newArray : newRefs)
                if (newArray->IsNotDistribute() == false)
                    func.second->usedArraysWrite.insert(newArray);
        }
    }

    bool changed = true;
    while (changed)
    {
        changed = false;
        for (auto &func : funcByName)
        {
            for (auto &callsFrom : func.second->callsFrom)
            {
                auto itF = funcByName.find(callsFrom);
                if (itF != funcByName.end())
                {
                    for (auto &usedArray : itF->second->allUsedArrays)
                    {
                        auto itA = func.second->allUsedArrays.find(usedArray);
                        if (itA == func.second->allUsedArrays.end())
                        {
                            changed = true;
                            func.second->allUsedArrays.insert(usedArray);
                        }
                    }
                }
            }
        }
    }

    changed = true;
    while (changed)
    {
        changed = false;
        for (auto &func : funcByName)
        {
            for (auto &callsFrom : func.second->callsFrom)
            {
                auto itF = funcByName.find(callsFrom);
                if (itF != funcByName.end())
                {
                    for (auto &usedArray : itF->second->usedArraysWrite)
                    {
                        auto itA = func.second->usedArraysWrite.find(usedArray);
                        if (itA == func.second->usedArraysWrite.end())
                        {
                            changed = true;
                            func.second->usedArraysWrite.insert(usedArray);
                        }
                    }
                }
            }
        }
    }
}

void createLinksBetweenFormalAndActualParams(map<string, vector<FuncInfo*>> &allFuncInfo, map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls,
                                             const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>> &declaredArrays, 
                                             map<string, vector<Messages>> &SPF_messages, bool keepFiles)
{
    for (auto &funcsOnFile : allFuncInfo)
    {
        for (auto &func : funcsOnFile.second)
        {
            //printf("func %s :\n", func->funcName.c_str());
            const string &name = func->funcName;
            for (auto &caller : func->callsTo)
                for (int i = 0; i < caller->callsFromDetailed.size(); ++i)
                    if (caller->callsFromDetailed[i].detailCallsFrom.first == name)
                        addLinks(caller->callsFromDetailed[i].actualParams, func->funcParams, arrayLinksByFuncCalls);
        }
    }

#if DEB
     printf("BEFORE\n");
     //debug dump
     for (auto& elem : declaredArrays)
     {
          auto array = elem.second.first;
          auto flag = array->GetDistributeFlagVal();
          // int { DISTR = 0, NO_DISTR, SPF_PRIV, IO_PRIV } distFlagType;
          string flagS = "";
          if (flag == DIST::DISTR)
               flagS = "DISTR";
          else if (flag == DIST::NO_DISTR)
               flagS = "NO_DISTR";
          else if (flag == DIST::SPF_PRIV)
               flagS = "SPF_PRIV";
          else if (flag == DIST::IO_PRIV)
               flagS = "IO_PRIV";

          printf("%s %s flag %s\n", array->GetShortName(), array->GetName(), flagS.c_str());
     }
#endif

    if (keepFiles)
    {
        FILE *file = fopen("_arrayLinksByCalls.txt", "w");
        for (auto &elem : arrayLinksByFuncCalls)
        {
            fprintf(file, "%s -> ", elem.first->GetName().c_str());
            for (auto &rhs : elem.second)
                fprintf(file, " %s ", rhs->GetName().c_str());
            fprintf(file, "\n");
        }
        fclose(file);
    }
    propagateArrayFlags(arrayLinksByFuncCalls, declaredArrays, SPF_messages);

    //propagate distr state
    bool change = true;
    while (change)
    {
        change = false;
        for (auto &array : declaredArrays)
        {
            set<DIST::Array*> realArrayRefs;
            getRealArrayRefs(array.second.first, array.second.first, realArrayRefs, arrayLinksByFuncCalls);

            if (realArrayRefs.size() && (*realArrayRefs.begin()) != array.second.first &&
                !(*realArrayRefs.begin())->IsNotDistribute() && array.second.first->IsNotDistribute())
            {
                array.second.first->SetDistributeFlag(DIST::DISTR);
                change = true;
            }
        }
    }

    map<string, FuncInfo*> funcByName;
    createMapOfFunc(allFuncInfo, funcByName);
    aggregateUsedArrays(funcByName, arrayLinksByFuncCalls);


#if DEB
     printf("AFTER\n");
    //debug dump
    for (auto &elem : declaredArrays)
    {
        auto array = elem.second.first;
        auto flag = array->GetDistributeFlagVal();
        // int { DISTR = 0, NO_DISTR, SPF_PRIV, IO_PRIV } distFlagType;
        string flagS = "";
        if (flag == DIST::DISTR)
            flagS = "DISTR";
        else if (flag == DIST::NO_DISTR)
            flagS = "NO_DISTR";
        else if (flag == DIST::SPF_PRIV)
            flagS = "SPF_PRIV";
        else if (flag == DIST::IO_PRIV)
            flagS = "IO_PRIV";

        printf("%s %s flag %s\n", array->GetShortName(), array->GetName(), flagS.c_str());
     }
     printf("");
#endif
}

bool detectMpiCalls(const map<string, vector<FuncInfo*>>& allFuncInfo, map<string, vector<Messages>>& SPF_messages)
{
    bool retVal = false;

    map<string, FuncInfo*> funcByName;
    createMapOfFunc(allFuncInfo, funcByName);

    for (auto& byFile : allFuncInfo)
    {
        for (auto& func : byFile.second)
        {
            for (auto& callInfo: func->callsFromDetailed)
            {
                auto& callsFromThis = callInfo.detailCallsFrom;
                if (isMpiFunction(callsFromThis.first) && funcByName.find(callsFromThis.first) == funcByName.end())
                {
                    retVal = true;

                    wstring messageE, messageR;
                    __spf_printToLongBuf(messageE, L"Detected mpi call, turn on special regime of paralyzing");
                    __spf_printToLongBuf(messageR, R148);

                    SPF_messages[byFile.first].push_back(Messages(NOTE, callsFromThis.second, messageR, messageE, 1051));
                }
            }
        }
    }

    return retVal;
}

void excludeArraysFromDistribution(const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls, 
                                   const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>> declaredArrays,
                                   map<string, vector<LoopGraph*>>& loopGraph, 
                                   vector<ParallelRegion*> parallelRegions,
                                   map<string, vector<Messages>>& SPF_messages,
                                   map<tuple<int, string, string>, DIST::Array*>& createdArrays,
                                   int sharedMemoryParallelization)
{
    checkArraysMapping(loopGraph, SPF_messages, arrayLinksByFuncCalls);
    propagateArrayFlags(arrayLinksByFuncCalls, declaredArrays, SPF_messages);

    if (sharedMemoryParallelization == 0)
    {
        for (int z = 0; z < parallelRegions.size(); ++z)
            filterArrayInCSRGraph(loopGraph, allFuncInfo, parallelRegions[z], arrayLinksByFuncCalls, SPF_messages);
        propagateArrayFlags(arrayLinksByFuncCalls, declaredArrays, SPF_messages);
    }

    for (auto& loopByFile : loopGraph)
    {
        for (auto& loop : loopByFile.second)
        {
            loop->removeNonDistrArrays();
            loop->removeGraphData();
        }
    }

    for (auto& funcByFile : allFuncInfo)
        for (auto& func : funcByFile.second)
            func->removeNonDistrArrays();

    //restore
    for (int z = 0; z < parallelRegions.size(); ++z)
    {
        parallelRegions[z]->GetAllArraysToModify().cleanData();
        parallelRegions[z]->GetGraphToModify().ClearGraphCSR();
        parallelRegions[z]->GetReducedGraphToModify().ClearGraphCSR();
    }
    createdArrays.clear();
}

void compliteArrayUsage(DIST::Arrays<int>& allArraysForRegion,
                        map<tuple<int, string, string>, DIST::Array*>& createdArrays,
                        const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                        const map<DIST::Array*, tuple<int, string, string>>& tableOfUniqNamesByArray)
{
    set<DIST::Array*> usedArraysLocal;
    usedArraysLocal.insert(allArraysForRegion.GetArrays().begin(), allArraysForRegion.GetArrays().end());

    //add array that linked with used
    for (auto& used : usedArraysLocal)
    {
        set<DIST::Array*> allArrayRefs;
        getAllArrayRefs(used, used, allArrayRefs, arrayLinksByFuncCalls);

        for (auto& array : allArrayRefs)
        {
            if (usedArraysLocal.find(array) == usedArraysLocal.end())
            {
                auto key = tableOfUniqNamesByArray.find(array);
                if (key != tableOfUniqNamesByArray.end())
                {
                    createdArrays.insert(make_pair(key->second, key->first));
                    allArraysForRegion.AddArrayToGraph(array);
                }
            }
        }
    }
}

void remoteNotUsedArrays(map<tuple<int, string, string>, DIST::Array*>& createdArrays,
                         const set<DIST::Array*>& usedArraysAcrossRegions,
                         const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{
    //remove arrays that is not used
    map<tuple<int, string, string>, DIST::Array*> createdArraysNew;
    for (auto it = createdArrays.begin(); it != createdArrays.end(); ++it)
    {
        if (usedArraysAcrossRegions.find(it->second) != usedArraysAcrossRegions.end())
            createdArraysNew.insert(*it);
        else
        {
            set<DIST::Array*> realArrayRefs;
            getRealArrayRefs(it->second, it->second, realArrayRefs, arrayLinksByFuncCalls);

            for (auto& array : realArrayRefs)
            {
                if (array == it->second)
                    continue;

                if (usedArraysAcrossRegions.find(array) != usedArraysAcrossRegions.end())
                    createdArraysNew.insert(*it);
            }
        }
    }
    createdArrays = createdArraysNew;
}
