#include "../Utils/leak_detector.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <vector>
#include <string>
#include <algorithm>

#include "../Utils/types.h"
#include "DvmhDirective.h"
#include "DvmhDirective_func.h"
#include "DvmhDirective_internal.h"
#include "../Utils/errors.h"
#include "../Utils/SgUtils.h"
#include "../Sapfor.h"
#include "../GraphCall/graph_calls_func.h"

#include "dvm.h"

using std::vector;
using std::tuple;
using std::get;
using std::string;
using std::pair;
using std::set;
using std::map;

using std::make_pair;

static vector<SgExpression*>
compliteTieListNoDist(const LoopGraph* currLoop, 
                      const set<string>& privates, 
                      const vector<LoopGraph*>& loops,
                      const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                      const map<string, set<SgSymbol*>>& byUseInFunc,
                      File* file, const pair<int, int>& lineRange)
{
    vector<SgExpression*> tieList;

    if (currLoop->usedArraysAll.size() == 0)
        return tieList;

    SgVarRefExp* zeroS = new SgVarRefExp(findSymbolOrCreate(file, "*"));

    for (auto& elem : currLoop->usedArraysAll)
    {
        if (privates.find(elem->GetShortName()) != privates.end())
            continue;

        auto type = elem->GetDeclSymbol(currLoop->fileName, lineRange, getAllFilesInProject())->GetOriginal()->type();
        SgSymbol* arrayS = getFromModule(byUseInFunc, findSymbolOrCreate(file, elem->GetShortName(), type));
        SgArrayRefExp* array = new SgArrayRefExp(*arrayS);
        bool needToAdd = false;

        vector<SgExpression*> subs;
        for (int k = 0; k < elem->GetDimSize(); ++k)
            subs.push_back(&zeroS->copy());

        for (int z = 0; z < loops.size(); ++z)
        {
            currLoop = loops[z];

            for (const auto& source : { currLoop->readOpsForLoop, currLoop->writeOpsForLoop }) {
                auto array_it = source.find(elem);

                if (array_it != source.end()) {
                    bool dim_found = false;

                    for (int i = 0; i < array_it->second.size(); i++) {
                        if (array_it->second[i].coefficients.size() != 0)
                        {
                            needToAdd = true;
                            dim_found = true;
                            subs[i] = new SgVarRefExp(findSymbolOrCreate(file, currLoop->loopSymbol));
                            break;
                        }
                    }
                    
                    if (dim_found)
                        break;
                }
            }
        }

        if (needToAdd)
        {
            for (int k = 0; k < subs.size(); ++k)
                array->addSubscript(*subs[k]);
            tieList.push_back(array);
        }
    }

    return tieList;
}

Directive*
ParallelDirective::genDirectiveNoDist(File* file, LoopGraph* currLoop, DIST::Arrays<int>& allArrays, 
                                      const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls)
{
    const set<DIST::Array*>& acrossOutAttribute = currLoop->acrossOutAttribute;
    const map<DIST::Array*, pair<vector<ArrayOp>, vector<bool>>>& readOps = currLoop->readOps;
    map< DIST::Array*, vector<ArrayOp>>& remoteReads = currLoop->remoteRegularReads;

    Statement* loop = currLoop->loop;
    string directive = "";
    vector<Expression*> dirStatement = { NULL, NULL, NULL };

    SgForStmt* loopG = (SgForStmt*)loop->GetOriginal();

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

    SgStatement* realStat = getRealStat(file->filename(), currLoop->lineNum, currLoop->altLineNum);
    SgStatement* parentFunc = getFuncStat(realStat);
    const map<string, set<SgSymbol*>> byUseInFunc = moduleRefsByUseInFunction(realStat);
    const int nested = countPerfectLoopNest(loopG);
    const pair<int, int> lineRange = make_pair(parentFunc->lineNumber(), parentFunc->lastNodeOfStmt()->lineNumber());
    const string& filename = currLoop->fileName;

    vector<SgSymbol*> loopSymbs;
    vector<LoopGraph*> loops;
    LoopGraph* pLoop = currLoop;
    const set<string> allFiles = getAllFilesInProject();

    map<string, DIST::Array*> arrayByName;
    for (DIST::Array* arr : currLoop->getAllArraysInLoop())
        arrayByName[arr->GetName()] = arr;

    for (int z = 0; z < nested; ++z)
    {
        loopSymbs.push_back(loopG->symbol());
        auto next = loopG->lexNext();
        auto attrSpfPar = getAttributes<SgStatement*, SgStatement*>(next, set<int>{ SPF_PARAMETER_OP });
        while (attrSpfPar.size() != 0 && next)
        {
            next = next->lexNext();
            attrSpfPar = getAttributes<SgStatement*, SgStatement*>(next, set<int>{ SPF_PARAMETER_OP });
        }

        if (next->variant() != FOR_NODE && z + 1 < nested)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        loopG = (SgForStmt*)next;

        loops.push_back(pLoop);
        if (pLoop->children.size())
            pLoop = pLoop->children[0];
    }

    SgExpression* expr = new SgExpression(EXPR_LIST);
    SgExpression* p = expr;

    directive += "!DVM$ PARALLEL(";
    //filter parallel
    vector<string> filteredParalel;
    for (int i = 0; i < (int)parallel.size(); ++i)
        if (parallel[i] != "*")
            filteredParalel.push_back(parallel[i]);
    set<SgSymbol*> privatesAfterSwap = changeLoopOrder(parallel, filteredParalel, loops);

    for (int i = 0; i < (int)filteredParalel.size(); ++i)
    {
        if (filteredParalel[i] == "*")
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        if (i == 0)
            directive += filteredParalel[i];
        else
            directive += "," + filteredParalel[i];

        SgVarRefExp* tmp = NULL;
        tmp = new SgVarRefExp(findSymbolOrCreate(file, filteredParalel[i]));

        p->setLhs(tmp);
        if (i != (int)filteredParalel.size() - 1)
            p = createAndSetNext(RIGHT, EXPR_LIST, p);
        else
            p->setRhs(NULL);
    }

    dirStatement[2] = new Expression(expr);

    directive += ")";

    SgArrayRefExp* arrayExpr = NULL;
    string arrayExprS = "";

    expr = new SgExpression(EXPR_LIST);
    p = expr;

    dirStatement[1] = NULL;

    set<string> uniqNamesOfPrivates;
    for (auto& elem : privates)
        uniqNamesOfPrivates.insert(elem->identifier());

    auto unitedPrivates = privates;
    for (auto& elem : privatesAfterSwap)
    {
        if (uniqNamesOfPrivates.find(elem->identifier()) == uniqNamesOfPrivates.end())
        {
            unitedPrivates.insert(new Symbol(elem));
            uniqNamesOfPrivates.insert(elem->identifier());
        }
    }

    if (unitedPrivates.size() != 0)
    {
        p = createAndSetNext(LEFT, ACC_PRIVATE_OP, p);

        directive += ", PRIVATE(";
        int k = 0;
        vector<SgExpression*> list;
        auto spfParVars = fillPrivateOnlyFromSpfParameter(loop, currLoop->lineNum < 0 ? currLoop->altLineNum : 0);
        for (auto& privVar : setToMapWithSortByStr(unitedPrivates))
        {
            bool isSfpPriv = false;
            for (auto& elem : spfParVars)
                if (OriginalSymbol(elem)->identifier() == string(OriginalSymbol(privVar.second)->identifier()))
                    isSfpPriv = true;

            if (isSfpPriv)
                continue;

            directive += (k != 0) ? "," + privVar.first : privVar.first;
            list.push_back(new SgVarRefExp(getFromModule(byUseInFunc, privVar.second)));
            ++k;
        }
        directive += ")";
        dirStatement[1] = new Expression(expr);

        p->setLhs(makeExprList(list));
    }

    vector<LoopGraph*> loopsTie;
    for (int i = 0; i < (int)parallel.size(); ++i)
        if (parallel[i] != "*")
            loopsTie.push_back(loops[i]);

    vector<SgExpression*> tieList;
    tieList = compliteTieListNoDist(currLoop, uniqNamesOfPrivates, loopsTie, arrayLinksByFuncCalls, byUseInFunc, file, lineRange);

    if (tieList.size())
    {
        if (dirStatement[1] != NULL)
        {
            expr = createAndSetNext(RIGHT, EXPR_LIST, expr);
            p = expr;
        }
        p = createAndSetNext(LEFT, ACC_TIE_OP, p);
        p->setLhs(makeExprList(tieList));

        directive += ", TIE(";
        int k = 0;
        for (auto& tieL : tieList)
        {
            if (k != 0)
                directive += ",";
            directive += tieL->unparse();
            ++k;
        }
        directive += ")";
    }
    set<DIST::Array*> arraysInAcross;
    if (across.size() != 0)
    {
        if (acrossShifts.size() == 0)
        {
            acrossShifts.resize(across.size());
            for (int i = 0; i < across.size(); ++i)
                acrossShifts[i].resize(across[i].second.size());
        }

        //TODO: add "OUT" key for string representation
        string acrossAdd = ", ACROSS(";
        int inserted = 0;
        SgExpression* acr_out = new SgExpression(EXPR_LIST);
        SgExpression* p_out = acr_out;

        SgExpression* acr_in = new SgExpression(EXPR_LIST);
        SgExpression* p_in = acr_in;

        SgExpression* acr_op = NULL;
        int inCount = 0;
        int outCount = 0;

        vector<int> ordered = sortShadow(across);
        for (int k = 0; k < (int)across.size(); ++k)
        {
            const int i1 = ordered[k];
            vector<map<pair<int, int>, int>> shiftsByAccess;

            auto currArray_it = arrayByName.find(across[i1].first.second);

            if (currArray_it == arrayByName.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            DIST::Array* currArray = currArray_it->second;

            bool isOut = acrossOutAttribute.find(currArray) != acrossOutAttribute.end();
            string bounds = genBoundsNoDist(across[i1], acrossShifts[i1], currArray, readOps, arraysInAcross, shiftsByAccess, arrayLinksByFuncCalls);
            if (bounds != "")
            {
                if (inserted != 0)
                {
                    acrossAdd += ",";
                    if (isOut)
                    {
                        if (outCount > 0)
                            p_out = createAndSetNext(RIGHT, EXPR_LIST, p_out);
                        outCount++;
                        p = p_out;
                    }
                    else
                    {
                        if (inCount > 0)
                            p_in = createAndSetNext(RIGHT, EXPR_LIST, p_in);
                        inCount++;
                        p = p_in;
                    }
                }
                else if (inserted == 0)
                {
                    if (dirStatement[1] != NULL)
                        expr = createAndSetNext(RIGHT, EXPR_LIST, expr);
                    acr_op = createAndSetNext(LEFT, ACROSS_OP, expr);

                    if (isOut)
                    {
                        outCount++;
                        p = p_out;
                    }
                    else
                    {
                        inCount++;
                        p = p_in;
                    }
                }

                acrossAdd += across[i1].first.first + "(" + bounds + ")";

                SgArrayRefExp* newArrayRef = new SgArrayRefExp(*getFromModule(byUseInFunc, currArray->GetDeclSymbol(filename, lineRange, allFiles)->GetOriginal()));
                newArrayRef->addAttribute(ARRAY_REF, currArray, sizeof(DIST::Array));

                for (auto& elem : genSubscripts(across[i1].second, acrossShifts[i1]))
                    newArrayRef->addSubscript(*elem);

                p->setLhs(newArrayRef);
                inserted++;
            }
        }
        acrossAdd += ")";

        if (inserted > 0)
        {
            directive += acrossAdd;

            if (dirStatement[1] == NULL)
                dirStatement[1] = new Expression(expr);

            if (acrossOutAttribute.size() > 0)
            {
                SgExpression* tmp = new SgExpression(DDOT, new SgKeywordValExp("OUT"), acr_out, NULL);
                acr_op->setLhs(*tmp);
                if (inCount != 0)
                    acr_op->setRhs(acr_in);
            }
            else
                acr_op->setLhs(acr_in);
        }
    }

    if (reduction.size() != 0)
    {
        if (dirStatement[1] != NULL)
        {
            expr = createAndSetNext(RIGHT, EXPR_LIST, expr);
            p = expr;
        }

        p = createAndSetNext(LEFT, REDUCTION_OP, p);
        p = createAndSetNext(LEFT, EXPR_LIST, p);

        directive += ", REDUCTION(";
        int k = 0;
        for (auto it = reduction.begin(); it != reduction.end(); ++it)
        {
            const string& nameGroup = it->first;
            for (auto& list : it->second)
            {
                if (k != 0)
                {
                    directive += ",";
                    p = createAndSetNext(RIGHT, EXPR_LIST, p);
                }

                SgSymbol* base = findSymbolOrCreate(file, correctSymbolModuleName(list), NULL, getModuleScope(list, moduleList, parentFunc));
                SgSymbol* redS = getFromModule(byUseInFunc, base, list.find("::") != string::npos);
                directive += nameGroup + "(" + redS->identifier() + ")";

                SgVarRefExp* tmp2 = new SgVarRefExp(redS);
                SgFunctionCallExp* tmp1 = new SgFunctionCallExp(*findSymbolOrCreate(file, nameGroup), *tmp2);

                p->setLhs(tmp1);
                ++k;
            }
        }
        if (reductionLoc.size() != 0)
            directive += ", ";
        else
        {
            directive += ")";

            if (dirStatement[1] == NULL)
                dirStatement[1] = new Expression(expr);
        }
    }

    if (reductionLoc.size() != 0)
    {
        if (dirStatement[1] != NULL && reduction.size() == 0)
        {
            expr = createAndSetNext(RIGHT, EXPR_LIST, expr);
            p = expr;
        }

        if (reduction.size() == 0)
        {
            p = createAndSetNext(LEFT, REDUCTION_OP, p);
            p = createAndSetNext(LEFT, EXPR_LIST, p);
            directive += ", REDUCTION(";
        }
        else
            p = createAndSetNext(RIGHT, EXPR_LIST, p);

        int k = 0;
        for (auto it = reductionLoc.begin(); it != reductionLoc.end(); ++it)
        {
            const string& nameGroup = it->first;
            for (auto& list : it->second)
            {
                if (k != 0)
                {
                    directive += ",";
                    p = createAndSetNext(RIGHT, EXPR_LIST, p);
                }

                SgSymbol* base1 = findSymbolOrCreate(file, correctSymbolModuleName(get<0>(list)), NULL, getModuleScope(get<0>(list), moduleList, parentFunc));
                SgSymbol* base2 = findSymbolOrCreate(file, correctSymbolModuleName(get<1>(list)), NULL, getModuleScope(get<1>(list), moduleList, parentFunc));

                SgSymbol* redS1 = getFromModule(byUseInFunc, base1, get<0>(list).find("::") != string::npos);
                SgSymbol* redS2 = getFromModule(byUseInFunc, base2, get<1>(list).find("::") != string::npos);

                directive += nameGroup + "(" + redS1->identifier() + ", " + redS2->identifier() + ", " + std::to_string(get<2>(list)) + ")";

                SgFunctionCallExp* tmp1 = new SgFunctionCallExp(*findSymbolOrCreate(file, nameGroup));

                tmp1->addArg(*new SgVarRefExp(redS1));
                tmp1->addArg(*new SgVarRefExp(redS2));
                tmp1->addArg(*new SgValueExp(get<2>(list)));

                p->setLhs(tmp1);
                ++k;
            }
        }
        directive += ")";

        if (dirStatement[1] == NULL)
            dirStatement[1] = new Expression(expr);
    }

    directive += "\n";

    auto dir = new CreatedDirective(directive, dirStatement);
    dir->line = currLoop->lineNum;
    return dir;
}