#pragma once

#include <string>
#include <set>

#include "AstWrapper.h"
#include "utils.h"
#include "../DirectiveProcessing/shadow.h"
#include "graph_loops.h"

static const char* paramNames[] = 
{ "NONE_T", "ARRAY_T", "STRING_ARRAY_T", "STRING_T", "SCALAR_CHAR_T", "SCALAR_BOOL_T", "SCALAR_SHORT_T", "SCALAR_INT_T", "SCALAR_LONG_INT_T",
  "SCALAR_FLOAT_T", "SCALAR_DOUBLE_T",   
  "SCALAR_CMPLX_FLOAT_T", "SCALAR_CMPLX_DOUBLE_T", "EXTERNAL_T", "UNKNOWN_T" };

typedef enum parF { NONE_T, ARRAY_T, STRING_ARRAY_T, STRING_T, SCALAR_CHAR_T, SCALAR_BOOL_T, SCALAR_SHORT_T, SCALAR_INT_T, SCALAR_LONG_INT_T,
                    SCALAR_FLOAT_T, SCALAR_DOUBLE_T,                      
                    SCALAR_CMPLX_FLOAT_T, SCALAR_CMPLX_DOUBLE_T, EXTERNAL_T, UNKNOWN_T} paramType;

#ifndef IN_BIT
    #define IN_BIT 16
#endif
#ifndef OUT_BIT
    #define OUT_BIT 256
#endif

struct FuncParam
{
    FuncParam() { countOfPars = 0; }

    void init(const int numPar)
    {
        countOfPars = numPar;
        if (numPar)
        {
            parameters.resize(numPar);
            parametersT.resize(numPar);
            inout_types.resize(numPar);
            std::fill(parametersT.begin(), parametersT.end(), NONE_T);
            std::fill(inout_types.begin(), inout_types.end(), 0);
        }
    }

    void completeParams()
    {
        for (int z = 0; z < countOfPars; ++z)
            if (inout_types[z] == 0)
                inout_types[z] = IN_BIT;
    }

    bool isArgIn(const int num) const
    {
        if (num >= countOfPars)
            return false;
        else
            return (inout_types[num] & IN_BIT) != 0;
    }

    bool isArgOut(const int num) const
    {
        if (num >= countOfPars)
            return false;
        else
            return (inout_types[num] & OUT_BIT) != 0;
    }

    bool isArgInOut(const int num) const
    {
        if (num >= countOfPars)
            return false;
        else
            return isArgIn(num) && isArgOut(num);
    }

    static bool isArgIn(int64_t type) { return (type & IN_BIT) != 0; }
    static bool isArgOut(int64_t type) { return (type & OUT_BIT) != 0; }
    static bool isArgInOut(int64_t type) { return isArgIn(type) && isArgOut(type); }

    std::vector<std::string> identificators;
    std::vector<void*> parameters;
    std::vector<paramType> parametersT;
    std::vector<int> inout_types;
    int countOfPars;       
};

#ifndef IN_BIT
    #undef IN_BIT
#endif
#ifndef OUT_BIT
    #undef OUT_BIT
#endif

struct NestedFuncCall 
{
    std::string CalledFuncName;
    std::vector<std::vector<int>> NoOfParamUsedForCall;

    NestedFuncCall(std::string funcName) : CalledFuncName(funcName) { }

    NestedFuncCall(std::string funcName, int ParsNum) :
        CalledFuncName(funcName),
        NoOfParamUsedForCall(std::vector<std::vector<int>>(ParsNum))
    { }
};

struct FuncInfoCallFrom {
    // <name, line>
    std::pair<std::string, int> detailCallsFrom;
    // <pointer, SG_VAR> SgStatement for PROC_STAT, SgExpression for FUNC_CALL, VAR_REF for external calls
    std::pair<void*, int> pointerDetailCallsFrom;
    // parent SgStatement* of FUNC_CALL
    void* parentForPointer; 
    FuncParam actualParams;
};

struct FuncInfo
{
    std::string funcName;
    std::pair<int, int> linesNum;
    std::string fileName;
    Statement *funcPointer;
    bool isMain;
    bool isInterface;

    std::set<std::string> callsFrom; //calls from this function
    std::set<FuncInfo*> callsFromV;
    std::vector<FuncInfoCallFrom> callsFromDetailed;

    std::map<std::string, std::set<std::string>> commonBlocks;

    std::vector<FuncInfo*> callsTo; //calls of this function
    FuncParam funcParams;
    std::vector<bool> isParamUsedAsIndex;
    std::vector<NestedFuncCall> funcsCalledFromThis; // size = amount of calls in this func;
                                                     // if (FuncsCalledFromThis[func_call_idx].
                                                    // NoOfParamUsedForCall.size() == 0) - no params of cur func used

    ShadowNode* shadowTreeStart;
    ShadowNode* shadowTreeEnd;
    std::map<void*, ShadowNode*> allShadowNodes;
    std::set<DIST::Array*> allUsedArrays; // real array refs
    std::set<DIST::Array*> usedArraysWrite; // real array refs

    std::vector<LoopGraph*> loopsInFunc;

    std::set<int> linesOfIO;
    std::set<int> linesOfStop;

    std::map<std::string, FuncInfo*> interfaceBlocks;
    std::map<std::string, FuncInfo*> interfaceSynonims;

    std::vector<FuncInfo*> entry; // all entry points

    std::set<std::string> externalCalls;

    bool isPure; // does this func or funcs called from this have common block[s] and have no side effects
    bool doNotInline;
    bool doNotAnalyze;
    bool needToInline;
    bool deadFunction;

    // for RESOLVE_PAR_REGIONS
    int inRegion; // 0 - none, 1 - explicit, 2 - implicit, 3 - indirect
    std::set<uint64_t> callRegions; // 0 - default; forall i > 0, i - user region
    bool isInRegion() { return inRegion == 1 || inRegion == 2; }
    bool isIndirect() { return inRegion == 3; }
    //

    std::vector<FuncInfo*> fullCopiesOfThisFunction;

    FuncInfo() :
        doNotInline(false), funcPointer(NULL), doNotAnalyze(false), needToInline(false), 
        deadFunction(false), inRegion(0), isPure(false), isMain(false), shadowTreeStart(NULL), shadowTreeEnd(NULL),
        isInterface(false) { }

    FuncInfo(const std::string &funcName, const std::pair<int, int> &lineNum) :
        funcName(funcName), linesNum(lineNum), doNotInline(false), funcPointer(NULL),
        doNotAnalyze(false), needToInline(false), deadFunction(false), inRegion(0), isMain(false), 
        isPure(false), shadowTreeStart(NULL), shadowTreeEnd(NULL), isInterface(false) { }

#if !__SPC
    FuncInfo(const std::string &funcName, const std::pair<int, int> &lineNum, Statement *pointer) :
        funcName(funcName), linesNum(lineNum), doNotInline(false), funcPointer(pointer),
        doNotAnalyze(false), needToInline(false), deadFunction(false), inRegion(0), isMain(false), 
        isPure(false), shadowTreeStart(NULL), shadowTreeEnd(NULL), isInterface(false) { fileName = pointer->fileName(); }
#endif

    std::vector<std::pair<void*, int>> GetDetailedCallInfo(const std::string &funcName)
    {
        std::vector<std::pair<void*, int>> result;
        for (int i = 0; i < callsFromDetailed.size(); ++i)
        {
            if (callsFromDetailed[i].detailCallsFrom.first == funcName)
                result.push_back(callsFromDetailed[i].pointerDetailCallsFrom);
        }
        return result;
    }

    std::string getFuncNameWithContainsByRegion(const uint64_t regionId)
    {
        if (regionId && callRegions.size() > 1 && callRegions.find(regionId) != callRegions.end())
            return funcName + "_r" + std::to_string(regionId);
        return funcName;
    }

    std::string getFuncNameByRegion(const std::string &shortName, const uint64_t regionId)
    {
        if (regionId && callRegions.size() > 1 && callRegions.find(regionId) != callRegions.end())
            return shortName + "_r" + std::to_string(regionId);
        return shortName;
    }

    void removeNonDistrArrays()
    {
        std::set<DIST::Array*> newUsedArrays;
        for (auto &elem : allUsedArrays)
            if (elem->GetDistributeFlagVal() == DIST::DISTR)
                newUsedArrays.insert(elem);
        allUsedArrays = newUsedArrays;
    }

    bool usesIO() const { return (linesOfIO.size() != 0 || linesOfStop.size() != 0); }

    std::string getCallName(const std::pair<void*, int>& call_info, const std::string& name, int line)
    {
        if (line <= 0)
            return name;

        std::set<std::string> names;

        for (auto& call : callsFromDetailed)
        {
            if (call.pointerDetailCallsFrom == call_info && call.detailCallsFrom.second == line)
                return call.detailCallsFrom.first;

            if (call.detailCallsFrom.second == line)
                if (call.detailCallsFrom.first.find(name) != std::string::npos)
                    names.insert(call.detailCallsFrom.first);
        }

        //TODO: detect func call better
        if (names.size() == 1)
            return *names.begin();

        //try to find in next lvl calls 
        for (auto& call : callsFromDetailed)
        {
            if (call.detailCallsFrom.second == line)
            {
                std::string name_next = call.detailCallsFrom.first;
                std::set<FuncInfo*> next, done;

                for (auto& callFrom : callsFromV)
                    if (callFrom->funcName == name_next)
                        next.insert(callFrom);
                
                done = next;
                while (next.size())
                {
                    auto curr = next;
                    next.clear();

                    for (auto& callFrom : curr)
                    {
                        for (auto& call : callFrom->callsFromDetailed)
                        {
                            if (call.detailCallsFrom.first == name)
                                return call.detailCallsFrom.first;
                        }

                        for (auto& callFromNext : callFrom->callsFromV)
                        {
                            if (done.find(callFromNext) == done.end())
                            {
                                next.insert(callFromNext);
                                done.insert(callFromNext);
                            }
                        }
                    }
                }
            }
        }
        return "";
    }
};

struct CallV
{
    std::string fName;
    std::string fileName;
    bool isMain;
    int inRegion;

    CallV() : inRegion(0), isMain(false) { }

    CallV(const std::string &fName) :
        fName(fName), fileName(""), isMain(false), inRegion(0)
    { }

    CallV(const std::string &fName, const std::string &fileName, bool isMain) : 
        fName(fName), fileName(fileName), isMain(isMain), inRegion(0)
    { }

    std::string to_string()
    {
        return fName + "@" + fileName + "@" + (isMain ? "1" : "0") + "@" + std::to_string(inRegion);
    }
};

void propagateArrayFlags(const std::map<DIST::Array*, std::set<DIST::Array*>> &arrayLinksByFuncCalls, const std::map<std::tuple<int, std::string, std::string>, std::pair<DIST::Array*, DIST::ArrayAccessInfo*>> &declaredArrays, std::map<std::string, std::vector<Messages>> &SPF_messages);
void removeDistrStateFromDeadFunctions(const std::map<std::string, std::vector<FuncInfo*>>& allFuncInfo, const std::map<std::tuple<int, std::string, std::string>, std::pair<DIST::Array*, DIST::ArrayAccessInfo*>>& declaredArrays);
bool detectMpiCalls(const std::map<std::string, std::vector<FuncInfo*>>& allFuncInfo, std::map<std::string, std::vector<Messages>>& SPF_messages);
int getLvlCall(FuncInfo* currF, int lvl, const std::string& func, const std::string& file, int line);
void compliteArrayUsage(DIST::Arrays<int>& allArraysForRegion, std::map<std::tuple<int, std::string, std::string>, DIST::Array*>& createdArrays, const std::map<DIST::Array*, std::set<DIST::Array*>>& arrayLinksByFuncCalls, const std::map<DIST::Array*, std::tuple<int, std::string, std::string>>& tableOfUniqNamesByArray);
void remoteNotUsedArrays(std::map<std::tuple<int, std::string, std::string>, DIST::Array*>& createdArrays, const std::set<DIST::Array*>& usedArraysAcrossRegions, const std::map<DIST::Array*, std::set<DIST::Array*>>& arrayLinksByFuncCalls);