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

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <vector>
#include <map>
#include <set>

#include "../ParallelizationRegions/ParRegions.h"
#include "../Distribution/Arrays.h"
#include "../Transformations/loop_transform.h"

#include "../Utils/errors.h"
#include "directive_parser.h"
#include "directive_creator.h"
#include "directive_creator_internal.h"
#include "directive_creator_nodist.h"

#define PRINT_PROF_INFO  1
#define PRINT_DIR_RESULT 0

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;


void createParallelDirectivesNoDist(const map<LoopGraph*, map<DIST::Array*, ArrayInfo*>> &loopInfos,
                                    const vector<ParallelRegion*>& regions,
                                    const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls,
                                    vector<Messages> &messages)
{
    for (auto &loopInfo : loopInfos)
    {
        LoopGraph* currLoop = loopInfo.first;
        ParallelRegion *currReg = getRegionByLine(regions, currLoop->fileName.c_str(), currLoop->lineNum);
        if (currReg == NULL || currLoop->userDvmDirective != NULL)
        {
            __spf_print(PRINT_PROF_INFO, "Skip loop on file %s and line %d\n", currLoop->fileName.c_str(), currLoop->lineNum);
            continue;
        }

        const int itersCount = currLoop->calculatedCountOfIters;
        uint64_t regId = (uint64_t)currLoop;

        const DIST::Arrays<int> &allArrays = currReg->GetAllArrays();

        vector<pair<pair<string, string>, vector<pair<int, int>>>> acrossInfo;
        fillAcrossInfoFromDirectives(currLoop, acrossInfo);
        bool hasConflict = false;
        // uniqKey -> pair<position of access, pair<acces>> ///write acceses ///
        map<DIST::Array*, pair<int, pair<int, int>>, DIST::ArrayComparator> arrayWriteAcc;
        set<DIST::Array*> acrossOutArrays;

        __spf_print(PRINT_DIR_RESULT, "  Loop on line %d:\n", currLoop->lineNum);

        const map<DIST::Array*, ArrayInfo*> &currAccesses = loopInfo.second;
        // find conflict and fill arrayWriteAcc
        hasConflict = checkForConflict(currAccesses, currLoop, arrayWriteAcc, acrossInfo, acrossOutArrays);
        if (hasConflict)
            __spf_print(PRINT_DIR_RESULT, "    has conflict\n");
        else
        {
            if (!currLoop->hasLimitsToParallel() &&
                (currLoop->lineNum > 0 || (currLoop->lineNum < 0 && currLoop->altLineNum > 0)))
            {
                ParallelDirective* parDir = new ParallelDirective();
#if __SPF
                parDir->langType = LANG_F;
#else
                parDir->langType = LANG_C;
#endif
                parDir->line = currLoop->lineNum;
                parDir->col = 0;
                parDir->file = currLoop->fileName;
                    
                fillInfoFromDirectives(currLoop, parDir);


                parDir->parallel.push_back(currLoop->loopSymbol);

                currLoop->directive = parDir;

                currLoop->acrossOutAttribute.insert(acrossOutArrays.begin(), acrossOutArrays.end());

                addShadowFromAnalysis(parDir, currAccesses);
                currLoop->directiveForLoop = new ParallelDirective(*currLoop->directive);
                
                __spf_print(PRINT_DIR_RESULT, "   directive created\n");
            }
        }
    }
}


void selectParallelDirectiveForVariantNoDist(File* file, ParallelRegion* currParReg,
                                       DIST::Arrays<int>& allArrays,
                                       const vector<LoopGraph*>& loopGraph,
                                       const map<int, LoopGraph*>& mapLoopsInFile,
                                       const map<string, FuncInfo*>& mapFuncInfo,
                                       vector<Directive*>& toInsert,
                                       const uint64_t regionId,
                                       const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCalls,
                                       const map<LoopGraph*, void*>& depInfoForLoopGraph,
                                       vector<Messages>& messages)
{
    for (int i = 0; i < loopGraph.size(); ++i)
    {
        LoopGraph* loop = loopGraph[i];
        const uint64_t loopRegId = (uint64_t)loop;
        const bool hasDirective = loop->directive;
        const bool noLimits = loop->hasLimitsToParallel() == false;
        const bool isMyRegion = loop->region == currParReg;
        const bool noUserDir = loop->userDvmDirective == NULL;

        if (hasDirective && noLimits && isMyRegion && noUserDir)
        {
            if (loop->perfectLoop >= 1)
            {
                ParallelDirective* parDirective = loop->directive;
                parDirective->cloneOfTemplate = "";

                //try to unite loops
                if (createNestedLoops(loop, depInfoForLoopGraph, mapFuncInfo, messages))
                    parDirective = loop->recalculateParallelDirective();
                // rewrite bool topCheck = isOnlyTopPerfect(loop, distribution);

                Directive* dirImpl = parDirective->genDirectiveNoDist(file, loop, allArrays, getTrueRegId(regionId, loopRegId), arrayLinksByFuncCalls);

#if __SPF
                //move label before loop
                if (loop->hasRedistribute())
                {
                    auto prev = loop->loop->lexPrev();
                    if (!prev)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    moveLabelBefore(prev, loop->loop);
                }
                else if(loop->lineNum > 0)
                    moveLabelBefore(loop->loop, NULL);

                // check correctness
                if (loop->lineNum < 0)
                {
                    if (loop->altLineNum == -1)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    SgStatement* local = NULL;
                    local = SgStatement::getStatementByFileAndLine(loop->loop->fileName(), loop->lineNum);
                    if (local == NULL)
                        local = SgStatement::getStatementByFileAndLine(loop->loop->fileName(), loop->altLineNum);
                    checkNull(local, convertFileName(__FILE__).c_str(), __LINE__);
                }
#endif
                 toInsert.push_back(dirImpl);
            }
        }
        else //TODO: add checker for indexing in this loop
        {
            if (loopGraph[i]->children.size() != 0)
                selectParallelDirectiveForVariantNoDist(file, currParReg, allArrays, loopGraph[i]->children, mapLoopsInFile, mapFuncInfo,
                                                        toInsert, regionId, arrayLinksByFuncCalls,depInfoForLoopGraph, messages);
        }
    }
}

#undef PRINT_PROF_INFO
#undef PRINT_DIR_RESULT 