#include "leak_detector.h"

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

#include <map>
#include <vector>
#include <set>
#include <string>
#include <stack>

#include "dvm.h"
#include "graph_calls_func.h"

#include "CFGraph/CFGraph.h"
#include "graph_loops_func.h"
#include "../DirectiveProcessing/directive_parser.h"
#include "SgUtils.h"
#include "json.hpp"
#include "../ParallelizationRegions/ParRegions_func.h"
#include "../DynamicAnalysis/gCov_parser_func.h"
#include "expr_transform.h"
#include "../LoopAnalyzer/loop_analyzer.h"
#include "../VerificationCode/verifications.h"

using std::vector;
using std::map;
using std::set;
using std::pair;
using std::tuple;
using std::string;
using std::wstring;
using std::make_pair;
using std::to_string;
using std::cout;
using std::endl;
using std::stack;

using json = nlohmann::json;

#define DEBUG 0

//TODO: improve parameter checking 
void correctNameIfContains(SgStatement *call, SgExpression *exCall, string &name, 
                           const vector<SgStatement*> &containsFunctions, const string &prefix)
{
    if (containsFunctions.size() <= 0)
        return;

    if (call == NULL && exCall == NULL)
        return;

    for (auto &func : containsFunctions)
    {
        if (func->symbol()->identifier() == name)
        {
            int numPar = 0;
            if (call && call->variant() == PROC_STAT)
                numPar = isSgCallStmt(call)->numberOfArgs();
            else if (exCall && exCall->variant() == FUNC_CALL)
                numPar = isSgFunctionCallExp(exCall)->numberOfArgs();
            else
                return;

            SgProgHedrStmt *f = (SgProgHedrStmt*)func;
            //XXX
            if (f->numberOfParameters() == numPar)
                name = prefix + name;
            break;
        }
    }
}

extern map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>> declaredArrays;
extern map<SgStatement*, set<tuple<int, string, string>>> declaratedArraysSt;
extern int getIntrinsicFunctionType(const char* name);

static parF detectType(SgType *type)
{
    parF retT = UNKNOWN_T;

    int len = getSizeOfType(type);
    int var = type->variant();

    switch (var)
    {
    case DEFAULT:
        break;
    case T_INT:
        if (len == 0 || len == 4)
            retT = SCALAR_INT_T;
        else if (len == 8)
            retT = SCALAR_LONG_INT_T;
        else if (len == 2)
            retT = SCALAR_SHORT_T;
        break;
    case T_FLOAT:
        if (len == 0 || len == 4)
            retT = SCALAR_FLOAT_T;
        else if (len == 8)
            retT = SCALAR_DOUBLE_T;
        break;
    case T_DOUBLE:
        if (len == 0 || len == 8)
            retT = SCALAR_DOUBLE_T;
        break;
    case T_CHAR:
        if (len == 0 || len == 4)
            retT = SCALAR_CHAR_T;
        break;
    case T_BOOL:
        if (len == 0 || len == 4)
            retT = SCALAR_BOOL_T;
        break;
    case T_STRING:
        retT = STRING_T;
        break;
    case T_COMPLEX:
        if (len == 0 || len == 4)
            retT = SCALAR_CMPLX_FLOAT_T;
        else if (len == 8)
            retT = SCALAR_CMPLX_DOUBLE_T;
        break;
    case T_DCOMPLEX:
        if (len == 0 || len == 8)
            retT = SCALAR_CMPLX_DOUBLE_T;
        break;
    }
    return retT;
}

static void fillParam(const int i, SgSymbol *parIn, FuncParam& currParams, const map<string, vector<SgExpression*>> &commonBlocks, bool isArrayRef,
                      bool isExternal = false)
{
    SgSymbol *par = OriginalSymbol(parIn);
    currParams.parametersT[i] = UNKNOWN_T;
    if ((par->attributes() & OPTIONAL_BIT) != 0)
        currParams.inout_types[i] |= OPTIONAL_BIT;

    if ((par->attributes() & EXTERNAL_BIT) || isExternal)
    {
        currParams.parametersT[i] = EXTERNAL_T;
        return;
    }

    if (par->variant() == FUNCTION_NAME)
    {
        if (isIntrinsicFunctionName(par->identifier()))
        {
            const int type = getIntrinsicFunctionType(par->identifier());
            if (type == T_DOUBLE)
                currParams.parametersT[i] = SCALAR_DOUBLE_T;
            else if (type == T_FLOAT)
                currParams.parametersT[i] = SCALAR_FLOAT_T;
        }
        else
        {
            SgType* type = par->type();
            currParams.parametersT[i] = detectType(type);
        }
        return;
    }

    SgType *type = par->type();
    if (type)
    {
        const int varT = type->variant();
        if (isArrayType(type))
        {
            currParams.parametersT[i] = ARRAY_T;

            SgStatement* decl = declaratedInStmt(par);
            auto uniqKey = getUniqName(commonBlocks, decl, par);
            auto itArray = declaredArrays.find(uniqKey);
            if (itArray == declaredArrays.end())
            {
                __spf_print(1, "array was not in declared list: '%s'\n", par->identifier());
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }

            currParams.parameters[i] = itArray->second.first;
        }
        else if (isStringArrayType(type))
        {
            if (isArrayRef)
                currParams.parametersT[i] = STRING_T;
            else
                currParams.parametersT[i] = STRING_ARRAY_T;
        }
        else
            currParams.parametersT[i] = detectType(type);
    }
    else
    {
        //printf("var = %d %s\n", par->variant(), tag[par->variant()]);
        currParams.parametersT[i] = UNKNOWN_T;
    }
}

static void fillFuncParams(FuncInfo *currInfo, const map<string, vector<SgExpression*>> &commonBlocks, SgProgHedrStmt *procHeader)
{
    int numOfParams = procHeader->numberOfParameters();

    FuncParam& currParams = currInfo->funcParams;
    currParams.init(numOfParams);

    if (numOfParams > 0)
    {
        for (int i = 0; i < numOfParams; ++i)
            fillParam(i, procHeader->parameter(i), currParams, commonBlocks, false);

        for (int i = 0; i < procHeader->numberOfParameters(); ++i)
        {
            currInfo->funcParams.identificators.push_back((procHeader->parameter(i))->identifier());
            currInfo->isParamUsedAsIndex.push_back(false);
        }
    }
}

static void fillFuncParams(FuncInfo *currInfo, const map<string, vector<SgExpression*>> &commonBlocks, SgStatement *entryHeader)
{
    SgExpression *parList = entryHeader->expr(0);
    int numOfParams = 0;
    for (SgExpression *p = parList; p; p = p->rhs())
        numOfParams++;

    FuncParam& currParams = currInfo->funcParams;
    currParams.init(numOfParams);

    if (numOfParams > 0)
    {
        SgExpression *p = parList;
        for (int i = 0; i < numOfParams; ++i, p = p->rhs())
        {
            fillParam(i, p->lhs()->symbol(), currParams, commonBlocks, (p->lhs()->lhs() || p->lhs()->rhs()));

            currInfo->funcParams.identificators.push_back(p->lhs()->symbol()->identifier());
            currInfo->isParamUsedAsIndex.push_back(false);
        }
    }
}

static parF detectMaxTypeOfExpression(SgExpression *ex)
{
    if (!ex)
        return UNKNOWN_T;

    parF res = UNKNOWN_T;
    if (ex->variant() == VAR_REF)
    {
        if (ex->symbol() && ex->symbol()->type())
            res = detectType(ex->symbol()->type());
    }
    else if (isArrayRef(ex))
    {
        if (ex->symbol() && ex->symbol()->type()->baseType())
            res = detectType(ex->symbol()->type()->baseType());
    }
    else if (ex->variant() == CONST_REF)
    {
        if (ex->type())
            res = detectType(ex->type());
    }
    else if (ex->variant() >= INT_VAL && ex->variant() <= STRING_VAL || ex->variant() == COMPLEX_VAL)
    {
        if (ex->type())
            res = detectType(ex->type());
    }
    else
    {
        parF lF = UNKNOWN_T, rF = UNKNOWN_T;
        if (ex->lhs())
            lF = detectMaxTypeOfExpression(ex->lhs());
        if (ex->rhs())
            rF = detectMaxTypeOfExpression(ex->rhs());
        res = MAX(res, lF);
        res = MAX(res, rF);        
    }
    return res;
}

parF detectExpressionType(SgExpression *exp) {

    const int var = exp->variant();
       if (var == INT_VAL)
           return SCALAR_INT_T;
       else if (var == FLOAT_VAL)
           return SCALAR_FLOAT_T;
       else if (var == DOUBLE_VAL)
           return SCALAR_DOUBLE_T;
       else if (var == CHAR_VAL)
           return SCALAR_CHAR_T;
       else if (var == BOOL_VAL)
           return SCALAR_BOOL_T;
       else if (exp->variant() == VAR_REF && exp->symbol() && (exp->symbol()->attributes() & EXTERNAL_BIT))
           return EXTERNAL_T;
       else
       {
           SgType* type = exp->type();
           if (type)
           {
               if (type->variant() == DEFAULT)
                   return detectMaxTypeOfExpression(exp);
               else
                   return detectType(type);
           }
           else
               return UNKNOWN_T;
       }
}

//TODO:: add values
static void processActualParams(SgExpression *parList, const map<string, vector<SgExpression*>> &commonBlocks, FuncParam& currParams,
                                const set<string>& externalCall)
{
    int numOfPar = 0;

    for (SgExpression *ex = parList; ex != NULL; ex = ex->rhs())
        numOfPar++;

    currParams.init(numOfPar);
    if (numOfPar > 0)
    {
        int num = 0;
        for (SgExpression *ex = parList; ex != NULL; ex = ex->rhs(), ++num)
        {
            if (ex->lhs() && ex->lhs()->lhs() == NULL && ex->lhs()->rhs() == NULL)
                currParams.identificators.push_back(string(ex->lhs()->unparse()));
            else
                currParams.identificators.push_back("");

            if (ex->lhs()->symbol())
                fillParam(num, ex->lhs()->symbol(), currParams, commonBlocks, (ex->lhs()->lhs() || ex->lhs()->rhs()),
                          externalCall.find(ex->lhs()->symbol()->identifier()) != externalCall.end());
            else
            {
                parF parf = detectExpressionType(ex->lhs());
                currParams.parametersT[num] = parf;
                if (parf == SCALAR_INT_T)
                {
                    SgExpression* result = CalculateInteger(ex->lhs()->copyPtr());
                    if (result != ex->lhs())
                    {
                        currParams.parameters[num] = new int[1];
                        addToCollection(__LINE__, __FILE__, currParams.parameters[num], 2);
                        ((int*)currParams.parameters[num])[0] = result->valueInteger();
                    }
                }
            }
        }
    }
}

static void findFuncCalls(SgStatement *parent, SgExpression *curr, FuncInfo* procInfo, const int line,
                          const map<string, vector<SgExpression*>> &commonBlocks, const set<string> &macroNames,
                          const vector<SgStatement*> &containsFunctions, const string &prefix)
{
    if (curr->variant() == FUNC_CALL && macroNames.find(curr->symbol()->identifier()) == macroNames.end())
    {
        vector<string> nameOfCallFunc;
        nameOfCallFunc.push_back(curr->symbol()->identifier());
        nameOfCallFunc.push_back(OriginalSymbol(curr->symbol())->identifier());

        for (auto& elem : nameOfCallFunc)
            correctNameIfContains(NULL, curr, elem, containsFunctions, prefix);

        procInfo->callsFrom.insert(nameOfCallFunc.begin(), nameOfCallFunc.end());

        FuncInfoCallFrom newCall;
        newCall.detailCallsFrom = make_pair(nameOfCallFunc[1], line); // original name of call
        newCall.pointerDetailCallsFrom = make_pair(curr, FUNC_CALL);
        newCall.parentForPointer = parent;
        newCall.actualParams = FuncParam();

        processActualParams(curr->lhs(), commonBlocks, newCall.actualParams, procInfo->externalCalls);
        procInfo->callsFromDetailed.push_back(newCall);
    }

    if (curr->lhs())
        findFuncCalls(parent, curr->lhs(), procInfo, line, commonBlocks, macroNames, containsFunctions, prefix);
    if (curr->rhs())
        findFuncCalls(parent, curr->rhs(), procInfo, line, commonBlocks, macroNames, containsFunctions, prefix);
}

static void findReplaceSymbolByExpression(SgExpression *parentEx, SgExpression *findIn, int pos, 
                                      const string &symbol, SgExpression *replace,
                                      vector<tuple<SgExpression*, int, SgExpression*>> &replaces)
{
    if (findIn)
    {
        if (findIn->symbol())
            if (findIn->symbol()->identifier() == symbol)
                replaces.push_back(std::make_tuple(parentEx, pos, replace->copyPtr()));

        findReplaceSymbolByExpression(findIn, findIn->lhs(), 0, symbol, replace, replaces);
        findReplaceSymbolByExpression(findIn, findIn->rhs(), 1, symbol, replace, replaces);
    }
}

static void doMacroExpand(SgStatement *parent, SgExpression *parentEx, SgExpression *findIn, int pos,
                          const map<string, SgStatement*> &macroStats,
                          const set<string> &macroNames, bool &needToIterate,
                          vector<Messages> &messages)
{
    if (findIn)
    {
        if(findIn->variant() == FUNC_CALL && macroNames.find(string(findIn->symbol()->identifier())) != macroNames.end())
        {
            const string funcName = string(findIn->symbol()->identifier());
            auto it = macroStats.find(funcName);
            SgStatement *macroStat = it->second;

            vector<SgExpression*> parameters;
            for (SgExpression *list = findIn->lhs(); list; list = list->rhs())
                parameters.push_back(list->lhs()->copyPtr());

            SgExpression *toInsert = macroStat->expr(0)->lhs()->copyPtr();
            if (parentEx == NULL)
                parent->setExpression(pos, *toInsert);
            else
            {
                if (pos == 0)
                    parentEx->setLhs(*toInsert);
                else if (pos == 1)
                    parentEx->setRhs(*toInsert);
            }

            SgSymbol *declSymb = macroStat->expr(0)->symbol();
            int parN = lenghtOfParamList(declSymb->thesymb);

            vector<tuple<SgExpression*, int, SgExpression*>> replaces;
            for (int z = 0; z < parN; ++z)
            {
                SgSymbol *toExpand = SymbMapping(GetThParam(declSymb->thesymb, z));
                findReplaceSymbolByExpression(toInsert, toInsert->lhs(), 0, toExpand->identifier(), parameters[z], replaces);
                findReplaceSymbolByExpression(toInsert, toInsert->rhs(), 1, toExpand->identifier(), parameters[z], replaces);
            }

            for (auto &repl : replaces)
            {
                if (std::get<1>(repl) == 0)
                    std::get<0>(repl)->setLhs(std::get<2>(repl));
                else if (std::get<1>(repl) == 1)
                    std::get<0>(repl)->setRhs(std::get<2>(repl));
            }
            needToIterate = true;

            wstring messageE, messageR;
            __spf_printToLongBuf(messageE, L"substitute statement function with name '%s'", to_wstring(funcName).c_str());
            __spf_printToLongBuf(messageR, R101, to_wstring(funcName).c_str());
            messages.push_back(Messages(NOTE, parent->lineNumber(), messageR, messageE, 2006));
        }

        doMacroExpand(parent, findIn, findIn->lhs(), 0, macroStats, macroNames, needToIterate, messages);
        doMacroExpand(parent, findIn, findIn->rhs(), 1, macroStats, macroNames, needToIterate, messages);
    }
}

void doMacroExpand(SgFile *file, vector<Messages> &messages)
{
    for (int i = 0; i < file->numberOfFunctions(); ++i)
    {
        SgStatement *st = file->functions(i);
        SgStatement *lastNode = st->lastNodeOfStmt();

        map<string, SgStatement*> macroStats;
        set<string> macroNames;
        while (st != lastNode)
        {
            if (st == NULL)
            {
                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (!isSgExecutableStatement(st))
            {
                if (st->variant() == STMTFN_STAT)
                {
                    macroStats.insert(make_pair(st->expr(0)->symbol()->identifier(), st));
                    macroNames.insert(st->expr(0)->symbol()->identifier());
                }
            }
            else
                break;
            st = st->lexNext();
        }

        if (macroStats.size() > 0)
        {
            bool needToIterate = true;
            while (needToIterate)
            {
                needToIterate = false;
                for (SgStatement *stat = st; stat != lastNode; stat = stat->lexNext())
                    for (int i = 1; i < 3; ++i)
                        if (stat->expr(i))
                            doMacroExpand(stat, NULL, stat->expr(i), i, macroStats, macroNames, needToIterate, messages);
            }
        }
    }
}

static void findIdxRef(SgExpression *exp, FuncInfo &currInfo)
{
    if (exp)
    {
        if (exp->variant() == VAR_REF)
        {
            for (int i = 0; i < currInfo.funcParams.identificators.size(); i++)
            {
                if (exp->symbol()->identifier() == currInfo.funcParams.identificators[i])
                    currInfo.isParamUsedAsIndex[i] = true;
            }
        }

        findIdxRef(exp->lhs(), currInfo);
        findIdxRef(exp->rhs(), currInfo);
    }
}

static void findArrayRef(SgExpression *exp, FuncInfo &currInfo, bool isWrite)
{
    if (exp)
    {
        if (isArrayRef(exp))
        {
            DIST::Array *arrayRef = NULL;
            SgSymbol *symbS = OriginalSymbol(exp->symbol());
            const string symb = symbS->identifier();

            if (symbS)
                arrayRef = getArrayFromDeclarated(declaratedInStmt(symbS), symb);
            else
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (arrayRef)
            {
                // only distributed arrays were added
                if (arrayRef->IsNotDistribute() == false)
                {
                    // Through all indexes
                    for (SgExpression *ex = exp; ex != NULL; ex = ex->rhs())
                        findIdxRef(exp->lhs(), currInfo);
                    currInfo.allUsedArrays.insert(arrayRef);
                    if (isWrite)
                        currInfo.usedArraysWrite.insert(arrayRef);
                }
            }
        }
        else
        {
            findArrayRef(exp->lhs(), currInfo, isWrite);
            findArrayRef(exp->rhs(), currInfo, isWrite);
        }
    }
}

static void findParamInParam(SgExpression *exp, FuncInfo &currInfo) 
{
    // Searching through expression, which parameter presented with
    if (exp)
    {
        if (exp->variant() == VAR_REF || isArrayRef(exp))
        {
            // check for matching with one of param of func which called this
            //cout << "Checking " << exp->symbol()->identifier() << " for match.." << endl;
            for (int i = 0; i < currInfo.funcParams.identificators.size(); i++)
            {
                const string &parName = currInfo.funcParams.identificators[i];
                //cout << "	with " << parName << ".. ";
                if (exp->symbol()->identifier() == parName)
                {
                    //cout << "Success." << endl;
                    NestedFuncCall &currNestedFuncCall = currInfo.funcsCalledFromThis.back();
                    currNestedFuncCall.NoOfParamUsedForCall.back().push_back(i);
                    break;
                }
            }
        }
        // do not search further if found, cause VAR_REF is always a leaf
        else
        {
            findParamInParam(exp->lhs(), currInfo);
            findParamInParam(exp->rhs(), currInfo);
        }
    }
}

static bool hasRecCall(const FuncInfo* proc, const vector<string>& newNames)
{
    bool has = false;
    for (auto& toAdd : newNames)
        if (proc->funcName == toAdd)
            has = true;
    return has;
}

static void findParamUsedInFuncCalls(SgExpression *exp, FuncInfo &currInfo, const vector<SgStatement*>& containsFunctions, const string& prefix);
static void throughParams(SgExpression *pars, FuncInfo &currInfo, const vector<SgStatement*>& containsFunctions, const string& prefix)
{
    for (SgExpression *par = pars; par != NULL; par = par->rhs())
    {
        // initialize vector representing parameter #parNo
        NestedFuncCall &currNestedFuncCall = currInfo.funcsCalledFromThis.back();        
        currNestedFuncCall.NoOfParamUsedForCall.push_back(vector<int>());

        findParamInParam(par->lhs(), currInfo);
    }

    // search another func call, possibly used in parameter
    for (SgExpression *par = pars; par != NULL; par = par->rhs())
        findParamUsedInFuncCalls(pars->lhs(), currInfo, containsFunctions, prefix);
}

// Takes random expression, finds there func calls and check their parameters 
// for using parameters of func where first is called from
static void findParamUsedInFuncCalls(SgExpression *exp, FuncInfo &currInfo,
                                     const vector<SgStatement*>& containsFunctions, const string& prefix)
{
    if (exp)
    {
        if (exp->variant() == FUNC_CALL)
        {
            vector<string> nameOfCallFunc;
            nameOfCallFunc.push_back(exp->symbol()->identifier());
            nameOfCallFunc.push_back(OriginalSymbol(exp->symbol())->identifier());

            for (auto& elem : nameOfCallFunc)
                correctNameIfContains(NULL, exp, elem, containsFunctions, prefix);

            if (!hasRecCall(&currInfo, nameOfCallFunc))
            {
                // Add func call which we've just found            
                currInfo.funcsCalledFromThis.push_back(NestedFuncCall(nameOfCallFunc[1]));

                // For every found func call iterate through pars
                //cout << "Through params of the call of " << exp->symbol()->identifier() << endl;
                throughParams(exp->lhs(), currInfo, containsFunctions, prefix);
            }
        }
        else
        {
            // If we've not found func call, search further in all branches
            findParamUsedInFuncCalls(exp->rhs(), currInfo, containsFunctions, prefix);
            findParamUsedInFuncCalls(exp->lhs(), currInfo, containsFunctions, prefix);
        }
    }
}

static void printParInfo(const map<string, vector<FuncInfo*>> &allFuncInfo)
{
    cout << "*********Which parameters of current function are used in func calls inside it*********" << endl;
    for (auto &file1 : allFuncInfo)
    {
        for (auto &currInfo : file1.second)
        {
            cout << currInfo->funcName << " calls to:" << endl;
            for (auto &calledFunc : currInfo->funcsCalledFromThis)
            {
                cout << "	" << calledFunc.CalledFuncName << " with params:" << endl;
                int parNo = 0;
                for (auto &paramOfCalled : calledFunc.NoOfParamUsedForCall) 
                {
                    cout << "		" << parNo << ": ";
                    for (auto &paramOfCalling : paramOfCalled)
                        cout << currInfo->funcParams.identificators[paramOfCalling] << " ";

                    parNo++;
                    cout << endl;
                }
            }
        }
    }

    cout << endl;
    cout << "*********Which parameters of current function are used as indices for arrays*********" << endl;
    for (auto &file1 : allFuncInfo)
    {
        for (auto &currInfo : file1.second)
        {
            cout << currInfo->funcName << endl;

            for (size_t i = 0; i < currInfo->isParamUsedAsIndex.size(); i++)
            {
                cout << currInfo->funcParams.identificators[i] << ": ";
                if (currInfo->isParamUsedAsIndex[i])
                    cout << "used" << endl;
                else
                    cout << "not used" << endl;
            }
        }
    }
    cout << endl;
}

void findContainsFunctions(SgStatement *st, vector<SgStatement*> &found, const bool searchAll)
{
    SgStatement *end = st->lastNodeOfStmt();

    bool containsStarted = false;
    for (; st != end; st = st->lexNext())
    {
        if (containsStarted)
        {
            if (st->variant() == PROC_HEDR || st->variant() == FUNC_HEDR)
            {
                found.push_back(st);
                st = searchAll? st : st->lastNodeOfStmt();
            }
            else if (!searchAll && st->variant() == CONTAINS_STMT)
                break;
        }

        if (st->variant() == CONTAINS_STMT)
            containsStarted = true;
    }
}

static void fillIn(FuncInfo *currF, SgExpression *ex, const map<string, int> &parNames, bool isInFuncPar)
{
    if (ex)
    {
        if (!isInFuncPar && (ex->variant() == VAR_REF || ex->variant() == ARRAY_REF))
        {
            const char *name = ex->symbol()->identifier();
            if (name && name != string(""))
            {
                auto it = parNames.find(name);
                if (it != parNames.end())
                    currF->funcParams.inout_types[it->second] |= IN_BIT;
            }
        }

        if (ex->variant() == FUNC_CALL) {
            SgFunctionCallExp* call = (SgFunctionCallExp*)ex;
            for (int z = 0; z < call->numberOfArgs(); ++z)
                fillIn(currF, call->arg(z), parNames, true);
        }
        else {
            fillIn(currF, ex->lhs(), parNames, false);
            fillIn(currF, ex->rhs(), parNames, false);
        }
    }
}

static void fillType(FuncInfo* currF, const string& symb, const map<string, int>& parNames, const int type)
{
    if (symb != "")
    {
        auto it = parNames.find(symb);
        if (it != parNames.end())
            currF->funcParams.inout_types[it->second] |= type;
    }
}

static void checkSpecList(SgExpression* pair, FuncInfo* currF, const map<string, int>& parNames, int)
{
    auto valExp = isSgKeywordValExp(pair->lhs());
    if (valExp->value() == string("unit"))
        if (pair->rhs() && pair->rhs()->symbol())
            fillType(currF, pair->rhs()->symbol()->identifier(), parNames, INOUT_BIT);
}

static void checkSpecList(SgExpression *spec, FuncInfo* currF, const map<string, int>& parNames)
{
    if (spec->variant() == SPEC_PAIR)
        checkSpecList(spec, currF, parNames, 0);
    else
    {
        while (spec)
        {
            if (spec->lhs()->variant() == SPEC_PAIR)
                checkSpecList(spec->lhs(), currF, parNames, 0);
            spec = spec->rhs();
        }
    }
}

static void checkInTypeDescription(SgExpression *ex, FuncInfo* currF, const map<string, int>& parNames)
{
    if (ex)
    {
        if (ex->variant() == ARRAY_REF)
            fillIn(currF, ex->lhs(), parNames, false);
        else
        {
            checkInTypeDescription(ex->lhs(), currF, parNames);
            checkInTypeDescription(ex->rhs(), currF, parNames);
        }
    }
}

static void fillInOut(FuncInfo *currF, SgStatement *start, SgStatement *last, const set<SgStatement*>& activeOps)
{
    if (currF->funcParams.countOfPars == 0)
        return;

    static map<string, vector<int>> supportedKeyWordArg = { {"system_clock", { OUT_BIT, OUT_BIT, OUT_BIT } } };

    map<string, int> parNamesMain;
    map<string, int> parNamesContains;

    for (int i = 0; i < currF->funcParams.identificators.size(); ++i)
        parNamesMain[currF->funcParams.identificators[i]] = i;

    map<string, int>& parNames = parNamesMain;

    bool isContainsFunctions = false;
    for (auto st = start; st != last; st = st->lexNext())
    {
        if (st->variant() == CONTAINS_STMT) {
            isContainsFunctions = true;
            continue;
        }

        if (isContainsFunctions) {
            if (st->variant() == PROC_HEDR || st->variant() == FUNC_HEDR) {
                parNamesContains = parNamesMain;
                
                SgProgHedrStmt* hedr = (SgProgHedrStmt*)st;

                int numOfParams = hedr->numberOfParameters();
                if (numOfParams > 0)
                {
                    for (int i = 0; i < numOfParams; ++i)
                    {
                        auto it = parNamesContains.find(hedr->parameter(i)->identifier());
                        if (it != parNamesContains.end())
                            parNamesContains.erase(it);
                    }
                }
                parNames = parNamesContains;
            }
        }

        if (st->variant() == ENTRY_STAT)
            continue;
        
        if (isSgExecutableStatement(st) == NULL) {
            if (!isContainsFunctions)
                checkInTypeDescription(st->expr(0), currF, parNames);
            continue;
        }

        if (st->lineNumber() <= 0)
            continue;

        //XXX: use parameters removing in block ... isContainsFunctions ...
        //TODO need to use IR for parameters checking
        /*if (activeOps.size() && activeOps.find(st) == activeOps.end())
            continue; */

        if (st->variant() == ASSIGN_STAT)
        {
            SgExpression *left = st->expr(0);

            fillIn(currF, left->lhs(), parNames, false);
            fillIn(currF, left->rhs(), parNames, false);
            fillIn(currF, st->expr(1), parNames, false);

            string symb = "";
            if (left->symbol())
                symb = left->symbol()->identifier();
            else if (left->variant() == ARRAY_OP)
            {
                if (left->lhs()->symbol())
                    symb = left->lhs()->symbol()->identifier();
            }
            fillType(currF, symb, parNames, OUT_BIT);
        } // TODO: need to extend 
        else if (st->variant() == READ_STAT)
        {
            SgInputOutputStmt *read = isSgInputOutputStmt(st);
            if (read == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            for (auto ex = read->itemList(); ex; ex = ex->rhs())
            {
                SgExpression* item = ex->lhs();
                if (item && (item->variant() == VAR_REF || item->variant() == ARRAY_REF))
                {
                    string symb = "";
                    if (item->symbol())
                        symb = item->symbol()->identifier();
                    fillType(currF, symb, parNames, OUT_BIT);
                }
                else if (item->variant() == IOACCESS)
                {
                    SgExpression* list = item->lhs();
                    stack<SgExpression*> queue;
                    queue.push(list);
                    while (!queue.empty())
                    {
                        auto item = queue.top();
                        queue.pop();
                        if (item->lhs())
                            queue.push(item->lhs());
                        if (item->rhs())
                            queue.push(item->rhs());

                        if (item->variant() == VAR_REF || item->variant() == ARRAY_REF)
                        {
                            string symb = "";
                            if (item->symbol())
                                symb = item->symbol()->identifier();
                            fillType(currF, symb, parNames, OUT_BIT);
                        }
                    }
                }
            }
            checkSpecList(read->specList(), currF, parNames);
        } // TODO: need to extend  
        else if (st->variant() == WRITE_STAT)
        {
            SgInputOutputStmt* write = isSgInputOutputStmt(st);
            if (write == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            checkSpecList(write->specList(), currF, parNames);
        }
        else if (!isDVM_stat(st))
        {
            bool processed = false;
            //TODO: for FUNC_CALL
            if (st->variant() == PROC_STAT && isIntrinsicFunctionName(st->symbol()->identifier()))
            {
                const string name = st->symbol()->identifier();
                if (supportedKeyWordArg.find(name) != supportedKeyWordArg.end())
                {
                    int z = 0;
                    const vector<int>& types = supportedKeyWordArg[name];
                    for (auto ex = st->expr(0); ex; ex = ex->rhs(), ++z)
                    {
                        SgExpression* arg = ex->lhs();
                        if (arg->variant() == KEYWORD_ARG)
                        {
                            arg = arg->rhs();
                            if (types.size() <= z)
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                            if (types[z] == OUT_BIT || types[z] == INOUT_BIT)
                                fillType(currF, arg->symbol()->identifier(), parNames, OUT_BIT);
                            if (types[z] == IN_BIT || types[z] == INOUT_BIT)
                                fillIn(currF, arg, parNames, false);
                        }
                        else
                            fillIn(currF, arg, parNames, false);
                    }
                    processed = true;
                }
            }

            if (!processed) 
            {
                if (st->variant() == PROC_STAT) 
                {
                    SgCallStmt* call = (SgCallStmt*)st;
                    for (int z = 0; z < call->numberOfArgs(); ++z)
                        fillIn(currF, call->arg(z), parNames, true);
                }
                else
                {
                    for (int i = 0; i < 3; ++i)
                        fillIn(currF, st->expr(i), parNames, false);
                }
            }
        }
    }
}

//TODO: check common block and module use
static void fillFunctionPureStatus(SgStatement *header, FuncInfo *currInfo, vector<Messages> &messagesForFile, const set<SgStatement*>& activeOps)
{
    if (!currInfo->isMain)
    {
        set<int> lines;
        bool hasIntent = hasThisIds(header, lines, { INTENT_STMT }, &activeOps);
        bool declaratedAsPure = (header->symbol()->attributes() & PURE_BIT);

        if (declaratedAsPure && !hasIntent && ((SgProgHedrStmt*)header)->numberOfParameters())
        {
            messagesForFile.push_back(Messages(ERROR, header->lineNumber(), R143, L"Wrong pure declaration - INTENT mismatch", 4002));
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }

        if (currInfo->commonBlocks.size() == 0)
        {
            lines.clear();
            bool has = hasThisIds(header, lines, { DATA_DECL, SAVE_DECL, USE_STMT,
                                                   WRITE_STAT, READ_STAT, OPEN_STAT, CLOSE_STAT, 
                                                   PRINT_STAT, STOP_STAT, PAUSE_NODE }, 
                                  &activeOps);

            if (!has || declaratedAsPure)
                currInfo->isPure = true;
            else
            {
                for (auto &line : lines)
                    messagesForFile.push_back(Messages(WARR, line, R93, L"Function is impure due to this operator usage", 1049));
            }
        }
        else if (declaratedAsPure)
            currInfo->isPure = true;
    }
    else
        currInfo->isPure = true;
}

static void fillCommons(FuncInfo *currInfo, const map<string, vector<SgExpression*>> &commonBlocks)
{
    for (auto &item : commonBlocks)
    {
        auto inserted = currInfo->commonBlocks.insert(make_pair(item.first, set<string>()));
        for (auto& list : item.second)
        {
            SgExpression* expr_list = list->lhs();
            while (expr_list != NULL)
            {
                SgExpression* var = expr_list->lhs();
                expr_list = expr_list->rhs();
                inserted.first->second.insert(var->symbol()->identifier());
            }
        }
    }
}

static void printActiveLines(const set<SgStatement*>& activeOps)
{
    set<int> lines;
    for (auto& st : activeOps)
        lines.insert(st->lineNumber());
    for (auto& line : lines)
        printf("%d\n", line);
}

static FuncInfo* createNewFuction(const string& funcName, SgStatement *st, SgStatement* entry, 
                                  vector<Messages>& messagesForFile,
                                  const map<string, vector<SgExpression*>>& commonBlocks,
                                  const set<SgStatement*>& activeOps)
{
    SgStatement* lastNode = st->lastNodeOfStmt();

    FuncInfo* currInfo = new FuncInfo(funcName, make_pair(entry->lineNumber(), lastNode->lineNumber()), new Statement(entry));
    hasThisIds(st, currInfo->linesOfIO, { WRITE_STAT, READ_STAT, OPEN_STAT, CLOSE_STAT, PRINT_STAT }, &activeOps);
    hasThisIds(st, currInfo->linesOfStop, { STOP_STAT, PAUSE_NODE }, &activeOps);
    currInfo->isMain = (st->variant() == PROG_HEDR);
    fillCommons(currInfo, commonBlocks);
    fillFunctionPureStatus(st, currInfo, messagesForFile, activeOps);

    if (st->variant() != PROG_HEDR)
    {
        SgProgHedrStmt* procFuncHedr = ((SgProgHedrStmt*)st);

        if (st == entry)
            fillFuncParams(currInfo, commonBlocks, procFuncHedr);
        else
            fillFuncParams(currInfo, commonBlocks, entry);
        fillInOut(currInfo, st, lastNode, activeOps);
    }

    if (isSPF_NoInline(new Statement(st->lexNext())))
    {
        __spf_print(1, "set NOINLINE attribute for function '%s'\n", funcName.c_str());
        currInfo->doNotInline = true;
    }
    
    return currInfo;
}

static FuncInfo* analyzeFunction(const string& funcName, const string& containsPrefix, 
                                 SgStatement *function, SgStatement* entry, map<string, vector<FuncInfo*>>& allFuncInfo,
                                 const map<int, LoopGraph*>& mapLoopGraph, vector<Messages>& messagesForFile,
                                 vector<SgStatement*>& containsFunctions,
                                 const set<SgStatement*>& activeOps)
{
    SgStatement* st = function;
    SgStatement* lastNode = st->lastNodeOfStmt();

    const string fileName = function->fileName();
    auto it = allFuncInfo.find(fileName);
    if (it == allFuncInfo.end())
        it = allFuncInfo.insert(it, make_pair(fileName, vector<FuncInfo*>()));

    map<string, vector<SgExpression*>> commonBlocks;
    getCommonBlocksRef(commonBlocks, function, lastNode);

    if (function->controlParent()->variant() == GLOBAL)
        containsFunctions.clear();

    findContainsFunctions(function, containsFunctions);
    auto procInfo = createNewFuction(funcName, function, entry, messagesForFile, commonBlocks, activeOps);
    it->second.push_back(procInfo);

    vector<SgStatement*> macroStats;
    set<string> macroNames;

    while (st != lastNode)
    {
        if (st->variant() == CONTAINS_STMT)
            break;

        if (st->variant() == INTERFACE_STMT)
        {
            st = st->lexNext();
            while (st && !(st->controlParent()->variant() == INTERFACE_STMT && st->variant() == CONTROL_END))
            {
                if (st->variant() == PROC_HEDR || st->variant() == FUNC_HEDR)
                {
                    procInfo->interfaceBlocks[st->symbol()->identifier()] = NULL;
                    st = st->lastNodeOfStmt();
                }
                st = st->lexNext();
            }
        }

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

        if (!isSgExecutableStatement(st))
        {
            if (st->variant() == STMTFN_STAT)
            {
                macroStats.push_back(st);
                macroNames.insert(st->expr(0)->symbol()->identifier());
            }
        }
        else
            break;
        st = st->lexNext();
    }

    st = function;
    const string file = st->fileName();

    while (st != lastNode)
    {
        if (st == NULL)
        {
            __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->variant() == ENTRY_STAT)
        {
            st = st->lexNext();
            continue;
        }

        if (activeOps.size())
        {
            if (st->fileName() ==  file &&
                isSgExecutableStatement(st) && 
                activeOps.find(st) == activeOps.end())
            {
                st = st->lexNext();
                continue;
            }
        }

        // check for external calls
        if (st->variant() == EXTERN_STAT)
            for (SgExpression* ex = st->expr(0); ex; ex = ex->rhs())
                if (ex->lhs()->symbol())
                    procInfo->externalCalls.insert(ex->lhs()->symbol()->identifier());

        const string prefix = containsPrefix == "" ? string(function->symbol()->identifier()) + "." : containsPrefix;
        //printf("var %d, line %d, file %s\n", st->variant(), st->lineNumber(), st->fileName());
        if (st->variant() == PROC_STAT)
        {
            vector<string> pureNameOfCallFunc;
            pureNameOfCallFunc.push_back(removeString("call ", st->symbol()->identifier()));
            pureNameOfCallFunc.push_back(removeString("call ", OriginalSymbol(st->symbol())->identifier()));

            for (auto& elem : pureNameOfCallFunc)
                correctNameIfContains(st, NULL, elem, containsFunctions, prefix);

            if (hasRecCall(procInfo, pureNameOfCallFunc))
                continue;

            procInfo->callsFrom.insert(pureNameOfCallFunc.begin(), pureNameOfCallFunc.end());

            FuncInfoCallFrom newCall;
            newCall.detailCallsFrom = make_pair(pureNameOfCallFunc[1], st->lineNumber()); // original name of call
            newCall.pointerDetailCallsFrom = make_pair(st, PROC_STAT);
            newCall.parentForPointer = st;
            newCall.actualParams = FuncParam();

            processActualParams(st->expr(0), commonBlocks, newCall.actualParams, procInfo->externalCalls);
            procInfo->callsFromDetailed.push_back(newCall);

            // Add func call which we've just found
            NestedFuncCall funcCall(pureNameOfCallFunc[1]);
            procInfo->funcsCalledFromThis.push_back(funcCall);

            // search for using pars of cur func in pars of called
            throughParams(st->expr(0), *procInfo, containsFunctions, prefix);

            //find external calls
            for (SgExpression* par = st->expr(0); par != NULL; par = par->rhs())
            {
                SgExpression* curr = par->lhs();
                if (curr)
                {
                    if (curr->variant() == VAR_REF)
                    {
                        auto s = curr->symbol();
                        if (procInfo->externalCalls.find(s->identifier()) != procInfo->externalCalls.end() ||
                            (s->attributes() & EXTERNAL_BIT))
                        {
                            vector<string> nameOfCallFunc;
                            nameOfCallFunc.push_back(s->identifier());
                            nameOfCallFunc.push_back(OriginalSymbol(s)->identifier());

                            for (auto& elem : nameOfCallFunc)
                                correctNameIfContains(NULL, curr, elem, containsFunctions, prefix);

                            procInfo->callsFrom.insert(nameOfCallFunc.begin(), nameOfCallFunc.end());

                            FuncInfoCallFrom newCall;
                            newCall.detailCallsFrom = make_pair(nameOfCallFunc[1], st->lineNumber()); // original name of call
                            newCall.pointerDetailCallsFrom = make_pair(curr, VAR_REF);
                            newCall.parentForPointer = st;
                            newCall.actualParams = FuncParam();

                            procInfo->callsFromDetailed.push_back(newCall);
                        }
                    }
                }
            }
        }
        else
        {
            for (int i = 0; i < 3; ++i)
                if (st->expr(i))
                    findParamUsedInFuncCalls(st->expr(i), *procInfo, containsFunctions, prefix);
        }

        for (int i = 0; i < 3; ++i)
            if (st->expr(i))
                findFuncCalls(st, st->expr(i), procInfo, st->lineNumber(), commonBlocks, macroNames, containsFunctions, prefix);

        if (isSgExecutableStatement(st))
        {
            if (procInfo->isParamUsedAsIndex.size())
                for (int i = 0; i < 3; i++)
                    findArrayRef(st->expr(i), *procInfo, st->variant() == ASSIGN_STAT && i == 0);

            if (st->variant() == FOR_NODE)
            {
                auto itL = mapLoopGraph.find(st->lineNumber());
                if (itL != mapLoopGraph.end())
                    procInfo->loopsInFunc.push_back(itL->second);
            }
        }

        st = st->lexNext();
    }

    return procInfo;
}

static set<SgStatement*> fillActiveOperators(SgStatement* func, const vector<SAPFOR::BasicBlock*>& blocks)
{
    if (blocks.size() == 0)
        return set<SgStatement*>();

    set<SgStatement*> active;
    set<SAPFOR::BasicBlock*> activeBlocks;

    activeBlocks.insert(blocks[0]);
    bool added = true;
    while (added)
    {
        added = false;
        for (auto& block : activeBlocks)
        {
            for (auto& next : block->getNext())
            {
                if (activeBlocks.find(next) == activeBlocks.end())
                {
                    activeBlocks.insert(next);
                    added = true;
                }
            }
        }
    }

    for (auto& block : activeBlocks)
    {
        for (auto& instr : block->getInstructions())
        {
            auto op = instr->getInstruction()->getOperator();
            if (op)
                active.insert(op);
        }
    }

    //complete blocked statements
    for (auto st = func->lexNext(); st != func->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() == CONTAINS_STMT)
            break;
                
        if (st->variant() == SWITCH_NODE)
        {
            auto select = isSgSwitchStmt(st);
            int numOfCases = select->numberOfCaseOptions();
            for (int z = 0; z < numOfCases; ++z)
            {
                auto caseOp = isSgCaseOptionStmt(select->caseOption(z));
                if (active.count(caseOp))
                {
                    active.insert(st);
                    break;
                }
            }
        }
    }

    return active;
}

//if fullIR not empty -> call this function from CALL_GRAPH2
void functionAnalyzer(SgFile *file, map<string, vector<FuncInfo*>> &allFuncInfo, const vector<LoopGraph*> &loops, vector<Messages> &messagesForFile,
                      map<FuncInfo*, vector<SAPFOR::BasicBlock*>> &fullIR)
{
    map<int, LoopGraph*> mapLoopGraph;
    createMapLoopGraph(loops, mapLoopGraph);

    map<SgStatement*, FuncInfo*> tmpInfoInIR;
    for (auto& elem : fullIR)
    {
        SgStatement* func = elem.first->funcPointer->GetOriginal();
        if (tmpInfoInIR.count(func) != 0)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        tmpInfoInIR[func] = elem.first;
    }

    int funcNum = file->numberOfFunctions();
    __spf_print(DEBUG, "functions num in file = %d\n", funcNum);
    vector<SgStatement*> containsFunctions;

    vector<SgStatement*> functions;
    for (int i = 0; i < funcNum; ++i)
    {
        auto func = file->functions(i);
        functions.push_back(func);

        //find entry points
        for (auto st = func->lexNext(); st != func->lastNodeOfStmt(); st = st->lexNext())
        {
            if (st->variant() == ENTRY_STAT)
                functions.push_back(st);
        }
    }

    FuncInfo* lastNonEntry = NULL;
    for (auto& function : functions)
    {
        bool isEntry = (function->variant() == ENTRY_STAT);

        const int line = function->lineNumber();
        const char* file = function->fileName();

        string containsPrefix = "";
        SgStatement* st_cp = isEntry ? function->controlParent()->controlParent() : function->controlParent();
        if (st_cp->variant() == PROC_HEDR || st_cp->variant() == PROG_HEDR || st_cp->variant() == FUNC_HEDR)
            containsPrefix = st_cp->symbol()->identifier() + string(".");
        else if (st_cp->variant() == INTERFACE_STMT)
            continue;

        string funcName = "";
        if (function->variant() == PROG_HEDR)
        {
            SgProgHedrStmt* progH = (SgProgHedrStmt*)function;
            funcName = progH->nameWithContains();
            __spf_print(DEBUG, "*** Program <%s> started at line %d / %s\n", progH->symbol()->identifier(), line, file);
        }
        else if (function->variant() == PROC_HEDR)
        {
            SgProcHedrStmt* procH = (SgProcHedrStmt*)function;
            funcName = procH->nameWithContains();
            __spf_print(DEBUG, "*** Function <%s> started at line %d / %s\n", procH->symbol()->identifier(), line, file);
        }
        else if (function->variant() == FUNC_HEDR)
        {
            SgFuncHedrStmt* funcH = (SgFuncHedrStmt*)function;
            funcName = funcH->nameWithContains();
            __spf_print(DEBUG, "*** Function <%s> started at line %d / %s\n", funcH->symbol()->identifier(), line, file);
        }
        else if (function->variant() == ENTRY_STAT)
        {
            funcName = function->symbol()->identifier();
            __spf_print(DEBUG, "*** Entry function <%s> started at line %d / %s\n", funcName.c_str(), line, file);
        }
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        set<SgStatement*> activeOps;
        if (fullIR.size())
        {
            if (tmpInfoInIR.count(function) == 0)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            activeOps = fillActiveOperators((isEntry ? function->controlParent() : function), fullIR[tmpInfoInIR[function]]);
            activeOps.insert(function);
            if (isEntry)
                activeOps.insert(function->controlParent());
        }

        auto procInfo = analyzeFunction(funcName, containsPrefix, isEntry ? function->controlParent() : function, function, allFuncInfo, mapLoopGraph, messagesForFile, containsFunctions, activeOps);

        if (isEntry)
        {
            if (!lastNonEntry)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            lastNonEntry->entry.push_back(procInfo);
        }
        else
            lastNonEntry = procInfo;
    }

    //fill INTERFACE block from modules
    vector<SgStatement*> modules;
    findModulesInFile(file, modules);

    for (auto& mod : modules)
    {
        if (mod->fileName() != string(file->filename()))
            continue;

        const string moduleName = mod->symbol()->identifier();
        for (auto st = mod->lexNext(); st != mod->lastNodeOfStmt(); st = st->lexNext())
        {
            if (st->variant() == CONTAINS_STMT)
                break;
            if (isSgExecutableStatement(st))
                break;

            if (st->variant() == INTERFACE_STMT)
            {
                if (needToReplaceInterfaceName(st))
                {
                    const string fileName = st->fileName();
                    auto it = allFuncInfo.find(fileName);
                    if (it == allFuncInfo.end())
                        it = allFuncInfo.insert(it, make_pair(fileName, vector<FuncInfo*>()));

                    string currF = st->symbol()->identifier();
                    if (currF.find("::") == string::npos)
                        currF = moduleName + "::" + currF;
                    FuncInfo* funcInterface = new FuncInfo(currF, make_pair(st->lineNumber(), st->lastNodeOfStmt()->lineNumber()), new Statement(st));
                    funcInterface->isInterface = true;

                    
                    for (auto st_l = st->lexNext(); st_l != st->lastNodeOfStmt(); st_l = st_l->lexNext())
                        if (st_l->variant() == MODULE_PROC_STMT)
                        {
                            auto list = st_l->expr(0);
                            for (auto ex = list; ex; ex = ex->rhs())
                                funcInterface->interfaceSynonims[moduleName + "::" + ex->lhs()->symbol()->identifier()] = NULL;
                        }

                    st = st->lastNodeOfStmt();

                    it->second.push_back(funcInterface);
                }
            }
        }
    }

    auto it = allFuncInfo.find(file->filename());
    if (it == allFuncInfo.end())
        return;

    if (fullIR.size() == 0)
        return;

    vector<FuncInfo*> toRemove;
    for (auto& func : it->second)
    {
        SgStatement* pointer = func->funcPointer->GetOriginal();
        if (tmpInfoInIR.find(pointer) != tmpInfoInIR.end())
        {
            auto key = tmpInfoInIR[pointer];
            toRemove.push_back(key);

            fullIR[func] = fullIR[key];
            fullIR.erase(key);
        }
    }

    for (auto& func : toRemove)
        delete func;
}

static bool findLoopVarInParameter(SgExpression *ex, const string &loopSymb)
{
    bool retVal = false;

    if (ex)
    {
        if (ex->variant() == VAR_REF)
        {
            const string ident(ex->symbol()->identifier());
            if (ident == loopSymb)
                retVal = true;
        }
    }
    return retVal;
}

static int countList(SgExpression *list)
{
    int num = 0;
    while (list)
    {
        num++;
        list = list->rhs();
    }
    return num;
}

static bool matchCallAndDefinition(const FuncParam &funcParDef, const FuncParam& funcParCall, const string &funcName,
                                   const string &file, const int line, map<string, vector<Messages>> &messages)
{
    bool result = true;
    auto typesDef = funcParDef.parametersT;
    auto typesCall = funcParCall.parametersT;

    if (typesDef.size() != typesCall.size())
    {
        wstring bufR, bufE;
        __spf_printToLongBuf(bufE, L"Function '%s': count of call parameters are not enouth", to_wstring(funcName).c_str());
        __spf_printToLongBuf(bufR, R38, to_wstring(funcName).c_str());
        //if (needToAddErrors)
        {
            messages[file].push_back(Messages(ERROR, line, bufR, bufE, 1013));
            __spf_print(1, "Function '%s': count of call parameters are not enouth\n", funcName.c_str());
        }
        result = false;
    }
    else
    {
        for (int i = 0; i < typesDef.size(); ++i)
        {
            if (typesCall[i] != ARRAY_T)
            {
                if (typesCall[i] != typesDef[i])
                {
                    wstring bufR, bufE;
                    __spf_printToLongBuf(bufE, L"Different type of call (%s : %s) and def (%s : %s) parameter %d for function '%s'",                         
                                         to_wstring(funcParCall.identificators[i]).c_str(), to_wstring(paramNames[typesCall[i]]).c_str(), 
                                         to_wstring(funcParDef.identificators[i]).c_str(), to_wstring(paramNames[typesDef[i]]).c_str(), i + 1, to_wstring(funcName).c_str());

                    __spf_printToLongBuf(bufR, R39, 
                                         to_wstring(funcParCall.identificators[i]).c_str(), to_wstring(paramNames[typesCall[i]]).c_str(), 
                                         to_wstring(funcParDef.identificators[i]).c_str(), to_wstring(paramNames[typesDef[i]]).c_str(), 
                                         i + 1, to_wstring(funcName).c_str());

                    //if (needToAddErrors)
                    {
                        messages[file].push_back(Messages(NOTE, line, bufR, bufE, 1013));
                        __spf_print(1, "Function '%s': different type of call and def parameter %d\n", funcName.c_str(), i + 1);
                    }
                    //result = false;
                }
            }
            else //TODO
            {
                /*if (callArrayRef->numberOfSubscripts() > 0)
                {
                    wstring bufR, bufE;
                    __spf_printToLongBuf(bufE, L"Function '%s' needs to be inlined, only full array passing was supported", to_wstring(def->funcName).c_str());
                    __spf_printToLongBuf(bufR, R40, to_wstring(def->funcName).c_str());

                    //if (needToAddErrors)
                    {
                        messages[file].push_back(Messages(ERROR, line, bufR, bufE, 1013));
                        __spf_print(1, "Function '%s' needs to be inlined, only full array passing was supported\n", def->funcName.c_str());
                    }
                    result = false;
                }*/
            }
        }
    }

    return result;
}

static bool matchCallAndDefinition(SgProject* proj, const map<string, int>& files, 
                                   map<string, vector<Messages>>& messages, bool needToAddErrors, const map<string, FuncInfo*> &funcByName)
{
    int count = 0;
    for (auto& func : funcByName)
    {
        FuncInfo* currF = func.second;

        for (auto& callsTo : currF->callsTo)
        {
            for (int cf = 0; cf < callsTo->callsFromDetailed.size(); ++cf)
            {
                if (callsTo->callsFromDetailed[cf].detailCallsFrom.first == currF->funcName)
                {
                    auto callInfo = callsTo->callsFromDetailed[cf].pointerDetailCallsFrom;
                    if (callInfo.second == VAR_REF) // external call through proc parameter
                        continue;

                    auto itF = files.find(callsTo->fileName);
                    if (itF == files.end())
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    bool localR = matchCallAndDefinition(currF->funcParams, callsTo->callsFromDetailed[cf].actualParams, currF->funcName,
                                                         callsTo->fileName, callsTo->callsFromDetailed[cf].detailCallsFrom.second, messages);
                    if (!localR)
                        count++;
                }
            }
        }
    }
    return count > 0;
}

bool isPassFullArray(SgExpression *ex)
{
    if (ex->lhs() == NULL && ex->rhs() == NULL)
        return true;
    else
        return false;
}

static bool hasCuttingDims(SgExpression *ex)
{
    if (ex->lhs())
    {
        if (ex->lhs()->variant() == EXPR_LIST)
        {
            SgExpression *list = ex->lhs();

            for  (auto list = ex->lhs(); list; list = list->rhs())
            {
                if (list->lhs() && list->lhs()->variant() == DDOT)
                    return true;
            }
        }
    }

    return false;
}

static bool checkDimSizesBetweenParams(bool type2, const FuncInfo* func, int parNum, SgStatement* decl, SgSymbol* symb, vector<Messages>& messages, const int& statLine)
{
    bool ret = false;
    if (type2)
    {
        DIST::Array* inFunction = (DIST::Array*)func->funcParams.parameters[parNum];
        DIST::Array* mainArray = getArrayFromDeclarated(decl, symb->identifier());

        if (mainArray == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        if (mainArray->GetDimSize() != inFunction->GetDimSize() &&
            !(inFunction->IsNotDistribute() && !mainArray->IsNotDistribute()))
        {
            wstring bufE, bufR;
            __spf_printToLongBuf(bufE, L"Function '%s' needs to be inlined due to different dimension sizes in formal (size = %d) and actual(size = %d) parameters for array reference '%s'",
                                 to_wstring(func->funcName).c_str(), inFunction->GetDimSize(), mainArray->GetDimSize(), to_wstring(symb->identifier()).c_str());

            __spf_printToLongBuf(bufR, R43,
                                 to_wstring(func->funcName).c_str(), to_wstring(symb->identifier()).c_str(), inFunction->GetDimSize(), mainArray->GetDimSize());

            messages.push_back(Messages(ERROR, statLine, bufR, bufE, 1013));
            __spf_print(1, "Function '%s' needs to be inlined due to different dimension sizes in formal (size = %d) and actual(size = %d) parameters for array reference '%s'\n",
                        func->funcName.c_str(), inFunction->GetDimSize(), mainArray->GetDimSize(), symb->identifier());
            ret = true;
        }
    }
    else
    {
        wstring bufE, bufR;
        __spf_printToLongBuf(bufE, L"Type mismatch in function '%s' in formal and actual parameters for array reference '%s'\n",
                             to_wstring(func->funcName).c_str(), to_wstring(symb->identifier()).c_str());

        __spf_printToLongBuf(bufR, R44,
                             to_wstring(func->funcName).c_str(), to_wstring(symb->identifier()).c_str());

        messages.push_back(Messages(ERROR, statLine, bufR, bufE, 1013));
        __spf_print(1, "Type mismatch in function '%s' in formal and actual parameters for array reference '%s'\n", func->funcName.c_str(), symb->identifier());
        ret = true;
    }
    return ret;
}

static bool checkParameter(SgExpression *ex, vector<Messages> &messages, const int statLine,
                           SgForStmt *loop, bool needToAddErrors, const FuncInfo *func, int parNum,
                           const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    bool ret = false;
    if (ex)
    {
        if (isArrayRef(ex))
        {
            SgArrayRefExp *arrayRef = isSgArrayRefExp(ex);
            SgType *type = ex->symbol()->type();
            if (arrayRef && type && type->variant() != T_STRING)
            {
                SgSymbol *symb = OriginalSymbol(arrayRef->symbol());
                if (symb)
                {
                    SgStatement *decl = declaratedInStmt(symb);
                    set<string> privatesVars;
                    tryToFindPrivateInAttributes(decl, privatesVars);
                    fillNonDistrArraysAsPrivate(decl, declaredArrays, declaratedArraysSt, privatesVars);

                    if (privatesVars.find(symb->identifier()) == privatesVars.end())
                    {
                        bool type1 = (func->funcParams.inout_types[parNum] & OUT_BIT) != 0;
                        bool type2 = func->funcParams.parametersT[parNum] == ARRAY_T;

                        string add = "";
                        wstring addW = L"";

                        if (type1)
                        {
                            add += "(as out argument";
                            addW += wstring(L"(") + RR42_1;
                        }

                        if (type2)
                        {
                            if (type1)
                            {
                                add += ", as array in function)";
                                addW += L"," + wstring(RR42_2) + L")";
                            }
                            else
                            {
                                add += "(as array in function)";
                                addW += L"(" + wstring(RR42_2) + L")";
                            }
                        }
                        else
                            add += ")";

                        if (!isPassFullArray(ex))
                        {
                            bool _hasCuttingDims = hasCuttingDims(ex);
                            
                            if (_hasCuttingDims)
                            {
                                if (needToAddErrors)
                                {
                                    if (loop)
                                    {
                                        wstring bufE, bufR;
                                        __spf_printToLongBuf(bufE, L"Function '%s' needs to be inlined due to non private array reference '%s' under loop on line %d %s", 
                                                             to_wstring(func->funcName).c_str(), to_wstring(symb->identifier()).c_str(), loop->lineNumber(), to_wstring(add).c_str());

                                        __spf_printToLongBuf(bufR, R41,
                                                             to_wstring(func->funcName).c_str(), to_wstring(symb->identifier()).c_str(), loop->lineNumber(), addW.c_str());

                                        messages.push_back(Messages(ERROR, statLine, bufR, bufE, 1013));
                                        __spf_print(1, "Function '%s' needs to be inlined due to non private array reference '%s' under loop on line %d %s\n", func->funcName.c_str(), symb->identifier(), loop->lineNumber(), add.c_str());
                                    }
                                    else
                                    {
                                        wstring bufE, bufR;
                                        __spf_printToLongBuf(bufE, L"Function '%s' needs to be inlined due to non private array reference '%s' %s", 
                                                             to_wstring(func->funcName).c_str(), to_wstring(symb->identifier()).c_str(), to_wstring(add).c_str());

                                        __spf_printToLongBuf(bufR, R42,
                                                             to_wstring(func->funcName).c_str(), to_wstring(symb->identifier()).c_str(), addW.c_str());

                                        messages.push_back(Messages(ERROR, statLine, bufR, bufE, 1013));
                                        __spf_print(1, "Function '%s' needs to be inlined due to non private array reference '%s' %s\n", func->funcName.c_str(), symb->identifier(), add.c_str());
                                    }
                                }
                                ret = true;
                            }
                            else
                            {
                                //deprecate N first dims to distribute
                                if (type2)
                                {
                                    ret = checkDimSizesBetweenParams(type2, func, parNum, decl, symb, messages, statLine);
                                    if (!ret)
                                    {
                                        DIST::Array *inFunction = (DIST::Array*)func->funcParams.parameters[parNum];
                                        DIST::Array *mainArray = getArrayFromDeclarated(decl, symb->identifier());

                                        if (mainArray == NULL)
                                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                                        set<DIST::Array*> realArrayRefs;
                                        getRealArrayRefs(mainArray, mainArray, realArrayRefs, arrayLinksByFuncCalls);
                                    
                                        const int toDepDims = inFunction->GetDimSize();
                                        for (auto &array : realArrayRefs)
                                            for (int z = 0; z < toDepDims; ++z)
                                                array->DeprecateDimension(z);

                                        for (int z = 0; z < toDepDims; ++z)
                                            mainArray->DeprecateDimension(z);

                                        inFunction->DeprecateAllDims();
                                        inFunction->SetDistributeFlag(DIST::NO_DISTR);

                                        wstring bufE, bufR;
                                        if (inFunction->GetDimSize() == 1)
                                            __spf_printToLongBuf(bufE, L"First dimension of array '%s' were deprecated to distributon due to function call '%s'", 
                                                                 to_wstring(symb->identifier()).c_str(), to_wstring(func->funcName).c_str());
                                        else
                                            __spf_printToLongBuf(bufE, L"First %d dimensions of array '%s' were deprecated to distributon due to function call '%s'", 
                                                                 inFunction->GetDimSize(), to_wstring(symb->identifier()).c_str(), to_wstring(func->funcName).c_str());

                                        if (inFunction->GetDimSize() == 1)
                                            __spf_printToLongBuf(bufR, R73, 
                                                                 to_wstring(symb->identifier()).c_str(), to_wstring(func->funcName).c_str());
                                        else
                                            __spf_printToLongBuf(bufR, R72, 
                                                                 inFunction->GetDimSize(), to_wstring(symb->identifier()).c_str(), to_wstring(func->funcName).c_str());

                                        messages.push_back(Messages(NOTE, statLine, bufR, bufE, 1040));
                                    }
                                }
                            }
                        }
                        else // check dim sizes between formal and actual parameters
                            ret = checkDimSizesBetweenParams(type2, func, parNum, decl, symb, messages, statLine);
                    }
                }
            }
        }

        if (ex->lhs())
        {
            bool res = checkParameter(ex->lhs(), messages, statLine, loop, needToAddErrors, func, parNum, arrayLinksByFuncCalls);
            ret |= res;
        }
        if (ex->rhs())
        {
            bool res = checkParameter(ex->rhs(), messages, statLine, loop, needToAddErrors, func, parNum, arrayLinksByFuncCalls);
            ret |= res;
        }
    }

    return ret;
}

static bool checkParameter(SgExpression *parList, vector<Messages> &messages, bool needToAddErrors, 
                           const FuncInfo *func, const int funcOnLine, SgForStmt *loop,
                           const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    int parNum = 0;
    bool needInsert = false;
    while (parList)
    {
        bool res = checkParameter(parList->lhs(), messages, funcOnLine, loop, needToAddErrors, func, parNum, arrayLinksByFuncCalls);
        needInsert |= res;
        ++parNum;
        parList = parList->rhs();
    }
    return needInsert;
}

static vector<int> findNoOfParWithLoopVar(SgExpression *pars, const string &loopSymb) 
{
    vector<int> parsWithLoopSymb;

    int parNo = 0;
    for (SgExpression *par = pars; par != NULL; par = par->rhs(), parNo++)
    {
        if (findLoopVarInParameter(par->lhs(), loopSymb))
            parsWithLoopSymb.push_back(parNo);
    }

    return parsWithLoopSymb;
}

static bool processParameterList(SgExpression *parList, SgForStmt *loop, const FuncInfo *func, const int funcOnLine, bool needToAddErrors,
                                 vector<Messages> &messages, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    bool needInsert = false;
    bool hasLoopVar = false;

    if (loop)
        hasLoopVar = findLoopVarInParameter(parList, loop->symbol()->identifier());

    if (hasLoopVar)
    {
        const vector<int> parsWithLoopSymb = findNoOfParWithLoopVar(parList, loop->symbol()->identifier());

        int idx = -1;
        for (auto &par : parsWithLoopSymb)
        {
            if (func->isParamUsedAsIndex[par])
            {
                idx = par + 1;
                break;
            }
        }

        if (idx != -1)
        {
            wstring bufE, bufR;
            __spf_printToLongBuf(bufE, L"Function '%s' needs to be inlined due to use of loop's symbol on line %d as index of an array inside this call, in parameter num %d", 
                                 to_wstring(func->funcName).c_str(), loop->lineNumber(), idx);

            __spf_printToLongBuf(bufR, R45,
                to_wstring(func->funcName).c_str(), idx, loop->lineNumber());

            if (needToAddErrors)
            {
                messages.push_back(Messages(ERROR, funcOnLine, bufR, bufE, 1013));
                __spf_print(1, "Function '%s' needs to be inlined due to use of loop's symbol  on line %d as index of an array inside this call, in parameter num %d\n", 
                                func->funcName.c_str(), loop->lineNumber(), idx);
            }

            needInsert = true;
        }
        else
            needInsert = checkParameter(parList, messages, needToAddErrors, func, funcOnLine, loop, arrayLinksByFuncCalls);
    }
    else
        needInsert = checkParameter(parList, messages, needToAddErrors, func, funcOnLine, loop, arrayLinksByFuncCalls);

    return needInsert;
}

static bool findFuncCall(SgExpression *ex, const FuncInfo *func, vector<Messages> &messages, const int statLine, SgForStmt *loop, bool needToAddErrors,
                         const map<string, FuncInfo*> &funcByName,
                         const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, 
                         bool processAll = false, set<string> *funcChecked = NULL, set<string> *needToInsert = NULL)
{
    bool ret = false;
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
        {
            if (processAll)
            {
                if (funcChecked && needToInsert)
                {
                    const string fName = ex->symbol()->identifier();
                    if (funcChecked->find(fName) == funcChecked->end())
                    {
                        funcChecked->insert(fName);
                        auto itF = funcByName.find(fName);
                        if (itF != funcByName.end())
                        {
                            ret = processParameterList(ex->lhs(), loop, itF->second, statLine, needToAddErrors, messages, arrayLinksByFuncCalls);
                            if (ret)
                                needToInsert->insert(fName);
                        }
                    }
                }
                else
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
            else
            {
                if (ex->symbol()->identifier() == func->funcName)
                {
                    bool res = processParameterList(ex->lhs(), loop, func, statLine, needToAddErrors, messages, arrayLinksByFuncCalls);
                    ret |= res;
                }
            }
        }

        bool resL = findFuncCall(ex->lhs(), func, messages, statLine, loop, needToAddErrors, funcByName, arrayLinksByFuncCalls, processAll, funcChecked, needToInsert);
        bool resR = findFuncCall(ex->rhs(), func, messages, statLine, loop, needToAddErrors, funcByName, arrayLinksByFuncCalls, processAll, funcChecked, needToInsert);
        ret |= resL || resR;
    }

    return ret;
}

static SgStatement* getStatByLine(string file, const int line, const map<string, map<int, SgStatement*>> &statByLine)
{
    auto itF = statByLine.find(file);
    if (itF == statByLine.end())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    auto itS = itF->second.find(line);
    if (itS == itF->second.end())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SwitchFile(itS->second->getFileId());
    return itS->second;
}

static void findInsertedFuncLoopGraph(const vector<LoopGraph*> &childs, set<string> &needToInsert, SgFile *currF, 
                                      vector<Messages> &messages, bool needToAddErrors, const map<string, FuncInfo*> &funcByName,
                                      const map<string, map<int, SgStatement*>> &statByLine, 
                                      const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, set<string> &funcChecked)
{
    for (int k = 0; k < (int)childs.size(); ++k)
    {
        auto stat = getStatByLine(currF->filename(), childs[k]->lineNum, statByLine);
        if (stat == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        SgForStmt *loop = isSgForStmt(stat);
        if (loop == NULL && stat->variant() != WHILE_NODE)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        if (loop && loop->lineNumber() != childs[k]->lineNum)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        // TODO: find loop variable in common block or module

        //dont check loop outside of parallel region and DO_WHILE
        if (childs[k]->region && loop)
        {
            for (int i = 0; i < (int)childs[k]->calls.size(); ++i)
            {
                //dont check call with !$SPF NOINLINE
                bool needToCheck = true;
                const string &funcName = childs[k]->calls[i].first;
                funcChecked.insert(funcName);

                auto it = funcByName.find(funcName);
                if (it != funcByName.end())
                    needToCheck = !(it->second->doNotInline == true);
                else // function not found
                    needToCheck = false;

                if (!needToCheck)
                    continue;

                int funcOnLine = childs[k]->calls[i].second;
                SgStatement *func = getStatByLine(currF->filename(), funcOnLine, statByLine);

                bool needInsert = false;
                const int var = func->variant();

                if (var == PROC_STAT)
                {
                    bool res = processParameterList(func->expr(0), loop, it->second, funcOnLine, needToAddErrors, messages, arrayLinksByFuncCalls);
                    needInsert |= res;
                }
                else
                    for (int z = 0; z < 3; ++z)
                    {
                        bool res = findFuncCall(func->expr(z), it->second, messages, funcOnLine, loop, needToAddErrors, funcByName, arrayLinksByFuncCalls);
                        needInsert |= res;
                    }

                if (needInsert)
                    needToInsert.insert(childs[k]->calls[i].first);
            }
        }
        findInsertedFuncLoopGraph(childs[k]->children, needToInsert, currF, messages, needToAddErrors, funcByName, statByLine, arrayLinksByFuncCalls, funcChecked);
    }
}

static bool runCheckOutOfLoop(SgExpression *parList, const FuncInfo *func, const int lineFromCall, bool needToAddErrors,
                              vector<Messages> &messages, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls)
{
    bool needInsert = processParameterList(parList, NULL, func, lineFromCall, needToAddErrors, messages, arrayLinksByFuncCalls);
    return needInsert;
}

static void findInsertedFuncLoopGraph(const map<string, vector<LoopGraph*>> &loopGraph, set<string> &needToInsert,
                                      SgProject *proj, const map<string, int> &files, map<string, vector<Messages>> &allMessages,
                                      bool needToAddErrors, const map<string, FuncInfo*> &funcByName,
                                      const map<string, map<int, SgStatement*>> &statByLine,
                                      const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, const vector<ParallelRegion*> &regions)
{
    set<string> funcChecked;
    
    for (auto &loop : loopGraph)
    {
        const int fileN = files.find(loop.first)->second;
        SgFile *currF = &(proj->file(fileN));
        SwitchFile(fileN);

        auto itM = allMessages.find(loop.first);
        if (itM == allMessages.end())
            itM = allMessages.insert(itM, make_pair(loop.first, vector<Messages>()));
        findInsertedFuncLoopGraph(loop.second, needToInsert, currF, itM->second, needToAddErrors, funcByName, statByLine, arrayLinksByFuncCalls, funcChecked);
    }

    //not checked, out of loops
    for (int f = 0; f < proj->numberOfFiles(); ++f)
    {
        SgFile *currF = &(proj->file(f));
        SwitchFile(f);

        auto itM = allMessages.find(currF->filename());
        if (itM == allMessages.end())
            itM = allMessages.insert(itM, make_pair(currF->filename(), vector<Messages>()));

        for (int z = 0; z < currF->numberOfFunctions(); ++z)
        {
            SgStatement *funcInFile = currF->functions(z);
            for (SgStatement *st = funcInFile->lexNext(); st != funcInFile->lastNodeOfStmt(); st = st->lexNext())
            {
                if (st->variant() == CONTAINS_STMT)
                    break;

                if (st->lineNumber() == -1)
                    continue;

                if (!__gcov_doesThisLineExecuted(st->fileName(), st->lineNumber()))
                    continue;

                set<ParallelRegion*> allRegs = getAllRegionsByLine(regions, st->fileName(), st->lineNumber());
                if (allRegs.size() == 0)
                    continue;

                if (isSgExecutableStatement(st))
                {
                    if (st->variant() == PROC_STAT)
                    {
                        const string fName = st->symbol()->identifier();
                        auto it = funcChecked.find(fName);
                        auto itF = funcByName.find(fName);
                        if (it == funcChecked.end() && itF != funcByName.end())
                        {
                            bool needInsert = runCheckOutOfLoop(st->expr(0), itF->second, st->lineNumber(), needToAddErrors, itM->second, arrayLinksByFuncCalls);
                            if (needInsert)
                                needToInsert.insert(fName);
                        }
                    }
                    else
                        for (int z = 0; z < 3; ++z)
                            findFuncCall(st->expr(z), NULL, itM->second, st->lineNumber(), NULL, needToAddErrors, funcByName, arrayLinksByFuncCalls, true, &funcChecked, &needToInsert);
                }
            }
        }
    }
}

//TODO: 'needToAddErrors' is deprecated and always true
int CheckFunctionsToInline(SgProject *proj, const map<string, int> &files, const char *fileName, map<string, vector<FuncInfo*>> &funcByFile, 
                           const map<string, vector<LoopGraph*>> &loopGraph, map<string, vector<Messages>> &allMessages, bool needToAddErrors,
                           const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCalls, const vector<ParallelRegion*> &regions)
{
    map<string, map<int, SgStatement*>> statByLine; //file -> map    
    //build info
    for (int i = 0; i < proj->numberOfFiles(); ++i)
    {
        map<int, SgStatement*> toAdd;
        SgFile *file = &(proj->file(i));
        SwitchFile(i);

        SgStatement *st = file->firstStatement();
        string currF = file->filename();        
        while (st)
        {
            if (st->lineNumber() != 0 && st->fileName() == currF)
                toAdd[st->lineNumber()] = st;
            st = st->lexNext();
        }

        statByLine[file->filename()] = toAdd;
    }

    map<string, FuncInfo*> funcByName;
    for (auto& byFile : funcByFile)
    {
        for (int k = 0; k < byFile.second.size(); ++k)
        {
            string name = byFile.second[k]->funcName;
            auto itF = funcByName.find(name);
            if (itF == funcByName.end())
                funcByName.insert(itF, make_pair(name, byFile.second[k]));
            else
            {
                __spf_print(1, "function with name '%s' was found in file '%s' on line %d, current position is file '%s' and line %d\n", 
                            name.c_str(), itF->second->fileName.c_str(), itF->second->linesNum.first, byFile.second[k]->fileName.c_str(), byFile.second[k]->linesNum.first);
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
        }
    }

    set<string> needToInsert;
    findInsertedFuncLoopGraph(loopGraph, needToInsert, proj, files, allMessages, needToAddErrors, funcByName, statByLine, arrayLinksByFuncCalls, regions);
    bool err = matchCallAndDefinition(proj, files, allMessages, needToAddErrors, funcByName);
    if (err)
        return -1;

    if (needToInsert.size() > 0)
    {
        for (auto it = needToInsert.begin(); it != needToInsert.end(); ++it)
        {
            auto itF = funcByName.find(*it);
            if (itF != funcByName.end())
                itF->second->needToInline = true;
        }
    }

    if (fileName)
    {
        FILE *out = fopen(fileName, "w");
        if (out == NULL)
        {
            __spf_print(1, "can not open file %s\n", fileName);
            throw -1;
        }

        fprintf(out, "digraph G{\n");

        auto it = funcByFile.begin();
        set<string> noInline;

        int fileNum = 0;
        while (it != funcByFile.end())
        {
            fprintf(out, "subgraph cluster%d {\n", fileNum);
            const int dimSize = (int)it->second.size();
            set<string> uniqNames;
            for (int k = 0; k < dimSize; ++k)
            {
                const string currfunc = it->second[k]->funcName;
                if (it->second[k]->doNotInline)
                    noInline.insert(currfunc);

                set<string>::iterator it = uniqNames.find(currfunc);
                if (it == uniqNames.end())
                {
                    uniqNames.insert(it, currfunc);
                    fprintf(out, "\"%s\"\n", currfunc.c_str());
                }
            }
            fprintf(out, "label = \"file <%s>\"\n", removeString(".\\", it->first).c_str());
            fprintf(out, "}\n");

            fileNum++;
            it++;
        }

        it = funcByFile.begin();
        while (it != funcByFile.end())
        {
            const char *formatString = "\"%s\" -> \"%s\" [minlen=2.0];\n";
            const int dimSize = (int)it->second.size();
            for (int k = 0; k < dimSize; ++k)
            {
                const string callFrom = it->second[k]->funcName;
                set<string>::const_iterator i = it->second[k]->callsFrom.begin();
                for (; i != it->second[k]->callsFrom.end(); i++)
                    fprintf(out, formatString, callFrom.c_str(), i->c_str());
            }
            it++;
        }

        auto it_set = needToInsert.begin();
        for (; it_set != needToInsert.end(); it_set++)
        {
            if (noInline.find(*it_set) == noInline.end())
                fprintf(out, "\"%s\" [color=red]\n", it_set->c_str());
        }
        fprintf(out, "overlap=false\n");
        fprintf(out, "}\n");
        fclose(out);
    }

    return needToInsert.size();
}

static string printChainRec(const vector<FuncInfo*> &currentChainCalls)
{
    string out = "";
    for (int i = 0; i < currentChainCalls.size(); ++i)
    {
        out += currentChainCalls[i]->funcName;
        if (i != currentChainCalls.size() - 1)
            out += " -> ";
    }
    return out;
}

static bool hasRecursionChain(vector<FuncInfo*> currentChainCalls, const FuncInfo *current,
                              const map<string, FuncInfo*> &allFuncinfo, vector<Messages> &messagesForFile)
{
    bool retVal = false;

    vector<FuncInfo*> toCallNext;
    for (auto it = current->callsFrom.begin(); it != current->callsFrom.end(); ++it)
    {
        auto itF = allFuncinfo.find(*it);
        if (itF == allFuncinfo.end())
            continue;

        if (std::find(currentChainCalls.begin(), currentChainCalls.end(), itF->second) != currentChainCalls.end())
        {
            if (itF->second == currentChainCalls[0])
            {
                retVal = true;

                currentChainCalls.push_back(itF->second);
                const string &chain = printChainRec(currentChainCalls);
                __spf_print(1, "  For function on line %d found recursive chain calls: %s\n", currentChainCalls[0]->linesNum.first, chain.c_str());

                wstring bufE, bufR;
                __spf_printToLongBuf(bufE, L"  Found recursive chain calls: %s, this function will be ignored", to_wstring(chain).c_str());
                __spf_printToLongBuf(bufR, R46, to_wstring(chain).c_str());

                messagesForFile.push_back(Messages(NOTE, currentChainCalls[0]->linesNum.first, bufR, bufE, 1014));
                break;
            }
        }
        else
            toCallNext.push_back(itF->second);
    }

    if (!retVal)
    {
        for (int i = 0; i < toCallNext.size() && !retVal; ++i)
        {
            currentChainCalls.push_back(toCallNext[i]);
            retVal = retVal || hasRecursionChain(currentChainCalls, toCallNext[i], allFuncinfo, messagesForFile);
            currentChainCalls.pop_back();
        }
    }

    return retVal;
}

void checkForRecursion(SgFile *file, map<string, vector<FuncInfo*>> &allFuncInfo, vector<Messages> &messagesForFile)
{
    auto itCurrFuncs = allFuncInfo.find(file->filename());
    if (itCurrFuncs == allFuncInfo.end())
        return;

    map<string, FuncInfo*> mapFuncInfo;
    createMapOfFunc(allFuncInfo, mapFuncInfo);

    for (int i = 0; i < itCurrFuncs->second.size(); ++i)
    {
        __spf_print(1, "  run for func %s\n", itCurrFuncs->second[i]->funcName.c_str());
        if (hasRecursionChain( { itCurrFuncs->second[i] }, itCurrFuncs->second[i], mapFuncInfo, messagesForFile))
            itCurrFuncs->second[i]->doNotAnalyze = true;
    }
}

void propagateWritesToArrays(map<string, vector<FuncInfo*>> &allFuncInfo)
{
    map<string, FuncInfo*> funcByName;
    createMapOfFunc(allFuncInfo, funcByName);

    bool change = true;
    while (change)
    {
        change = false;

        for (auto &func : funcByName)
        {
            if (func.second->funcParams.countOfPars == 0)
                continue;

            for (int z = 0; z < func.second->funcParams.countOfPars; ++z)
            {
                if ((func.second->funcParams.inout_types[z] & OUT_BIT) == 0)
                    continue;

                for (auto &callsTo : func.second->callsTo)
                {
                    map<string, int> parNames;
                    for (int p = 0; p < callsTo->funcParams.countOfPars; ++p)
                        parNames[callsTo->funcParams.identificators[p]] = p;

                    bool ok = callsTo->funcPointer->GetOriginal()->switchToFile();
                    if (!ok)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    for (auto &callFromInfo : callsTo->callsFromDetailed)
                    {
                        auto& callFrom = callFromInfo.pointerDetailCallsFrom;
                        if (callFrom.second == VAR_REF) // pass procedure through parameter
                            continue;

                        SgExpression *arg = NULL;
                        if (callFrom.second == PROC_STAT)
                        {
                            SgCallStmt *call = (SgCallStmt*)callFrom.first;
                            if (call->symbol()->identifier() != func.second->funcName)
                                continue;
                            if (z >= call->numberOfArgs())
                                continue;

                            arg = call->arg(z);
                        }
                        else if (callFrom.second == FUNC_CALL)
                        {
                            SgFunctionCallExp *call = (SgFunctionCallExp*)callFrom.first;

                            if (call->symbol()->identifier() != func.second->funcName)
                                continue;
                            if (z >= call->numberOfArgs())
                                continue;

                            arg = call->arg(z);
                        }

                        if (arg == NULL)
                        {
                            if ((func.second->funcParams.inout_types[z] & OPTIONAL_BIT) != 0)
                                continue;
                            else
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        }

                        string argName = "";
                        if (arg->symbol())
                            argName = arg->symbol()->identifier();

                        auto it = parNames.find(argName);
                        if (it != parNames.end() && ((callsTo->funcParams.inout_types[it->second] & OUT_BIT) == 0))
                        {
                            change = true;
                            callsTo->funcParams.inout_types[it->second] |= OUT_BIT;
                            if ((func.second->funcParams.inout_types[z] & IN_BIT) != 0)
                                callsTo->funcParams.inout_types[it->second] |= IN_BIT;
                        }
                    }
                }
            }
        }
    }
}

//TODO: inmprove checker
void detectCopies(map<string, vector<FuncInfo*>> &allFuncInfo)
{
    map<string, FuncInfo*> mapOfFunc;
    createMapOfFunc(allFuncInfo, mapOfFunc);

    for (auto& byFile : allFuncInfo)
    {
        for (auto& func : byFile.second)
        {
            string name = func->funcName;
            auto offset = name.rfind("_spf_");
            if (offset != string::npos)
            {
                string orig = name.substr(0, offset);
                auto it = mapOfFunc.find(orig);
                if (it != mapOfFunc.end())
                    it->second->fullCopiesOfThisFunction.push_back(func);
            }
        }
    }
}

void fillInterfaceBlock(map<string, vector<FuncInfo*>>& allFuncInfo)
{
    map<string, FuncInfo*> mapOfFunc;
    createMapOfFunc(allFuncInfo, mapOfFunc);

    for (auto& byFile : allFuncInfo)
    {
        for (auto& func : byFile.second)
        {            
            for (auto& interface : func->interfaceBlocks)
            {
                auto itF = mapOfFunc.find(interface.first);
                if (itF == mapOfFunc.end())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                set<string> namesToFind = { interface.first };
                for (auto& elem : itF->second->fullCopiesOfThisFunction)
                    namesToFind.insert(elem->funcName);

                for (auto& elem : namesToFind)
                {
                    auto isCalled = func->callsFrom.find(elem);
                    if (isCalled != func->callsFrom.end())
                    {
                        interface.second = itF->second;
                        break;
                    }
                }
            }

            //filted interfaces
            map<string, FuncInfo*> copy = func->interfaceBlocks;
            func->interfaceBlocks.clear();
            for (auto& interface : func->interfaceBlocks)
                if (interface.second)
                    func->interfaceBlocks[interface.first] = interface.second;
        }
    }

    for (auto& byFile : allFuncInfo)
    {
        for (auto& func : byFile.second)
        {
            if (!func->isInterface)
                continue;

            if (func->interfaceSynonims.size() == 0)
                continue;

            for (auto& syn : func->interfaceSynonims)
            {
                auto itF = mapOfFunc.find(syn.first);
                if (itF == mapOfFunc.end())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                syn.second = itF->second;
            }
        }
    }
}

void removeDistrStateFromDeadFunctions(const map<string, vector<FuncInfo*>>& allFuncInfo, 
                                       const map<tuple<int, string, string>, pair<DIST::Array*, DIST::ArrayAccessInfo*>>& declaredArrays)
{
    map<string, FuncInfo*> funcByName;
    createMapOfFunc(allFuncInfo, funcByName);

    // remove distr state of arrays from dead functions
    for (auto& elem : declaredArrays)
    {
        DIST::Array* array = elem.second.first;
        if (array->GetLocation().first == DIST::l_PARAMETER)
        {
            if (array->IsNotDistribute() == false)
            {
                auto declInfo = array->GetDeclInfo();
                auto place = *declInfo.begin();
                if (SgFile::switchToFile(place.first) == -1)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                SgStatement *st = SgStatement::getStatementByFileAndLine(place.first, place.second);
                checkNull(st, convertFileName(__FILE__).c_str(), __LINE__);

                SgProgHedrStmt *hedr = isSgProgHedrStmt(getFuncStat(st));
                checkNull(hedr, convertFileName(__FILE__).c_str(), __LINE__);

                const string funcName = hedr->nameWithContains();

                auto it = funcByName.find(funcName.c_str());
                if (it == funcByName.end())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                if (it->second->deadFunction)
                    array->SetDistributeFlag(DIST::NO_DISTR);
            }
        }
    }
}

int getLvlCall(FuncInfo* currF, int lvl, const string& func, const string& file, int line)
{
    if (currF->funcName == func)
        return 0;

    for (auto& callsFromInfo : currF->callsFromDetailed)
    {
        auto& callsFrom = callsFromInfo.detailCallsFrom;
        if (callsFrom.first == func && callsFrom.second == line && currF->fileName == file)
            return lvl;
    }
    
    int whatLvl = -1;
    for (auto& callsFrom : currF->callsFromV)
    {
        int outLvl = getLvlCall(callsFrom, lvl + 1, func, file, line);
        if (outLvl != -1)
        {
            whatLvl = outLvl;
            break;
        }
    }

    return whatLvl;
}


void setInlineAttributeToCalls(const map<string, FuncInfo*>& allFunctions, 
                               const map<string, set<pair<string, int>>>& inDataChains,
                               const map<string, vector<SgStatement*>>& hiddenData)
{
    set<pair<string, int>> pointsForShadowCopies;

    for (auto& elem : allFunctions)
    {
        auto itNeed = inDataChains.find(elem.first);

        const FuncInfo* curr = elem.second;
        set<pair<string, int>> needToInline;
        if (itNeed != inDataChains.end()) 
            needToInline = itNeed->second;

        for (int k = 0; k < curr->callsFromDetailed.size(); ++k)
        {
            if (needToInline.find(curr->callsFromDetailed[k].detailCallsFrom) == needToInline.end() &&
                !isIntrinsicFunctionName(curr->callsFromDetailed[k].detailCallsFrom.first.c_str()))
            {
                pair<void*, int> detail = curr->callsFromDetailed[k].pointerDetailCallsFrom;

                if (SgFile::switchToFile(curr->fileName) == -1)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                if (detail.second == PROC_STAT)
                    ((SgStatement*)detail.first)->addAttribute(BOOL_VAL);
                else if (detail.second == FUNC_CALL)
                {
                    //TODO: many functions in same statement
                    SgStatement* callSt = SgStatement::getStatementByFileAndLine(curr->fileName, curr->callsFromDetailed[k].detailCallsFrom.second);
                    if (!callSt)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    //((SgExpression*)detail.first)->addAttribute(BOOL_VAL);
                    callSt->addAttribute(BOOL_VAL);
                }
                else
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                pointsForShadowCopies.insert(make_pair(curr->fileName, curr->callsFromDetailed[k].detailCallsFrom.second));
                __spf_print(1, " added attribute to '%s' <%s, %d>\n", curr->callsFromDetailed[k].detailCallsFrom.first.c_str(), curr->fileName.c_str(), curr->callsFromDetailed[k].detailCallsFrom.second);
            }
        }
    }

    if (pointsForShadowCopies.size())
    {
        for (auto& byFile : hiddenData)
        {
            if (SgFile::switchToFile(byFile.first) == -1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            for (auto& stF : byFile.second)
                for (auto st = stF; st != stF->lastNodeOfStmt(); st = st->lexNext())
                    if (pointsForShadowCopies.find(make_pair(st->fileName(), st->lineNumber())) != pointsForShadowCopies.end())
                    {
                        st->addAttribute(BOOL_VAL);
                        //__spf_print(1, " added attribute to shadow copy in file '%s' <%s %d>\n", byFile.first.c_str(), st->fileName(), st->lineNumber());
                    }
        }
    }
}

static json convertToJson(const FuncInfo* currFunc) {
    json func;

    if (currFunc)
    {
        func["funcName"] = currFunc->funcName;
        func["line"] = currFunc->linesNum.first;
        func["lineEnd"] = currFunc->linesNum.second;
        func["isMain"] = (int)currFunc->isMain;
        func["needToInline"] = (int)currFunc->needToInline;
        func["doNotInline"] = (int)currFunc->doNotInline;
        func["doNotAnalyze"] = (int)currFunc->doNotAnalyze;

        json func_pars = json::array();

        for (int z = 0; z < currFunc->funcParams.countOfPars; ++z)
        {
            json par;
            par["inoutType"] = currFunc->funcParams.inout_types[z];
            par["identificator"] = currFunc->funcParams.identificators[z];
            par["parameterT"] = string(paramNames[currFunc->funcParams.parametersT[z]]);

            func_pars.push_back(par);
        }

        func["params"] = func_pars;

        json calls_from = json::array();
        for (const auto& call_from : currFunc->callsFromDetailed)
        {
            json call;
            call["line"] = call_from.detailCallsFrom.second;
            call["funcName"] = call_from.detailCallsFrom.first;

            calls_from.push_back(call);
        }
        func["callsFrom"] = calls_from;
    }
    return func;
}

json convertToJson(const map<string, vector<FuncInfo*>>& funcsByFileMap) {
    json loopsByFile = json::array();

    for (auto& byFile : funcsByFileMap)
    {
        json func;
        const string& file = byFile.first;

        json func_array = json::array();
        for (auto& func : byFile.second)
        {
            auto conv = convertToJson(func);
            if (!conv.empty())
                func_array.push_back(conv);
        }

        func["file"] = file;
        func["functions"] = func_array;

        loopsByFile.push_back(func);
    }

    json allFuncs;
    allFuncs["allFunctions"] = loopsByFile;
    return allFuncs;
}
#undef DEBUG
