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

#include <string>
#include <fstream>
#include <iostream>
#include <algorithm>

#include <vector>
#include <map>
#include <set>
#include <utility>
#include <assert.h>

#include "../DirectiveProcessing/directive_creator_nodist.h"

#include "loop_analyzer_internal.h"
#include "loop_analyzer_nodist.h"

using std::vector;
using std::pair;
using std::tuple;
using std::map;
using std::set;
using std::make_pair;
using std::make_tuple;
using std::get;
using std::string;
using std::wstring;
using std::stack;

extern void createMapLoopGraph(map<int, LoopGraph*>& sortedLoopGraph, const vector<LoopGraph*>* loopGraph);

extern map<DIST::Array*, std::tuple<int, string, string>> tableOfUniqNamesByArray;
static void convertOneLoopNoDist(LoopGraph* currLoop, map<LoopGraph*, map<DIST::Array*, ArrayInfo*>>& outInfo,
                                 const map<SgSymbol*, ArrayInfo>& toConvert,
                                 const set<string>& privateArrays,
                                 const map<string, vector<SgExpression*>>& commonBlocks,
                                 const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>>& declaredArrays,
                                 const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                                 map<tuple<int, string, string>, DIST::Array*>& createdArrays,
                                 bool freeArrays = false)
{
    map<DIST::Array*, ArrayInfo*> toAdd;
    for (auto& conv : toConvert)
    {
        SgSymbol* currentArray = OriginalSymbol(conv.first);
        ArrayInfo* currentInfo = (ArrayInfo*)(&conv.second);

        DIST::Array* arrayToAdd;

        SgStatement* decl = declaratedInStmt(currentArray);
        const char* symbIdent = currentArray->identifier();

        const tuple<int, string, string> uniqKey = getUniqName(commonBlocks, decl, currentArray);

        auto itFound = createdArrays.find(uniqKey);
        if (itFound == createdArrays.end())
        {
            auto itArray = declaredArrays.find(uniqKey);
            if (itArray == declaredArrays.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            arrayToAdd = itArray->second.first;
            itFound = createdArrays.insert(itFound, make_pair(uniqKey, arrayToAdd));
        }
        else
            arrayToAdd = itFound->second;

        set<DIST::Array*> links;
        getRealArrayRefs(arrayToAdd, arrayToAdd, links, arrayLinksByFuncCalls);

        int countOflinks = 0;
        for (auto& linkedArray : links)
        {
            if (arrayToAdd == linkedArray)
                continue;

            ++countOflinks;
            auto key = tableOfUniqNamesByArray[linkedArray];
            auto value = declaredArrays.find(key)->second;
            if (value.second == 0 && createdArrays.find(key) == createdArrays.end())
                createdArrays.insert(make_pair(key, linkedArray));
        }

        if (freeArrays)
            if (countOflinks == 0)
                continue;

        toAdd[arrayToAdd] = currentInfo;

        for (int z = 0; z < currentInfo->getDimSize(); ++z)
        {
            if (currentInfo->readOps[z].coefficients.size() || currentInfo->writeOps[z].coefficients.size())
            {
                arrayToAdd->SetMappedDim(z);

                for (auto& realRef : links)
                    realRef->SetMappedDim(z);
            }
        }
    }

    outInfo[currLoop] = toAdd;
}

static map<LoopGraph*, map<DIST::Array*, ArrayInfo*>>
convertLoopInfoNoDist(const map<SgForStmt*, map<SgSymbol*, ArrayInfo>>& loopInfo,
                      const map<int, LoopGraph*>& sortedLoopGraph,
                      const set<string>& privateArrays,
                      const map<string, vector<SgExpression*>>& commonBlocks,
                      const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>>& declaredArrays,
                      const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                      map<tuple<int, string, string>, DIST::Array*>& createdArrays)
{
    map<LoopGraph*, map<DIST::Array*, ArrayInfo*>> outInfo;

    for (auto it = loopInfo.begin(); it != loopInfo.end(); ++it)
    {
        auto itGraph = sortedLoopGraph.find(it->first->lineNumber());
        if (itGraph == sortedLoopGraph.end())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        convertOneLoopNoDist(itGraph->second, outInfo, it->second, privateArrays, commonBlocks, declaredArrays, arrayLinksByFuncCalls, createdArrays);
    }

    return outInfo;
}

void loopAnalyzerNoDist(SgFile* file, vector<ParallelRegion*>& regions, map<tuple<int, string, string>, DIST::Array*>& createdArrays,
                        vector<Messages>& messagesForFile, const map<string, vector<FuncInfo*>>& AllfuncInfo,
                        const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>>& declaredArrays,
                        const map<SgStatement*, set<tuple<int, string, string>>>& declaratedArraysSt,
                        const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                        const map<SgStatement*, vector<DefUseList>>& defUseByPlace,
                        vector<LoopGraph*>* loopGraph)
{
    currMessages = &messagesForFile;
    currRegime = DATA_DISTR; //?

    map<string, vector<SgExpression*>> commonBlocks;
    map<int, LoopGraph*> sortedLoopGraph;
    map<int, pair<SgForStmt*, pair<set<string>, set<string>>>> allLoops;

    createMapLoopGraph(sortedLoopGraph, loopGraph);

    int funcNum = file->numberOfFunctions();
    __spf_print(PRINT_PROF_INFO, "functions num in file = %d\n", funcNum);

    vector<SgStatement*> modules;
    findModulesInFile(file, modules);

    map<string, SgStatement*> modulesByName;
    for (int i = 0; i < modules.size(); ++i)
        modulesByName[modules[i]->symbol()->identifier()] = modules[i];

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

    const vector<FuncInfo*>& funcInfo = AllfuncInfo.find(file->filename())->second;

    for (int i = 0; i < funcNum; ++i)
    {
        createNeededException();

        string fName = file->functions(i)->symbol()->identifier();
#if _WIN32
        if (file->functions(i)->variant() != MODULE_STMT)
            sendMessage_2lvl(wstring(L"обработка функции '") + wstring(fName.begin(), fName.end()) + L"'");
        else
            sendMessage_2lvl(wstring(L"обработка модуля '") + wstring(fName.begin(), fName.end()) + L"'");
#else
        if (file->functions(i)->variant() != MODULE_STMT)
            sendMessage_2lvl(wstring(L"processing function '") + wstring(fName.begin(), fName.end()) + L"'");
        else
            sendMessage_2lvl(wstring(L"processing module '") + wstring(fName.begin(), fName.end()) + L"'");
#endif

        set<SgSymbol*> delcsSymbViewed;
        set<SgStatement*> delcsStatViewed;

        if (funcInfo[i]->doNotAnalyze)
            continue;

        map<SgForStmt*, map<SgSymbol*, ArrayInfo>> loopInfo;
        set<int> loopWithOutArrays;

        set<string> privatesVars;

        SgStatement* st = file->functions(i);
        string funcName = "";
        if (st->variant() == PROG_HEDR)
        {
            SgProgHedrStmt* progH = (SgProgHedrStmt*)st;
            __spf_print(PRINT_PROF_INFO, "*** Program <%s> started at line %d / %s\n", progH->symbol()->identifier(), st->lineNumber(), st->fileName());
            funcName = progH->symbol()->identifier();
        }
        else if (st->variant() == PROC_HEDR)
        {
            SgProcHedrStmt* procH = (SgProcHedrStmt*)st;
            __spf_print(PRINT_PROF_INFO, "*** Function <%s> started at line %d / %s\n", procH->symbol()->identifier(), st->lineNumber(), st->fileName());
            funcName = procH->symbol()->identifier();
        }
        else if (st->variant() == FUNC_HEDR)
        {
            SgFuncHedrStmt* funcH = (SgFuncHedrStmt*)st;
            __spf_print(PRINT_PROF_INFO, "*** Function <%s> started at line %d / %s\n", funcH->symbol()->identifier(), st->lineNumber(), st->fileName());
            funcName = funcH->symbol()->identifier();
        }

        vector<LoopGraph*> loopsForFunction;
        for (auto& loop : *loopGraph)
        {
            auto fStat = getFuncStat(loop->loop->GetOriginal());
            if (fStat->symbol()->identifier() == funcName)
                loopsForFunction.push_back(loop);
        }

        commonBlocks.clear();
        getCommonBlocksRef(commonBlocks, st, st->lastNodeOfStmt());
        __spf_print(PRINT_PROF_INFO, "  number of common blocks %d\n", (int)commonBlocks.size());

        SgStatement* lastNode = st->lastNodeOfStmt();
        vector<SgForStmt*> parentLoops;
        vector<set<string>> privatesVarsForLoop;

        //For remote access
        pair<SgForStmt*, LoopGraph*>* under_dvm_dir = NULL;

        map<string, pair<SgSymbol*, SgStatement*>> notMappedDistributedArrays;
        set<string> mappedDistrbutedArrays;

        double currentWeight = 1.0;
        while (st != lastNode)
        {
            createNeededException();

            if (st == NULL)
            {
                currMessages->push_back(Messages(ERROR, 1, R128, L"internal error in analysis, parallel directives will not be generated for this file!", 3008));

                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (st->variant() == CONTAINS_STMT)
                break;

            if (!__gcov_doesThisLineExecuted(st->fileName(), st->lineNumber()))
            {
                st = st->lexNext();
                continue;
            }

            const int currentLine = st->lineNumber() < -1 ? st->localLineNumber() : st->lineNumber();
            ParallelRegion* currReg = getRegionByLine(regions, st->fileName(), currentLine);
            if (currReg == NULL)
            {
                st = st->lexNext();
                continue;
            }

            if (isSgExecutableStatement(st) == NULL)
                delcsStatViewed.insert(st);

            //printf("new st with var = %d, on line %d\n", st->variant(), st->lineNumber());
            const int currV = st->variant();
            if (currV == FOR_NODE)
            {   
                tryToFindPrivateInAttributes(st, privatesVars);

                set<string> toAdd;
                tryToFindPrivateInAttributes(st, toAdd);

                if (PRINT_LOOP_STRUCT)
                    printBlanks(2, (int)parentLoops.size());
                __spf_print(PRINT_LOOP_STRUCT, "FOR NODE on line %d\n", st->lineNumber());

                parentLoops.push_back((SgForStmt*)st);
                changeLoopWeight(currentWeight, sortedLoopGraph, st->lineNumber());
                privatesVarsForLoop.push_back(toAdd);
            }
            else if (currV == CONTROL_END)
            {
                SgStatement* contrlParent = st->controlParent();
                if (contrlParent)
                {
                    if (contrlParent->variant() == FOR_NODE)
                    {
                        changeLoopWeight(currentWeight, sortedLoopGraph, contrlParent->lineNumber(), false);

                        if (loopInfo.find((SgForStmt*)contrlParent) == loopInfo.end() && !sortedLoopGraph[contrlParent->lineNumber()]->hasUnknownDistributedMap)
                            loopWithOutArrays.insert(contrlParent->lineNumber());

                        set<string> unitedPrivates;
                        for (int p = 0; p < parentLoops.size(); ++p)
                            for (auto& privVar : privatesVarsForLoop[p])
                                unitedPrivates.insert(privVar);

                        set<string> setDiff;

                        allLoops[contrlParent->lineNumber()] = make_pair((SgForStmt*)contrlParent, make_pair(unitedPrivates, setDiff));
                        parentLoops.pop_back();
                        privatesVarsForLoop.pop_back();
                    }
                }
                else
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
            else if (currV == ASSIGN_STAT)
            {
                if (st->expr(0))
                    findArrayRef(parentLoops, st->expr(0), st->lineNumber(), LEFT, loopInfo, privatesVars, privatesVarsForLoop,
                        sortedLoopGraph, commonBlocks, declaredArrays, false, notMappedDistributedArrays,
                        mappedDistrbutedArrays, st, currReg, currentWeight, arrayLinksByFuncCalls);
                if (st->expr(1))
                    findArrayRef(parentLoops, st->expr(1), st->lineNumber(), RIGHT, loopInfo, privatesVars, privatesVarsForLoop,
                        sortedLoopGraph, commonBlocks, declaredArrays, false, notMappedDistributedArrays,
                        mappedDistrbutedArrays, st, currReg, currentWeight, arrayLinksByFuncCalls);
            }
            else if (currV == IF_NODE || currV == ELSEIF_NODE || currV == LOGIF_NODE || currV == SWITCH_NODE)
            {
                SgStatement* before = NULL;
                if (st->expr(0))
                {
                    findArrayRef(parentLoops, st->expr(0), st->lineNumber(), RIGHT, loopInfo, privatesVars, privatesVarsForLoop,
                        sortedLoopGraph, commonBlocks, declaredArrays, false, notMappedDistributedArrays,
                        mappedDistrbutedArrays, st, currReg, currentWeight, arrayLinksByFuncCalls);
                }
            }
            else if (currV == PROC_STAT)
            {
                auto func = isUserFunctionInProject(st->symbol()->identifier());
                if (func != NULL)
                {
                    SgExpression* parList = st->expr(0);
                    set<DIST::Array*> toRedistr;
                    if (parList)
                    {
                        SgExprListExp* list = isSgExprListExp(parList);
                        for (int z = 0; z < list->length(); ++z)
                        {
                            SgExpression* par = list->elem(z);
                            if ((func->funcParams.inout_types[z] & OUT_BIT) != 0)
                                findArrayRef(parentLoops, par, st->lineNumber(), LEFT, loopInfo, privatesVars, privatesVarsForLoop,
                                    sortedLoopGraph, commonBlocks, declaredArrays, false, notMappedDistributedArrays,
                                    mappedDistrbutedArrays, st, currReg, currentWeight, arrayLinksByFuncCalls);
                            else
                                findArrayRef(parentLoops, par, st->lineNumber(), RIGHT, loopInfo, privatesVars, privatesVarsForLoop,
                                    sortedLoopGraph, commonBlocks, declaredArrays, false, notMappedDistributedArrays,
                                    mappedDistrbutedArrays, st, currReg, currentWeight, arrayLinksByFuncCalls);
                        }

                    }
                }
            }
            else if (currV == USE_STMT)
            {
                if (st->lineNumber() > 0)
                {
                    auto itF = modulesByName.find(st->symbol()->identifier());

                    if (itF == modulesByName.end())
                    {
                        wstring messageE, messageR;
                        __spf_printToLongBuf(messageE, L"Module with name '%s' must be placed in current file", to_wstring(st->symbol()->identifier()).c_str());

                        __spf_printToLongBuf(messageR, R62, to_wstring(st->symbol()->identifier()).c_str());

                        currMessages->push_back(Messages(ERROR, st->lineNumber(), messageR, messageE, 1028));

                        __spf_print(1, "Module at line %d with name '%s' must be placed in current file\n", st->lineNumber(), st->symbol()->identifier());
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    }
                }
            }
            else
            {   
                if (isDVM_stat(st) == false && isSgExecutableStatement(st))
                {
                    int const var = st->variant();
                    int side = (var == READ_STAT || var == WRITE_STAT || var == PRINT_STAT) ? LEFT : RIGHT;

                    for (int z = 0; z < 3; ++z)
                        if (st->expr(z))
                            findArrayRef(parentLoops, st->expr(z), st->lineNumber(), side, loopInfo, privatesVars, privatesVarsForLoop,
                                sortedLoopGraph, commonBlocks, declaredArrays, false, notMappedDistributedArrays,
                                mappedDistrbutedArrays, st, currReg, currentWeight, arrayLinksByFuncCalls);

                }
            }

            st = st->lexNext();
        }

        auto convertedLoopInfo = convertLoopInfoNoDist(loopInfo, sortedLoopGraph, privatesVars, commonBlocks, declaredArrays, arrayLinksByFuncCalls, createdArrays);

        processLoopInformationForFunction(convertedLoopInfo);

        //find dependencies for loops in function
        initAnnotationsSysExt(0);
        set<SgStatement*> funcWasInit;
        map<SgExpression*, string> collection;

        int idx = 0;
        for (auto& loop : convertedLoopInfo)
        {
            ++idx;
            createNeededException();

            string fName = file->functions(i)->symbol()->identifier();
#ifdef _WIN32
            sendMessage_2lvl(wstring(L"обработка цикла ") + std::to_wstring(idx) + L"/" + std::to_wstring(convertedLoopInfo.size()));
#else
            sendMessage_2lvl(wstring(L"processing loop ") + std::to_wstring(idx) + L"/" + std::to_wstring(convertedLoopInfo.size()));
#endif
            tryToFindDependencies(loop.first, allLoops, funcWasInit, file, regions, currMessages, collection, funcByName, defUseByPlace);

        }

        for (auto& loopLine : loopWithOutArrays)
        {
            if (loopLine > 0)
            {
                tryToFindDependencies(sortedLoopGraph[loopLine], allLoops, funcWasInit, file, regions, currMessages, collection, funcByName, defUseByPlace);
                sortedLoopGraph[loopLine]->withoutDistributedArrays = true;

                //TODO: enable linear writes to non distr arrays for CONSISTENT
                bool hasWritesToArray = false;
                //TODO: add IPA for non pure
                bool hasNonPureProcedures = false;

                auto loopRef = sortedLoopGraph[loopLine];
                SgStatement* loopSt = loopRef->loop;

                for (SgStatement* start = loopSt->lexNext(); start != loopSt->lastNodeOfStmt(); start = start->lexNext())
                {

                    if (start->variant() == PROC_STAT && isIntrinsicFunctionName(start->symbol()->identifier()) == 0)
                    {
                        checkNull(isSgCallStmt(start), convertFileName(__FILE__).c_str(), __LINE__);

                        auto itF = funcByName.find(isSgCallStmt(start)->name()->identifier());
                        bool isPure = false;
                        if (itF != funcByName.end())
                            isPure = itF->second->isPure;

                        if (!isPure)
                        {
                            hasNonPureProcedures = true;
                            loopRef->hasNonPureProcedures = true;

                            messagesForFile.push_back(Messages(WARR, start->lineNumber(), R80, L"Only pure procedures were supported", 1044));
                        }
                    }

                    for (int z = 1; z < 3; ++z)
                        if (hasNonPureFunctions(start->expr(z), loopRef, messagesForFile, start->lineNumber(), funcByName))
                            hasNonPureProcedures = true;
                }

                if (hasWritesToArray || hasNonPureProcedures)
                    loopRef->withoutDistributedArrays = false;
            }
        }

        sendMessage_2lvl(L"");

        createParallelDirectivesNoDist(convertedLoopInfo, regions, arrayLinksByFuncCalls, messagesForFile);
        if (parallizeFreeLoops)
            selectFreeLoopsForParallelization(loopsForFunction, funcName, false, regions, messagesForFile);

        __spf_print(PRINT_PROF_INFO, "Function ended\n");
    }
}