#pragma once

#include "graph_calls_func.h"
#include "graph_loops_func.h"
#include "expr_transform.h"
#include "../ParallelizationRegions/ParRegions.h"
#include "SgUtils.h"
#include "ReadWriteAnalyzer.h"
#include "DvmhRegion.h"


typedef std::set<DIST::Array* > ArraySet;
struct ReadWrite
{
    ArraySet read;
    ArraySet write;
};
typedef std::map<int, ReadWrite> UsageByLine;
typedef std::map<std::string, UsageByLine> UsageByFile;


class DvmhRegionInserter 
{
    // input data
    SgFile *file;
    std::map<int, LoopGraph*> loopGraphMap;
    const std::vector<LoopGraph*> loopGraph;
    const std::map<std::string, FuncInfo*> allFunctions;
    const std::vector<FuncInfo*> funcsForFile;
    bool isMpiProgram;

    ReadWriteAnalyzer& rw_analyzer;
    std::map<FuncInfo*, std::set<LoopGraph*>> parallel_functions;
    std::set<DIST::Array*> writesToArraysInParallelLoops;
    std::set<DIST::Array*> usedArraysInParallelLoops;
    const std::map<DIST::Array*, std::set<DIST::Array*>>& arrayLinksByFuncCalls;
    // operating data
    std::vector<DvmhRegion*> regions;

    // region directives
    void findEdgesForRegions(const std::vector<LoopGraph*>&);
    bool hasLimitsToDvmhParallel(const LoopGraph*) const;
    void insertRegionDirectives();

    // actual directives
    ArraySet symbs_to_arrs(std::set<SgSymbol*>) const;
    ArraySet get_used_arrs(SgStatement* st, int usage_type) const;
    ArraySet get_used_arrs_for_block(SgStatement* st, int usage_type) const;
    SgStatement* processSt(SgStatement *st, const std::vector<ParallelRegion*>* regs);
    
    void insertActualDirective(SgStatement*, const ArraySet&, int, bool, const std::set<std::string>* = NULL);

    void parFuncsInNode(LoopGraph *loop, bool isParallel);
    bool isLoopParallel(const LoopGraph *loop) const;
    std::vector<SgExpression*> getArrayList(Statement* start, Statement* end, bool left = false) const;
    ArraySet applyUseFilter(const ArraySet& block, const std::set<DIST::Array*>& filter) const;
    ArraySet excludePrivates(const ArraySet& block) const;
    ArraySet excludeRemotes(const ArraySet& block, SgStatement* remoteDir) const;
    void insertForProcCall(SgStatement* st, bool& skipGetActualIfProcCall, bool& skipActualIfProcCall);
public:
    explicit DvmhRegionInserter(
        SgFile* curFile,
        const std::vector<LoopGraph*>& curLoopGraph,
        ReadWriteAnalyzer& rws,
        const std::map<DIST::Array*, std::set<DIST::Array*>>& arrayLinksByFuncCalls,
        const std::map<std::string, FuncInfo*>& allFunctions,
        const std::vector<FuncInfo*>& funcsForFile,
        bool mpi_program
    ) : file(curFile), loopGraph(curLoopGraph), rw_analyzer(rws), arrayLinksByFuncCalls(arrayLinksByFuncCalls), allFunctions(allFunctions), funcsForFile(funcsForFile), isMpiProgram(mpi_program)
    {
        if (loopGraph.size())
            createMapLoopGraph(loopGraph, loopGraphMap);
    }

    void insertDirectives(const std::vector<ParallelRegion*>* regs = NULL);
    void insertActualDirectives(const std::vector<ParallelRegion*>* regs);

    void updateParallelFunctions(const std::map<std::string, std::vector<LoopGraph*>>& loopGraphs);
    void createInterfaceBlockForParallelFunctions(bool onlyRoutine = true);
    void removePrivatesFromParallelLoops();
    void addPrivatesToParallelLoops();
    void addUsedArrays(std::set<DIST::Array*>& arrays);
    void addUsedWriteArrays(std::set<DIST::Array*>& arrays);

    void updateUsedArrays(const std::set<DIST::Array*>& used, const std::set<DIST::Array*>& usedForWrite)
    {
        ArraySet newSet = usedForWrite;
        for (auto& elem : usedForWrite)
            getRealArrayRefs(elem, elem, newSet, arrayLinksByFuncCalls);
        writesToArraysInParallelLoops = newSet;

        newSet = used;
        for (auto& elem : used)
            getRealArrayRefs(elem, elem, newSet, arrayLinksByFuncCalls);
        usedArraysInParallelLoops = newSet;
    }

    const std::set<FuncInfo*> getParallelFunctions() const { 
        std::set<FuncInfo*> retVal;
        for (auto& elem : parallel_functions)
            retVal.insert(elem.first);
        return retVal; 
    }

    static void createInterfaceBlockForOutCall(FuncInfo* func, FuncInfo* callFrom);
    static void createInterfaceBlockForOutCalls(FuncInfo* func);
        
    ~DvmhRegionInserter()
    {
        for (auto& reg : regions)
            delete reg;
    }
};

int insertDvmhRegions(SgProject& project, int files, const std::vector<ParallelRegion*>& parallelRegions,
                      std::map<std::string, std::vector<FuncInfo*>>& allFuncInfo,
                      std::map<std::string, std::vector<LoopGraph*>> loopGraph,
                      ReadWriteAnalyzer& rw_analyzer,
                      std::map<std::string, std::vector<Messages>>& SPF_messages,
                      const std::map<DIST::Array*, std::set<DIST::Array*>> arrayLinksByFuncCalls);
