#include "leak_detector.h"

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

#include "dvm.h"
#include "errors.h"
#include "utils.h"
#include "SgUtils.h"
#include "graph_calls_func.h"
#include "inliner.h"
#include "../VisualizerCalls/SendMessage.h"
#include "expr_transform.h"

using std::set;
using std::map;
using std::vector;
using std::pair;
using std::make_pair;
using std::string;
using std::wstring;
using std::to_string;
using std::get;
using std::tuple;

#define DEB 0

extern map<string, vector<SgStatement*>> hiddenData;

static map<SgSymbol*, SgSymbol*> linkToOrig;
static map<pair<string, int>, map<string, SgSymbol*>> createdByFunc; // key == <funcName, fileID>
static map<string, int> createdVarCounterByName;
static map<pair<string, int>, map<string, pair<SgSymbol*, string>>> replacedConstRef; // key == <funcName, fileID>
static map<pair<string, int>, set<SgValueExp*>> dataDeclsByFunc; // key == <funcName, fileID>
static map<pair<string, int>, map<string, SgExpression*>> dataDeclsByFuncS; // key == <funcName, fileID>

struct PointCall
{
private:
    bool findChainCall(FuncInfo* call, int lvl, vector<string>& str, const string& toFind) const
    {
        bool found = false;
        if (lvl == 0)
        {
            for (auto& elem : call->callsFromV)
            {
                if (elem->funcName == toFind)
                {
                    str.push_back(elem->funcName);
                    found = true;
                    break;
                }
            }
        }
        else
        {
            for (auto& elem : call->callsFromV)
            {
                str.push_back(elem->funcName);
                found = findChainCall(elem, lvl - 1, str, toFind);
                if (found)
                    break;
                else
                    str.pop_back();
            }
        }

        return found;
    }

public:
    pair<string, int> mainPoint;
    FuncInfo *func;
    string currCall;
    int currLvl;

    string getChainCall() const
    {
        string str = func->funcName;
        if (currLvl)
        {
            vector<string> chain;
            findChainCall(func, currLvl - 1, chain, currCall);
            if (chain.size() != currLvl)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            for (int z = 0; z < chain.size(); ++z)
                str += "->" + chain[z];
        }
        return str;
    }
};

static void checkSymbols(const int currFileId, const set<SgSymbol*>& symbs)
{    
    for (auto& symb : symbs)
    {
        if (symb->getFileId() != currFileId)
        {
            __spf_print(1, "check failed - given %d, correct %d\n", symb->getFileId(), currFileId);
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
    }
}

static map<string, SgExpression*> createMapOfArgs(SgStatement* tempHedr, SgExpression* actualArgs)
{
    __spf_print(DEB, "------create map of vars------\n");

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

    int numPars = hedr->numberOfParameters();
    map<string, SgExpression*> vars;
    int i = 0;
    while (actualArgs)
    {
        if (i >= numPars)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        auto actualArg = actualArgs->lhs();
        auto formalArg = hedr->parameter(i++);

        auto it = vars.find(formalArg->identifier());
        if (it == vars.end())
            vars.insert(it, make_pair(formalArg->identifier(), actualArg));
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        actualArgs = actualArgs->rhs();
    }

    return vars;
}

static bool isAllocated(SgSymbol* s)
{
    return (s->attributes() & ALLOCATABLE_BIT) || (s->attributes() & POINTER_BIT);
}

static inline SgSymbol* createSymbAndDecl(const string& funcName, const string& varName, SgSymbol* newS, set<SgSymbol*>& newSymbols, SgType* type = NULL)
{
    SgSymbol* original = newS;
    if (newS)
        newS = &newS->copy();

    int* count;
    if (createdVarCounterByName.find(varName) == createdVarCounterByName.end())
    {
        createdVarCounterByName[varName] = 0;
        count = &createdVarCounterByName[varName];
    }
    else
        count = &createdVarCounterByName[varName];

    const string base = varName + "_";
    int nextCount = checkSymbNameAndCorrect(base, count[0]);
    const string newName = base + to_string(nextCount);

    const pair<string, int> key = make_pair(funcName, current_file_id);
    if (newS)
    {
        if (isSgConstantSymb(newS))
        {
            if (funcName != "null" && createdByFunc[key].find(varName) == createdByFunc[key].end() ||
                funcName == "null")
            {
                auto it = replacedConstRef[key].find(newS->identifier());
                if (it != replacedConstRef[key].end() && it->second.first->identifier() != it->second.second)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                if (it == replacedConstRef[key].end())
                {
                    //check replaces
                    SgSymbol* wasDone = NULL;
                    for (auto& elem : replacedConstRef[key])
                        if (elem.second.second == varName)
                            wasDone = elem.second.first;

                    if (wasDone == NULL)
                    {
                        replacedConstRef[key][newS->identifier()] = make_pair(newS, newName.c_str());
                        newS->changeName(newName.c_str());
                    }
                    else
                        newS = wasDone;
                }
            }
            else if (funcName != "null" && createdByFunc[key].find(varName) != createdByFunc[key].end())
            {
                ; //skip
            }
            else
                newS->changeName(newName.c_str());
        }
        else
            newS->changeName(newName.c_str());
    }
    else
        newS = findSymbolOrCreate(current_file, newName, type);

    if (newS->variant() == PROCEDURE_NAME)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    if (funcName != "null")
    {
        auto itS = createdByFunc[key].find(varName);
        if (itS == createdByFunc[key].end())
        {
            createdByFunc[key][varName] = newS;
            count[0] = nextCount;
            if (original && isAllocated(original))
                linkToOrig[newS] = original;
        }
        else
            newS = itS->second;
    }
    //printf("newS %s = %d\n", newS->identifier(), newS->id());
    newSymbols.insert(newS);
    checkSymbols(current_file_id, newSymbols);
    return newS;
}

static SgStatement* findDuplicateInHidden(SgStatement* data)
{
    SgStatement* clone = NULL;

    auto itF = hiddenData.find(current_file->filename());
    if (itF == hiddenData.end())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (auto& func : itF->second)
    {
        if (func->fileName() != string(data->fileName()))
            continue;

        SgStatement* st = func->lexNext();
        SgStatement* last = func->lastNodeOfStmt();

        while (st != last)
        {
            if (data->lineNumber() == st->lineNumber() && data->variant() == st->variant())
            {
                clone = st;
                break;
            }
            st = st->lexNext();
        }

        if (clone)
            break;
    }

    checkNull(clone, convertFileName(__FILE__).c_str(), __LINE__);
    return clone;
}

static SgValueExp* oneExpr = NULL;
static SgValueExp* zeroExpr = NULL;

static vector<SgExpression*> getLowBounds(SgSymbol* arrayS)
{
    if (oneExpr == NULL)
        oneExpr = new SgValueExp(1);

    SgExpression* list = NULL;

    if (isAllocated(arrayS))
    {   
        SgSymbol* copyFrom = NULL;
        if (linkToOrig.find(arrayS) == linkToOrig.end())
            copyFrom = arrayS;
        else
            copyFrom = linkToOrig[arrayS];

        SgStatement* decl = declaratedInStmt(copyFrom);
        checkNull(decl, convertFileName(__FILE__).c_str(), __LINE__);

        int consistInAllocates = 0;
        const string origName = OriginalSymbol(copyFrom)->identifier();

        auto allocData = getAttributes<SgStatement*, SgStatement*>(decl, set<int>{ ALLOCATE_STMT });
        if (allocData.size() == 0) // try to find statements in original file
        {
            string declFile(decl->fileName());
            int line = decl->lineNumber();
            string file_name = current_file->filename();
            
            if (current_file->filename() != declFile)
            {
                auto trueDecl = SgStatement::getStatementByFileAndLine(declFile, line);
                if (trueDecl)
                {
                    auto trueData = getAttributes<SgStatement*, SgStatement*>(trueDecl, set<int>{ ALLOCATE_STMT });
                    if (trueData.size())
                        allocData = trueData;
                }
            }
            SgFile::switchToFile(file_name);
        }

        for (auto data : allocData)
        {
            if (data->variant() != ALLOCATE_STMT)
                continue;

            if (data->getFileId() != current_file_id)
                data = findDuplicateInHidden(data);

            SgExpression* iter = data->expr(0);
            
            while (iter)
            {
                SgArrayRefExp* arrayRef = isSgArrayRefExp(iter->lhs());
                if (arrayRef != NULL)
                {
                    SgSymbol* origS = OriginalSymbol(arrayRef->symbol());
                    if (origS->identifier() == origName)
                    {
                        consistInAllocates++;
                        list = iter->lhs()->lhs();
                    }
                }
                iter = iter->rhs();
            }
        }

        if (consistInAllocates != 1)
            list = NULL;

        if (list == NULL)
            __spf_print(1, "find for %s, consistInAllocates = %d\n", arrayS->identifier(), consistInAllocates);
        checkNull(list, convertFileName(__FILE__).c_str(), __LINE__);
    }
    else
    {
        auto type = isSgArrayType(arrayS->type());
        list = type->getDimList();
    }

    vector<SgExpression*> bounds;
    while (list)
    {
        SgExpression* elem = list->lhs();
        if (elem->variant() == DDOT)
            bounds.push_back(elem->lhs());
        else
            bounds.push_back(oneExpr);
        list = list->rhs();
    }
    return bounds;
}

static vector<SgExpression*> getBoundsExpression(SgSymbol* arrayS)
{
    if (isAllocated(arrayS))
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    auto type = isSgArrayType(arrayS->type());
    auto list = type->getDimList();

    vector<SgExpression*> bounds;
    while (list)
    {
        SgExpression* elem = list->lhs();
        if (elem->variant() == DDOT)
            bounds.push_back(&(elem->rhs()->copy() -  elem->lhs()->copy() + *new SgValueExp(1)));
        else
            bounds.push_back(elem->copyPtr());
        list = list->rhs();
    }
    return bounds;
}

static SgArrayRefExp* addressPass(SgArrayRefExp* result, SgArrayRefExp* arrayExpOld, SgArrayRefExp* arrayExpNew,
                                  const vector<SgExpression*>& boundsOld, const vector<SgExpression*>& boundsNew,
                                  map<SgExpression*, string>& collection)
{
    //TODO: check dims position
    for (int z = 0; z < boundsOld.size(); ++z)
    {
        checkNull(boundsOld[z], convertFileName(__FILE__).c_str(), __LINE__);
        checkNull(boundsNew[z], convertFileName(__FILE__).c_str(), __LINE__);
        SgExpression* shift = NULL;
        SgExpression& baseShift = (boundsNew[z]->copy() - boundsOld[z]->copy());

        if (arrayExpNew->subscript(z) &&
            !isEqExpressions(arrayExpNew->subscript(z), boundsNew[z], collection)) 
        {
            shift = &(arrayExpNew->subscript(z)->copy() - boundsNew[z]->copy());
        }

        SgExpression& oldSub = arrayExpOld->subscript(z)->copy();
        if (isEqExpressions(boundsOld[z], boundsNew[z], collection))
            result->addSubscript(shift ? (oldSub + *shift) : oldSub);
        else
            result->addSubscript(shift ? (oldSub + baseShift + *shift) : (oldSub + baseShift));
    }
    return result;
}

static SgArrayRefExp* linearize(SgArrayRefExp* result, SgArrayRefExp* arrayExpOld, SgExpression* oldExp,
                                map<string, vector<pair<SgSymbol*, SgExpression*>>>& argVarsLinearForm,
                                const vector<SgExpression*>& boundsOld, const vector<SgExpression*>& boundsNew,
                                map<SgExpression*, string>& collection)
{
    const string arrayName = oldExp->symbol()->identifier();
    const int numOldSubs = arrayExpOld->numberOfSubscripts();

    auto itL = argVarsLinearForm.find(arrayName);
    if (itL == argVarsLinearForm.end())
    {
        vector<pair<SgSymbol*, SgExpression*>> newVars;
        int boundsN = 0;
        auto initDeclareBounds = getBoundsExpression(oldExp->symbol());
        for (int z = 0; z < boundsOld.size() - 1; ++z)
        {
            pair<SgSymbol*, SgExpression*> forDim;
            string dimName = "s_";
            int nextBoound = checkSymbNameAndCorrect(dimName, boundsN);
            dimName += to_string(nextBoound);

            forDim.first = new SgSymbol(VARIABLE_NAME, dimName.c_str());
            forDim.second = initDeclareBounds[z];
            newVars.push_back(forDim);
        }
        itL = argVarsLinearForm.insert(itL, make_pair(arrayName, newVars));
    }

    SgExpression* linearForm = NULL;
    if (numOldSubs != 0)
    {
        for (int z = 0; z < boundsOld.size(); ++z)
        {
            SgExpression* nextArg = &(arrayExpOld->subscript(z)->copy() - boundsOld[z]->copy());

            for (int p = 0; p < z; ++p)
                nextArg = &(*nextArg * *new SgVarRefExp(itL->second[p].first));

            if (linearForm)
                linearForm = &(*linearForm + *nextArg);
            else
                linearForm = nextArg;
        }

        int numS = result->numberOfSubscripts();
        if (!isEqExpressions(zeroExpr, boundsNew[0], collection) && numS == 0)
            linearForm = &(*linearForm + boundsNew[0]->copy());

        if (numS == 0)
            result->addSubscript(*linearForm);
        else if (numS == 1)
            result->replaceSubscripts(*result->subscript(0) + *linearForm);
        else // TODO
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }
    
    return result;
}

static SgExpression* doReplace(SgExpression* oldExp, SgExpression* newExp, map<SgExpression*, string>& collection, 
                               map<string, vector<pair<SgSymbol*, SgExpression*>>>& argVarsLinearForm, 
                               map<string, vector<Messages>>& messages,
                               const PointCall& point)
{
    if (oneExpr == NULL)
        oneExpr = new SgValueExp(1);

    if (zeroExpr == NULL)
        zeroExpr = new SgValueExp(0);

    SgExpression* retVal = NULL;
    if (oldExp)        
    {
        if (oldExp->variant() == ARRAY_REF && oldExp->symbol() && oldExp->symbol()->type()->variant() == T_ARRAY)
        {
            if (newExp->variant() != ARRAY_REF)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            SgArrayRefExp* arrayExpOld = isSgArrayRefExp(oldExp);
            SgArrayRefExp* arrayExpNew = isSgArrayRefExp(newExp);

            /*arrayExpOld->unparsestdout();
            arrayExpNew->unparsestdout();
            printf("\n");*/

            auto boundsOld = getLowBounds(oldExp->symbol());
            auto boundsNew = getLowBounds(newExp->symbol());
           
            /*for (auto& elem : boundsOld)
                elem->unparsestdout();
            printf("--\n");
            for (auto& elem : boundsNew)
                elem->unparsestdout();
            printf("--\n");*/

            const int numOldSubs = arrayExpOld->numberOfSubscripts();
            const int numNewSubs = arrayExpNew->numberOfSubscripts();
            if (numNewSubs == 0)
            {
                SgArrayRefExp* result = (SgArrayRefExp*)arrayExpNew->copyPtr();
                if (boundsOld.size() != boundsNew.size() && boundsNew.size() != 1 && boundsOld.size() != 1)
                {
                    std::wstring bufE, bufR;
                    __spf_printToLongBuf(bufE, L"Can not do replace argument of call '%s': sizes mismatch of array reference '%s'",
                                               to_wstring(point.getChainCall()).c_str(), to_wstring(oldExp->symbol()->identifier()).c_str());
                    __spf_printToLongBuf(bufR, R180, to_wstring(point.getChainCall()).c_str(), to_wstring(oldExp->symbol()->identifier()).c_str());
                    getObjectForFileFromMap(point.mainPoint.first.c_str(), messages).push_back(Messages(ERROR, point.mainPoint.second, bufR, bufE, 2014));

                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }

                if (boundsNew.size() == 1 && boundsOld.size() > 1)
                    result = linearize(result, arrayExpOld, oldExp, argVarsLinearForm, boundsOld, boundsNew, collection);
                else if (boundsNew.size() > 1 && boundsOld.size() == 1)
                {
                    std::wstring bufE, bufR;
                    __spf_printToLongBuf(bufE, L"Can not do replace argument of call '%s': sizes mismatch of array reference '%s'", 
                                               to_wstring(point.getChainCall()).c_str(), to_wstring(oldExp->symbol()->identifier()).c_str());
                    __spf_printToLongBuf(bufR, R180, to_wstring(point.getChainCall()).c_str(), to_wstring(oldExp->symbol()->identifier()).c_str());
                    getObjectForFileFromMap(point.mainPoint.first.c_str(), messages).push_back(Messages(ERROR, point.mainPoint.second, bufR, bufE, 2014));

                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }
                else if (numOldSubs != 0)
                    result = addressPass(result, arrayExpOld, arrayExpNew, boundsOld, boundsNew, collection);
                retVal = result;
            }
            else if (numNewSubs >= numOldSubs && numOldSubs != 0)
            {
                SgArrayRefExp* result = new SgArrayRefExp(*arrayExpNew->symbol());
                if (boundsOld.size() > boundsNew.size())
                {
                    std::wstring bufE, bufR;
                    __spf_printToLongBuf(bufE, L"Can not do replace argument of call '%s': sizes mismatch of array reference '%s'",
                                         to_wstring(point.getChainCall()).c_str(), to_wstring(oldExp->symbol()->identifier()).c_str());
                    __spf_printToLongBuf(bufR, R180, to_wstring(point.getChainCall()).c_str(), to_wstring(oldExp->symbol()->identifier()).c_str());
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }

                result = addressPass(result, arrayExpOld, arrayExpNew, boundsOld, boundsNew, collection);
                for (int z = 0; z < boundsNew.size() - boundsOld.size(); ++z)
                    result->addSubscript(arrayExpNew->subscript(boundsOld.size() + z)->copy());
                retVal = result;
            }
            else
            {
                if (boundsNew.size() == 1 && boundsOld.size() > 1)
                {
                    SgArrayRefExp* result = (SgArrayRefExp*)arrayExpNew->copyPtr();
                    retVal = linearize(result, arrayExpOld, oldExp, argVarsLinearForm, boundsOld, boundsNew, collection);
                }
                else
                {
                    retVal = newExp->copyPtr();
                    retVal->setLhs(oldExp->lhs());
                    retVal->setRhs(oldExp->rhs());
                }
            }
        }
        else
            retVal = newExp->copyPtr();
    }    
    if (retVal == NULL)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    return retVal;
}

//return 0 - local, 1 - common, 2 - module
static int isGlobal(SgStatement* st, SgSymbol* toCheckS, vector<SgSymbol*>* allInCommon = NULL, string* globalName = NULL)
{
    auto scope = toCheckS->scope();
    bool isFunctionS = (toCheckS->variant() == FUNCTION_NAME);

    if (toCheckS != OriginalSymbol(toCheckS) || (scope && scope->variant() == MODULE_STMT && !isFunctionS))
    {
        //TODO for module
        //globalName = 
        return 2;
    }

    const string toCheck = toCheckS->identifier();
    SgStatement* func = getFuncStat(st);
    map<string, vector<SgExpression*>> commonBlocksRef;
    getCommonBlocksRef(commonBlocksRef, func, func ? func->lastNodeOfStmt() : NULL);

    int isCommon = 0;
    for (auto& commonBlockRef : commonBlocksRef)
    {
        for (auto& commExp : commonBlockRef.second)
        {
            for (auto exp = commExp->lhs(); exp; exp = exp->rhs())
            {
                SgSymbol* varSymb = exp->lhs()->symbol();
                string varName = varSymb->identifier();

                if (toCheck == varName)
                {
                    if (globalName)
                        globalName[0] = commonBlockRef.first;
                    isCommon = 1;
                    break;
                }
            }
            if (isCommon == 1)
                break;
        }

        if (isCommon == 1 && allInCommon)
            for (auto& commExp : commonBlockRef.second)
                for (auto exp = commExp->lhs(); exp; exp = exp->rhs())
                    allInCommon[0].push_back(exp->lhs()->symbol());

        if (isCommon == 1)
            break;
    }
    return isCommon;
}

inline static void findAllConstRef(SgExpression* ex, set<SgSymbol*>& result)
{
    if (ex)
    {
        if (ex->variant() == CONST_REF)
        {
            result.insert(ex->symbol());
            auto constS = isSgConstantSymb(ex->symbol());
            if (!constS)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            auto val = constS->constantValue();
            if (!val)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            findAllConstRef(val, result);
        }

        findAllConstRef(ex->lhs(), result);
        findAllConstRef(ex->rhs(), result);
    }
}

//globalType: 0 - local, 1 - common, 2 - module
static void detectTypeOfSymbol(const string& funcName, SgSymbol* s, int globalType, set<SgSymbol*>& newSymbols, 
                               const vector<SgSymbol*>& allInCommon, const string& globalName)
{
    if (newSymbols.find(s) == newSymbols.end())
    {
        char* newAttr = new char[globalName.size() + 1];
        strcpy(newAttr, globalName.c_str());
        if (globalType == 1)
        {
            if (allInCommon.size() == 0)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            const pair<string, int> key = make_pair(funcName, current_file_id);
            int pos = 0;
            for (auto& commS : allInCommon)
            {
                auto attrsC = getAttributes<SgSymbol*, char*>(commS, set<int> { COMM_STAT });
                auto attrsPos = getAttributes<SgSymbol*, int*>(commS, set<int> { COMM_LIST });

                if (attrsC.size() == 0 && attrsPos.size() == 0 ||
                    attrsC.size() && attrsPos.size() && (attrsC[0] != string(newAttr) || *attrsPos[0] != pos))
                {
                    commS->addAttribute(COMM_STAT, newAttr, sizeof(char*));
                    int* posS = new int[1];
                    posS[0] = pos;
                    commS->addAttribute(COMM_LIST, posS, sizeof(int*));
                }

                if (isSgArrayType(commS->type()))
                {
                    set<SgSymbol*> constInDecl;
                    auto arrT = isSgArrayType(commS->type());
                    findAllConstRef(arrT->getDimList(), constInDecl);

                    for (auto& elem : constInDecl)
                    {
                        auto itS = replacedConstRef[key].find(elem->identifier());
                        if (itS == replacedConstRef[key].end())
                        {
                            replacedConstRef[key][elem->identifier()] = make_pair(elem, elem->identifier());
                            newSymbols.insert(elem);
                            checkSymbols(current_file_id, newSymbols);
                        }
                    }
                }
                newSymbols.insert(commS);
                checkSymbols(current_file_id, newSymbols);

                if (funcName != "null" && commS->attributes() & DATA_BIT)
                {
                    auto itS = createdByFunc[key].find(commS->identifier());
                    if (itS == createdByFunc[key].end())
                        createdByFunc[key][commS->identifier()] = commS;
                }
                ++pos;
            }
        }
        else if (globalType == 2)
            s->addAttribute(MODULE_STMT, newAttr, sizeof(char*));
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        if (globalType != 2)
            newSymbols.insert(s);

        checkSymbols(current_file_id, newSymbols);
    }
}

static void replaceSymbInExp(SgStatement* st, SgExpression* exp, SgExpression* par, const int expIdx, const bool isLeft,
                             const map<string, SgExpression*>& argVars, map<string, SgSymbol*>& locVars, set<SgSymbol*>& newSymbols,
                             const string& funcName, const string& resultName, SgSymbol*& newHedrSymb, map<SgExpression*, string>& collection,
                             map<string, vector<pair<SgSymbol*, SgExpression*>>>& argVarsLinearForm,
                             map<string, vector<Messages>>& messages, const PointCall& point)
{
    if (exp)
    {
        replaceSymbInExp(st, exp->lhs(), exp, expIdx, true, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, collection, argVarsLinearForm, messages, point);
        replaceSymbInExp(st, exp->rhs(), exp, expIdx, false, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, collection, argVarsLinearForm, messages, point);

        const int var = exp->variant();
        if (var == FUNC_CALL && exp->symbol())
            if (!isIntrinsicFunctionName(exp->symbol()->identifier()) || exp->symbol()->identifier() == string("dvtime"))
                newSymbols.insert(exp->symbol());

        if ((var == VAR_REF || var == ARRAY_REF || var == CONST_REF || var == IOACCESS) && exp->symbol())
        {
            const string varName = exp->symbol()->identifier();
            auto it = argVars.find(varName);
            if (it != argVars.end())
            {
                SgExpression* copy = NULL;
                if (!par)
                {
                    copy = doReplace(st->expr(expIdx), it->second, collection, argVarsLinearForm, messages, point);
                    st->setExpression(expIdx, copy);
                }
                else
                {
                    copy = doReplace((isLeft ? par->lhs() : par->rhs()), it->second, collection, argVarsLinearForm, messages, point);
                    isLeft ? par->setLhs(copy) : par->setRhs(copy);
                }
            }
            else
            {
                auto expS = exp->symbol();
                string globalName;
                vector<SgSymbol*> allInCommon;
                int globalType = isGlobal(st, expS, &allInCommon, &globalName);
                if (globalType == 0)
                {
                    auto it = locVars.find(varName);
                    if (it == locVars.end())
                    {
                        auto symb = createSymbAndDecl(funcName, varName, expS, newSymbols);
                        it = locVars.insert(it, make_pair(varName, symb));

                        if (varName == resultName && !newHedrSymb)
                            newHedrSymb = symb;

                        if (newHedrSymb != symb)
                        {
                            auto arrType = isSgArrayType(symb->type());
                            if (arrType)
                            {
                                auto allList = arrType->getDimList();
                                while (allList)
                                {
                                    auto list = allList;
                                    if (list && list->lhs())
                                        replaceSymbInExp(NULL, list->lhs(), list, -1, true, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, collection, argVarsLinearForm, messages, point);
                                    allList = allList->rhs();
                                }
                            }
                        }
                    }
                    exp->setSymbol(*it->second);
                }
                else
                    detectTypeOfSymbol(funcName, expS, globalType, newSymbols, allInCommon, globalName);
            }
        }
    }
}

inline static void replaceSymbInStat(SgStatement* st, SgStatement* tempHedr,
                                     const map<string, SgExpression*>& argVars, map<string, SgSymbol*>& locVars, set<SgSymbol*>& newSymbols,
                                     const string& funcName, const string& resultName, SgSymbol*& newHedrSymb, map<SgExpression*, string>& collection,
                                     map<string, vector<pair<SgSymbol*, SgExpression*>>>& argVarsLinearForm,
                                     map<string, vector<Messages>>& messages, const PointCall& point)
{
    if (st->variant() == SPF_PARALLEL_REG_DIR)
        return;

    if (st->symbol() && st->variant() != PROC_STAT)
    {
        auto s = st->symbol();

        const string varName = s->identifier();
        auto it = argVars.find(varName);
        if (it != argVars.end())
            st->setSymbol(*it->second->symbol());
        else
        {
            string globalName;
            vector<SgSymbol*> allInCommon;
            int globalType = isGlobal(st, s, &allInCommon, &globalName);
            if (globalType == 0)
            {
                auto it = locVars.find(varName);
                if (it == locVars.end())
                {
                    auto symb = createSymbAndDecl(funcName, varName, s, newSymbols);
                    it = locVars.insert(it, make_pair(varName, symb));

                    if (varName == resultName && !newHedrSymb)
                        newHedrSymb = symb;
                }
                st->setSymbol(*it->second);
            }
            else
                detectTypeOfSymbol(funcName, s, globalType, newSymbols, allInCommon, globalName);
        }
    }

    for (int i = 0; i < 3; ++i)
        replaceSymbInExp(st, st->expr(i), NULL, i, false, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, collection, argVarsLinearForm, messages, point);
}

static inline void remapVars(SgStatement* tempHedr,
                             map<string, SgExpression*> argVars, map<string, SgSymbol*>& locVars,
                             set<SgSymbol*>& newSymbols, const string& funcName, const string& resultName, 
                             SgSymbol*& newHedrSymb, map<string, vector<Messages>>& messages, const PointCall& point)
{    
    map<SgExpression*, string> collection;
    map<string, vector<pair<SgSymbol*, SgExpression*>>> argVarsLinearForm;

    for (auto st = tempHedr; st != tempHedr->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() == CONTAINS_STMT)
            break;

        if (st->variant() == ENTRY_STAT) // exclude same argVars
        {
            if (st->symbol())
            {
                SgExpression* parList = st->expr(0);
                for (SgExpression* p = parList; p; p = p->rhs())
                {
                    string parName = p->lhs()->symbol()->identifier();
                    if (argVars.find(parName) != argVars.end())
                        argVars.erase(parName);
                }
            }
            else
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }

        if (isSgExecutableStatement(st))
            replaceSymbInStat(st, tempHedr, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, collection, argVarsLinearForm, messages, point);
        else
        {
            if (st->variant() == DATA_DECL)
                dataDeclsByFunc[make_pair(funcName, current_file_id)].insert((SgValueExp*)st->expr(0));
            else if (st->variant() == VAR_DECL_90 || st->variant() == VAR_DECL)
            {
                SgExpression* list = st->expr(0);
                while (list)
                {
                    auto ex = list->lhs();
                    if (ex->variant() == ASSGN_OP)
                    {
                        SgExpression* tmp = new SgExprListExp();
                        map<string, vector<pair<SgSymbol*, SgExpression*>>> dummy;

                        auto copy = ex->rhs()->copyPtr();
                        tmp->setLhs(copy);
                        replaceSymbInExp(NULL, tmp->lhs(), tmp, -1, true, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, collection, dummy, messages, point);

                        dataDeclsByFuncS[make_pair(funcName, current_file_id)][OriginalSymbol(ex->lhs()->symbol())->identifier()] = copy;
                    }

                    list = list->rhs();
                }
            }
        }
    }

    if (argVarsLinearForm.size())
    {
        SgStatement* lastDecl = tempHedr;
        for (auto st = tempHedr; st != tempHedr->lastNodeOfStmt(); st = st->lexNext())
        {
            if (st->variant() == CONTAINS_STMT)
                break;

            auto next = st->lexNext();

            if (next && isSgExecutableStatement(next))
            {
                lastDecl = st;
                break;
            }
        }

        SgExpression* tmp = new SgExprListExp();
        map<string, vector<pair<SgSymbol*, SgExpression*>>> dummy;
        for (auto& bySymb : argVarsLinearForm)
        {
            for (auto& elem : bySymb.second)
            {
                newSymbols.insert(elem.first);
                checkSymbols(current_file_id, newSymbols);

                tmp->setLhs(elem.second);
                
                replaceSymbInExp(NULL, tmp->lhs(), tmp, -1, true, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, collection, dummy, messages, point);
                
                SgAssignStmt* init = new SgAssignStmt(*new SgVarRefExp(elem.first), *tmp->lhs());
                lastDecl->insertStmtAfter(*init, *tempHedr);
            }
        }
    }
}

static void addComment(SgStatement* st, bool isStart, string ident)
{
    string comments = "";
    if (st->comments())
    {
        comments = st->comments();
        if (isStart)
            comments += "![INLINING] start of '" + ident + "'\n";
        else
            comments = "![INLINING] end of '" + ident + "'\n" + comments;
    }
    else
        comments = (isStart ? "![INLINING] start of '" : "![INLINING] end of '") + ident + "'\n";
    st->setComments(comments.c_str());
}

static void insert(SgStatement* callSt, SgStatement* tempHedr, SgStatement* begin, SgSymbol* newHedrSymb, 
                   vector<SgStatement*>& toDelete, set<SgStatement*>& useStats)
{
    auto prev = callSt->lexPrev();
    auto last = tempHedr->lastNodeOfStmt();

    auto firstExec = begin->lexNext();
    auto lastExec = callSt->lexNext();
    while (lastExec && lastExec->variant() == GLOBAL) // bounds marker
        lastExec = lastExec->lexNext();
    
    vector<SgStatement*> formats;

    for (; firstExec && firstExec != tempHedr->lastNodeOfStmt(); firstExec = firstExec->lexNext())
    {
        if (firstExec->variant() == USE_STMT)
            useStats.insert(firstExec->copyPtr());
        
        if (isSgExecutableStatement(firstExec))
            break;

        if (firstExec->variant() == FORMAT_STAT)
            formats.push_back(firstExec->copyPtr());
    }
    checkNull(firstExec, convertFileName(__FILE__).c_str(), __LINE__);

    tempHedr->extractStmt();

    SgLabel* callLabel = callSt->label();

    prev->insertStmtAfter(*firstExec, *callSt->controlParent());

    auto next = prev->lexNext();
    if (callSt->comments())
    {
        string callCom(callSt->comments());
        if (next->comments()) {
            string newCom = callCom + next->comments();
            next->setComments(newCom.c_str());
        }
        else
            next->addComment(callCom.c_str());
        callSt->delComments();
    }

    auto ident = string(tempHedr->symbol()->identifier());
    addComment(next, true, ident);
    addComment(lastExec, false, ident);

    if (callLabel)
    {
        if (firstExec->label())
        {
            SgContinueStmt* contSt = new SgContinueStmt();
            contSt->setLabel(*callLabel);
            prev->insertStmtAfter(*contSt, *callSt->controlParent());
        }
        else
            firstExec->setLabel(*callLabel);

        if (callSt->variant() != PROC_STAT) // function calls
            callSt->deleteLabel(true);
    }

    for (auto &fmt : formats)
        prev->insertStmtAfter(*fmt, *callSt->controlParent());

    for (auto st = prev->lexNext(); st != callSt; st = st->lexNext())
    {
        if (st->lineNumber() > 0)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        st->setlineNumber(getNextNegativeLineNumber());
    }

    next = prev->lexNext();
    next->setlineNumber(callSt->lineNumber());
    next->setFileName(callSt->fileName());

    last->extractStmt();

    if (callSt->variant() == PROC_STAT)
        toDelete.push_back(callSt);    
    else
    {
        callSt->setExpression(1, new SgExpression(VAR_REF));
        callSt->expr(1)->setSymbol(newHedrSymb);
    }
}

static int clean(const string& funcName, SgStatement* funcSt, const map<string, FuncInfo*>& funcMap, vector<SgStatement*>& toDelete)
{
    // site-independent transformation
    int gotoLabelId = -1;
    SgLabel* contLab = NULL;
    SgStatement* cont = NULL;

    // return goto label if entry function is called
    auto it = funcMap.find(funcName);
    if (it != funcMap.end() && it->second->funcPointer->variant() == ENTRY_STAT)
        gotoLabelId = getNextFreeLabel();

    vector<SgStatement*> insertBeforeEnd;
    // TODO: move all ENTRY points to the top of the subprogram
    for (auto st = funcSt; st != funcSt->lastNodeOfStmt()->lexNext(); st = st->lexNext())
    {
        switch (st->variant())
        {
        case ENTRY_STAT:
        {
            SgLabel* gotoLab = new SgLabel(getNextFreeLabel());
            SgContinueStmt* contSt = new SgContinueStmt();
            contSt->setLabel(*gotoLab);
            st->insertStmtBefore(*contSt, *st->controlParent());

            // return goto label if entry function is called
            if (st->symbol()->identifier() == funcName)
                gotoLabelId = gotoLab->getLabNumber();

            toDelete.push_back(st);
            break;
        }
        case RETURN_STAT:
        {
            if (st->lexNext()->variant() == CONTROL_END && funcSt->lastNodeOfStmt() == st->lexNext())
            {
                if (st->label())
                {
                    SgStatement* cont = new SgStatement(CONT_STAT);
                    cont->setLabel(*st->label());
                    st->insertStmtBefore(*cont, *st->controlParent());
                }
                toDelete.push_back(st);
                break;
            }

            if (!cont) // create CONT_STAT
            {
                contLab = new SgLabel(getNextFreeLabel());
                cont = new SgStatement(CONT_STAT);
                cont->setLabel(*contLab);
                insertBeforeEnd.push_back(cont);
            }

            SgGotoStmt* gotoSt = new SgGotoStmt(*contLab);
            if (st->label())
                gotoSt->setLabel(*st->label());
            st->insertStmtBefore(*gotoSt, *st->controlParent());

            toDelete.push_back(st);
            break;
        }
        default:
            break;
        }
    }

    std::reverse(insertBeforeEnd.begin(), insertBeforeEnd.end());
    for (auto& beforeEnd : insertBeforeEnd)
        funcSt->lastNodeOfStmt()->insertStmtBefore(*beforeEnd, *funcSt);

    return gotoLabelId;
}

// true if inserted
static inline bool insert(SgStatement* callSt, SgStatement* funcStat, SgExpression* args, set<SgSymbol*>& newSymbols, 
                          const map<string, FuncInfo*>& funcMap, vector<SgStatement*>& toDelete, set<SgStatement*>& useStats,
                          map<string, vector<Messages>>& messages, const PointCall& point)
{
    SgSymbol* funcSymb = funcStat->symbol();
    bool isEntry = false;
    SgSymbol* entrySymb = NULL;

    SgStatement* tempHedr = NULL;
    SgSymbol* tempSymb = NULL;

    __spf_print(DEB, "------creating template------\n");
    // 2.a create function template
    auto funcSt = funcSymb->body();
    if (funcSt->variant() == ENTRY_STAT)
    {
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        //TODO:
        isEntry = true;
        entrySymb = funcSymb;

        while (funcSt && funcSt->variant() != FUNC_HEDR)
            funcSt = funcSt->lexPrev();
        checkNull(funcSt, convertFileName(__FILE__).c_str(), __LINE__);
        funcSymb = funcSt->symbol();
    }

    // insert template after global statement in current file
    tempHedr = duplicateProcedure(funcStat, NULL, true, false, false);
    tempSymb = tempHedr->symbol();

    if (string(tempSymb->identifier()) != funcSymb->identifier())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    // set first inserting statement
    SgStatement* begin = NULL;
    if (isEntry)
    {
        for (auto st = tempHedr; st != tempHedr->lastNodeOfStmt(); st = st->lexNext())
            if (st->variant() == ENTRY_STAT && st->symbol()->identifier() == string(entrySymb->identifier()))
                begin = st;
    }
    else
        begin = tempHedr;
    checkNull(begin, convertFileName(__FILE__).c_str(), __LINE__);

    // create map of arguments
    SgSymbol* newHedrSymb = NULL; // requires for function with return variable
    map<string, SgExpression*> argVars = createMapOfArgs(tempHedr, args); // local argument variable -> top argument variable    
    map<string, SgSymbol*> locVars; // local varname -> new local var

    const string funcName = begin->symbol()->identifier();
    string resultName = funcName;
    if (isSgFuncHedrStmt(begin))
        if (begin->expr(0) && begin->expr(0)->symbol())
            resultName = begin->expr(0)->symbol()->identifier();

    remapVars(tempHedr, argVars, locVars, newSymbols, funcName, resultName, newHedrSymb, messages, point);

    if (!newHedrSymb && tempSymb->variant() == FUNCTION_NAME) // if no textual use of return variable, create new anyway
        newHedrSymb = createSymbAndDecl("null", string(tempSymb->identifier()) + "_spf", tempSymb, newSymbols);
    
    if (newHedrSymb)
    {
        //set clear name for module symbols
        const string clearName = getClearName(newHedrSymb->identifier());
        if (clearName != newHedrSymb->identifier())
            newHedrSymb->changeName(clearName.c_str());
    }

    // clean function
    int gotoLabId = clean(funcSymb->identifier(), tempHedr, funcMap, toDelete);
    if (gotoLabId != -1)
    {
        SgLabel* gotoLab = new SgLabel(gotoLabId);
        SgGotoStmt* gotoSt = new SgGotoStmt(*gotoLab);
        callSt->insertStmtBefore(*gotoSt, *callSt->controlParent());
    }

    // insert template
    insert(callSt, tempHedr, begin, newHedrSymb, toDelete, useStats);
    return true;
}

static inline void replaceCall(SgExpression* exp, SgExpression* par, const int i, const bool lhs,
                               SgStatement* callSt, set<SgSymbol*>& newSymbols, SgStatement* insertPlace)
{
    // create new call variable for this call and its declaration
    SgSymbol* v = createSymbAndDecl("null", "arg", exp->symbol(), newSymbols);

    SgAssignStmt* assign = new SgAssignStmt(*new SgVarRefExp(*v), *exp->copyPtr());
    assign->setlineNumber(getNextNegativeLineNumber());
    assign->setLocalLineNumber(callSt->lineNumber());

    insertPlace->insertStmtBefore(*assign, *callSt->controlParent());

    // replace function call to a new variable
    if (!par)
        callSt->setExpression(i, new SgVarRefExp(*v));
    else
        lhs ? par->setLhs(new SgVarRefExp(*v)) : par->setRhs(new SgVarRefExp(*v));
}

static void recFindFuncCall(FuncInfo* currentFuncI, 
                            SgExpression* exp, SgExpression* par, const int i, const bool lhs,
                            const string& funcName, bool& foundCall,
                            SgStatement* callSt, set<SgSymbol*>& newSymbols, SgStatement* insertPlace)
{
    if (exp)
    {
        recFindFuncCall(currentFuncI, exp->rhs(), exp, i, false, funcName, foundCall, callSt, newSymbols, insertPlace);
        recFindFuncCall(currentFuncI, exp->lhs(), exp, i, true, funcName, foundCall, callSt, newSymbols, insertPlace);

        if (exp->variant() == FUNC_CALL && exp->symbol() && 
            currentFuncI->getCallName(make_pair(exp, exp->variant()), exp->symbol()->identifier(), callSt->lineNumber()) == funcName)
        {
            foundCall = true;
            if (par) // do not extract external func call
                replaceCall(exp, par, i, lhs, callSt, newSymbols, insertPlace);
        }
    }
}

static int ParameterType(SgExpression* exp, SgStatement* st)
{
    if (isSgVarRefExp(exp) ||                                    // scalar variable
        isSgArrayRefExp(exp) ||                                  // array variable
        exp->variant() == CONST_REF ||                           // symbol (named) constant
        isSgValueExp(exp))// && exp->type()->variant() != T_STRING) // literal constant
        return 1;
    return 0;
}

static inline void PrecalculateExpression(SgSymbol* sp, SgExpression* e, SgStatement* stmt)
{
    SgStatement* newst;
    newst = new SgAssignStmt(*new SgVarRefExp(sp), *e);
    newst->setlineNumber(getNextNegativeLineNumber());
    stmt->insertStmtBefore(*newst, *stmt->controlParent());
}

static SgType* SgTypeFromFile(SgFile* f, int type)
{
    SgType* t;
    for (t = f->firstType(); t; t = t->next())
        if (t->variant() == type)
            return t;

    return new SgType(type);
}

//TODO: more types
static SgType* getTrueType(SgType* inExp, parF funcParType)
{
    if (funcParType == SCALAR_FLOAT_T)
        return SgTypeFloat();
    else if (funcParType == SCALAR_DOUBLE_T)
        return SgTypeDouble();
    else if (funcParType == SCALAR_CMPLX_FLOAT_T)
        return SgTypeFromFile(current_file, T_COMPLEX);
    else if (funcParType == SCALAR_CMPLX_DOUBLE_T)
        return SgTypeFromFile(current_file, T_DCOMPLEX);
    else
        return inExp;
}

static inline void PrecalculateActualParameters(SgStatement* st, SgExpression* e, 
                                                const FuncInfo* func, set<SgSymbol*>& newSymbols)
{
    // Precalculate actual parameter expressions
    // e - actual parameter list
    SgExpression* el;
    SgSymbol* sp;
    if (!e) 
        return;

//TODO: added exclude list!
    //if (is_NoExpansionFunction(s)) 
        //return;

    int i = 0;
    for (el = e; el; el = el->rhs(), i++)
    {
        switch (ParameterType(el->lhs(), st))
        {
        case 1:
            break; // actual parameter can be accessed by reference
        //case 2:  PrecalculateSubscripts(el->lhs(),stmt); break;
        default:            
            sp = createSymbAndDecl("null", "arg", NULL, newSymbols, getTrueType(el->lhs()->type(), func->funcParams.parametersT[i]));
            PrecalculateExpression(sp, el->lhs(), st); //to support access by reference 
            el->setLhs(new SgVarRefExp(sp)); //replace actual parameter expression by 'sp' reference
            break;
        }
    }
}

static void renameArgs(SgExpression* ex, const map<string, string>& remapArgs)
{
    if (ex)
    {
        if (ex->variant() == VAR_REF || ex->variant() == ARRAY_REF)
        {
            auto s = ex->symbol();
            if (s)
            {
                auto it = remapArgs.find(s->identifier());
                if (it != remapArgs.end())
                    ex->setSymbol(findSymbolOrCreate(current_file, it->second, s->type()));
            }
        }

        renameArgs(ex->lhs(), remapArgs);
        renameArgs(ex->rhs(), remapArgs);
    }
}

static void renameArgsIfGlobalNameIntersection(FuncInfo* func, const set<string>& globalNames)
{
    if (func->isMain)
        return;

    map<string, string> remapArgs;
    for (auto& par : func->funcParams.identificators)
    {
        if (globalNames.find(par) != globalNames.end())
        {
            int tmp = 0;
            string newName = "";
            do {
                newName = par + to_string(tmp++);
            } while (globalNames.find(newName) != globalNames.end());
            remapArgs[par] = newName;
            par = newName;
        }
    }

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

    auto header = func->funcPointer->GetOriginal();
    auto last = header->lastNodeOfStmt();
    for (SgStatement* st = header->lexNext(); st != last; st = st->lexNext())
        for (int z = 0; z < 3; ++z)
            renameArgs(st->expr(z), remapArgs);

    auto prog = isSgProcHedrStmt(header);

    PTR_SYMB listP = SYMB_FUNC_PARAM(BIF_SYMB(prog->thebif));
    vector<PTR_SYMB> newElems;
    for (int p = 0; p < prog->numberOfParameters(); ++p)
    {
        SgSymbol* par = prog->parameter(p);
        auto it = remapArgs.find(par->identifier());

        if (it != remapArgs.end())
        {
            SgSymbol* replace = par->copyPtr();
            replace->changeName(it->second.c_str());
            newElems.push_back(replace->thesymb);
        }
        else
            newElems.push_back(duplicateSymbol(listP));
        listP = SYMB_NEXT_DECL(listP);
    }

    listP = newElems[0];
    PTR_SYMB p = listP;
    for (int z = 1; z < newElems.size(); ++z)
    {
        SYMB_NEXT_DECL(p) = newElems[z];
        p = SYMB_NEXT_DECL(p);
    }
    SYMB_FUNC_PARAM(BIF_SYMB(prog->thebif)) = listP;
}

static void findUsed(SgExpression* ex, set<string>& used)
{
    if (ex)
    {
        if (ex->variant() == VAR_REF)
        {
            auto s = ex->symbol();
            if (s)
                used.insert(s->identifier());
        }

        findUsed(ex->lhs(), used);
        findUsed(ex->rhs(), used);
    }
}

static map<SgStatement*, set<string>> usedByFunc;

static bool run_inliner(const map<string, FuncInfo*>& funcMap, set<SgStatement*>& toInsert, map<string, vector<Messages>>& SPF_messages,
                        const string& fileName, const FuncInfo* func, map<SgStatement*, set<SgSymbol*>>& newSymbsToDeclare, 
                        const PointCall& point,
                        const map<string, CommonBlock*>& commonBlocks)
{
    bool isInlined = false;
    const string& funcName = func->funcName;

    set<string> globalNames;
    for (auto& common : commonBlocks)
        for (auto& var : common.second->getVariables())
            globalNames.insert(var->getName());

    for (auto& callSt : toInsert)
    {
        SgStatement* insertPlace = callSt;
        SgProgHedrStmt* currentFunc = (SgProgHedrStmt*) getFuncStat(callSt);

        if (usedByFunc.find(currentFunc) == usedByFunc.end())
        {
            set<string> used;
            for (auto st = currentFunc->lexNext(); st != currentFunc->lastNodeOfStmt(); st = st->lexNext())
            {
                if (isSgExecutableStatement(st))
                    for (int z = 0; z < 3; ++z)
                        findUsed(st->expr(z), used);

                if (st->variant() == CONTAINS_STMT)
                    break;
            }
            usedByFunc[currentFunc] = used;
        }

        auto itF = funcMap.find(currentFunc->nameWithContains());
        if (itF == funcMap.end())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        FuncInfo* currentFuncI = itF->second;
        renameArgsIfGlobalNameIntersection(currentFuncI, globalNames);

        set<string> useStatsInFunc;
        for (SgStatement* s = currentFunc; s != currentFunc->lastNodeOfStmt(); s = s->lexNext())
        {
            if (s->variant() == CONTAINS_STMT)
                break;
            if (s->variant() == USE_STMT)
                useStatsInFunc.insert(s->unparse());
        }

        SgStatement* funcStat = func->funcPointer->GetOriginal();
        if (funcStat->fileName() != fileName)
        {
            //get from shadow copies
            auto itF = hiddenData.find(fileName);
            if (itF == hiddenData.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            const string fromFile = funcStat->fileName();
            const int line = funcStat->lineNumber();

            bool done = false;
            for (auto& shadowFunc : itF->second)
            {
                if (shadowFunc->fileName() == fromFile && shadowFunc->lineNumber() == line)
                {
                    funcStat = shadowFunc;
                    done = true;
                    break;
                }
            }

            if (!done)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }

        set<SgSymbol*> newSymbols;
        bool foundCall = false;

        if (!callSt || !isSgExecutableStatement(callSt))
        {
            wstring messageE, messageR;
            __spf_printToLongBuf(messageE, L"It is allowed to inline function only at execution code section.");
            __spf_printToLongBuf(messageR, R177);

            getObjectForFileFromMap(fileName.c_str(), SPF_messages).push_back(Messages(ERROR, insertPlace->lineNumber(), messageR, messageE, 2011));
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }

        __spf_print(DEB, "------start inliner-----\n");        
        __spf_print(DEB, "---statement preprocessing---\n");
        //simple convertation
        if (callSt->controlParent()->variant() == LOGIF_NODE)
            LogIftoIfThen(callSt->controlParent());

        SgStatement* begin = callSt->lexPrev();
        SgStatement* end = callSt->lexNext();

        // 1.a: make statement preprocessing
        // if call statement contains several inlining functions, split every such call
        for (int i = 0; i < 3; ++i)
            recFindFuncCall(currentFuncI, callSt->expr(i), NULL, i, false, funcName, foundCall, callSt, newSymbols, insertPlace);

        __spf_print(DEB, "---argument preprocessing---\n");
        // 1.b: make argument preprocessing        
        checkNull(begin, convertFileName(__FILE__).c_str(), __LINE__);
        // if call statement has any expression as its artument, split this expression for separate statement
        // if call statement has any function as its argument, split this call
        for (auto st = begin->lexNext(); st != end; st = st->lexNext())
        {
            if (st->variant() == ASSIGN_STAT)
            {
                auto rPart = st->expr(1);
                int line = st->lineNumber() < 0 ? st->localLineNumber() : st->lineNumber();
                if (rPart->variant() == FUNC_CALL && rPart->symbol() &&
                    currentFuncI->getCallName(make_pair(rPart, rPart->variant()), rPart->symbol()->identifier(), line) == funcName)
                {
                    if (isSgVarRefExp(st->expr(0)) || isSgArrayRefExp(st->expr(0)) && !isSgArrayType(st->expr(0)->type()))
                        PrecalculateActualParameters(st, st->expr(1)->lhs(), func, newSymbols);
                }
            }
            else if (st->variant() == PROC_STAT)
            {
                if (st->symbol() &&
                    currentFuncI->getCallName(make_pair(st, st->variant()), st->symbol()->identifier(), st->lineNumber()) == funcName)
                {
                    foundCall = true;
                    if (st->expr(0))
                        PrecalculateActualParameters(st, st->expr(0), func, newSymbols);
                }
            }
        }

        set<SgStatement*> useStats;
        __spf_print(DEB, "---start inlining---\n");
        // 2. create function template to modify and insert it
        if (foundCall)
        {
            bool change = true;

            while (change)
            {
                change = false;
                vector<SgStatement*> toDelete;
                for (auto st = begin->lexNext(); st != end; st = st->lexNext())
                {
                    switch (st->variant())
                    {
                    case ASSIGN_STAT:
                        {
                            auto rPart = st->expr(1);
                            int line = st->lineNumber() < 0 ? st->localLineNumber() : st->lineNumber();
                            if (rPart->variant() == FUNC_CALL && rPart->symbol() &&
                                currentFuncI->getCallName(make_pair(rPart, rPart->variant()), rPart->symbol()->identifier(), line) == funcName)
                            {
                                bool doInline = insert(st, funcStat, rPart->lhs(), newSymbols, funcMap, toDelete, useStats, SPF_messages, point);
                                change |= doInline;
                                isInlined |= doInline;
                            }
                        }
                        break;
                    case PROC_STAT:
                        if (st->symbol() && 
                            currentFuncI->getCallName(make_pair(st, st->variant()), st->symbol()->identifier(), st->lineNumber()) == funcName)
                        {
                            bool doInline = insert(st, funcStat, st->expr(0), newSymbols, funcMap, toDelete, useStats, SPF_messages, point);
                            change |= doInline;
                            isInlined |= doInline;
                        }
                        break;
                    default:
                        break;
                    }
                }

                for (auto& st : toDelete)
                    st->extractStmt();
            }
        }
        
        for (auto& use : useStats)
        {
            string currUse = use->unparse();
            if (useStatsInFunc.find(currUse) == useStatsInFunc.end())
            {
                useStatsInFunc.insert(currUse);
                currentFunc->insertStmtAfter(*use, *currentFunc);
            }
        }

        checkSymbols(currentFunc->getFileId(), newSymbols);
        for (auto& symb : newSymbols)
        {
            if (symb->variant() == CONSTRUCT_NAME)
                continue;
            newSymbsToDeclare[currentFunc].insert(symb);
        }
    }

    return isInlined;
}

static vector<pair<SgStatement*, SgStatement*>> getNextBounds(SgStatement* point)
{
    vector<pair<SgStatement*, SgStatement*>> parts;
    pair<SgStatement*, SgStatement*> newPart;
    bool sectionStarted = false;

    for (auto st = point; st != point->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() == CONTAINS_STMT)
            break;

        if (st->variant() == GLOBAL)
        {
            if (!sectionStarted)
            {
                newPart.first = st;
                sectionStarted = true;
            }
            else if (sectionStarted)
            {
                newPart.second = st;
                parts.push_back(newPart);
                sectionStarted = false;
            }
        }
    }
    if (sectionStarted)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    return parts;
}

static bool dontInlineByAttribute(SgStatement* st)
{
    bool dontInline = false;
    for (int k = 0; k < st->numberOfAttributes(); ++k)
        if (st->getAttribute(k)->getAttributeType() == BOOL_VAL)
            dontInline = true;

    if (dontInline)
        __spf_print(1, " skip call in statement with line %d by attribute\n", st->lineNumber());
    return dontInline;
}

static void fillNewFunctions(SgExpression* ex, SgStatement* main,
                             map<FuncInfo*, set<SgStatement*>>& nextInfo,
                             const map<string, FuncInfo*>& funcMap)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
            if (funcMap.find(ex->symbol()->identifier()) != funcMap.end())
                if (!dontInlineByAttribute(main))
                    nextInfo[funcMap.at(ex->symbol()->identifier())].insert(main);

        fillNewFunctions(ex->lhs(), main, nextInfo, funcMap);
        fillNewFunctions(ex->rhs(), main, nextInfo, funcMap);
    }
}

static void fillNewFunctions(SgStatement* begin, SgStatement* end,
                             map<FuncInfo*, set<SgStatement*>>& nextInfo, 
                             const map<string, FuncInfo*>& funcMap)
{
    for (auto st = begin; st != end; st = st->lexNext())
    {
        if (st->variant() == PROC_STAT)
            if (funcMap.find(st->symbol()->identifier()) != funcMap.end())
                if (!dontInlineByAttribute(st))
                    nextInfo[funcMap.at(st->symbol()->identifier())].insert(st);

        for (int z = 0; z < 3; ++z)
            fillNewFunctions(st->expr(z), st, nextInfo, funcMap);
    }
}

static map<FuncInfo*, set<SgStatement*>> fillNextDeep(const set<SgStatement*>& insertedIn, const map<string, FuncInfo*>& funcMap)
{
    map<FuncInfo*, set<SgStatement*>> nextInfo;

    for (auto& point : insertedIn)
        for (auto& part : getNextBounds(point))
            fillNewFunctions(part.first, part.second, nextInfo, funcMap);

    return nextInfo;
}

static void addMessage(bool status, map<string, vector<Messages>>& SPF_messages, const string& fileName, const int line)
{
    if (status == true)
        return;

    wstring messageE, messageR;
    __spf_printToLongBuf(messageE, L"Function inlining failed with an error.");
    __spf_printToLongBuf(messageR, R193);

    getObjectForFileFromMap(fileName.c_str(), SPF_messages).push_back(Messages(ERROR, line, messageR, messageE, 2020));
}

static bool inliner(const string& fileName_in, const string& funcName, const int lineNumber,
                    const map<string, vector<FuncInfo*>>& allFuncInfo, map<string, vector<Messages>>& SPF_messages, 
                    map<SgStatement*, set<SgSymbol*>>& newSymbsToDeclare, const map<string, CommonBlock*>& commonBlocks,
                    int deepLvl = 0)
{
    map<string, FuncInfo*> funcMap;
    createMapOfFunc(allFuncInfo, funcMap);

    auto func = getFuncInfo(funcMap, funcName);
    const string& fileName = (fileName_in == "") ? func->fileName : fileName_in;

    if (func && !func->doNotInline)
    {
        if (SgFile::switchToFile(fileName) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        //TODO:
        // now function is expected to be declared in the target file
        set<SgStatement*> toInsert;
        if (lineNumber > 0)
        {
            SgStatement* callSt = SgStatement::getStatementByFileAndLine(fileName, lineNumber);
            if (callSt)
                toInsert.insert(callSt);
        }
        else
        {
            for (auto& callTo : func->callsTo)
            {
                for (auto& callInfo : callTo->callsFromDetailed)
                {
                    auto& callFrom = callInfo.detailCallsFrom;
                    if (callFrom.first == funcName)
                    {
                        SgStatement* callSt = SgStatement::getStatementByFileAndLine(fileName, callFrom.second);
                        if (callSt)
                            toInsert.insert(callSt);
                    }
                }
            }
        }

        set<SgStatement*> insertedIn;
        set<SgStatement*> markers;
        for (auto& elem : toInsert)
        {
            bool dontInline = dontInlineByAttribute(elem);
            
            if (dontInline)
                continue;

            insertedIn.insert(getFuncStat(elem));
            //insert bounds
            auto cp = elem->controlParent();
            auto boundAfter = new SgStatement(GLOBAL);
            auto boundBefore = new SgStatement(GLOBAL);

            elem->insertStmtBefore(*boundAfter, *cp);
            elem->insertStmtAfter(*boundBefore, *cp);

            markers.insert(boundAfter);
            markers.insert(boundBefore);
        }

        if (markers.size() == 0)
            return false;

        PointCall point;
        point.mainPoint.first = fileName;
        point.mainPoint.second = lineNumber;

        point.func = func;
        point.currLvl = 0;
        point.currCall = func->funcName;

        __spf_print(1, "  INLINE %s - ", func->funcName.c_str());
        sendMessage_2lvl(6, func->funcName);

        //1 level
        bool isInlined = run_inliner(funcMap, toInsert, SPF_messages, fileName, func, newSymbsToDeclare, point, commonBlocks);
        __spf_print(1, "%s\n", isInlined ? "done" : "fault");
        addMessage(isInlined, SPF_messages, fileName, lineNumber);

        if (isInlined == false)
        {
            __spf_print(1, "    missing ...\n");
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }

        if (deepLvl >= 0 && isInlined)
        {
            int currDeep = 0;
            bool changed = true;
            while (changed)
            {
                changed = false;
                currDeep++;
                if (deepLvl > 0 && deepLvl <= currDeep)
                    break;

                for (auto& next : fillNextDeep(insertedIn, funcMap))
                {
                    for (int p = 0; p < currDeep; ++p)
                        __spf_print(1, "  ");

                    if (next.first->doNotInline)
                    {
                        __spf_print(1, "skip %s by flag\n", next.first->funcName.c_str());
                        continue;
                    }

                    point.currLvl = currDeep;
                    point.currCall = next.first->funcName;
                    __spf_print(1, "  INLINE %s - ", next.first->funcName.c_str());                    
                    bool isInlined = run_inliner(funcMap, next.second, SPF_messages, fileName, next.first, newSymbsToDeclare, point, commonBlocks);
                    __spf_print(1, "%s\n", isInlined ? "done" : "fault");
                    addMessage(isInlined, SPF_messages, fileName, lineNumber);

                    changed |= isInlined;
                }
            }
        }

        for (auto& marker : markers)
            marker->extractStmt();
    }
    return true;
}

static bool inliner(const string& allInFunc, const map<string, vector<FuncInfo*>>& allFuncInfo, 
                    map<string, vector<Messages>>& SPF_messages,
                    map<SgStatement*, set<SgSymbol*>>& newSymbsToDeclare, const map<string, CommonBlock*>& commonBlocks,
                    int deepLvl = 0)
{
    bool result = true;
    map<string, FuncInfo*> funcMap;
    createMapOfFunc(allFuncInfo, funcMap);

    auto func = getFuncInfo(funcMap, allInFunc);
    if (func && !func->doNotInline)
    {
        if (SgFile::switchToFile(func->fileName) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto& callInfo : func->callsFromDetailed)
        {
            auto& callFrom = callInfo.detailCallsFrom;
            bool res = inliner(func->fileName, callFrom.first, callFrom.second, allFuncInfo, SPF_messages, newSymbsToDeclare, commonBlocks, deepLvl);
            result = result && res;
        }
    }
    return result;
}

static vector<SgSymbol*> sortConstRefs(const map<string, SgSymbol*>& constRefs)
{
    map<SgSymbol*, set<string>> deps;
    map<SgSymbol*, pair<string, int>> constRefByFunc; // value is <funcName, fileId>
    for (auto& byF : replacedConstRef)
        for (auto& s : byF.second)
            constRefByFunc[s.second.first] = byF.first;

    bool printInternal = false;
    for (auto& elem : constRefs)
    {
        SgSymbol* s = elem.second;

        if (constRefByFunc.find(s) == constRefByFunc.end())
        {
            __spf_print(1, " const not found '%s' with id %d\n", s->identifier(), s->id());
            printInternal = true;
            continue;
        }
        auto& inFunc = constRefByFunc[s];
        map<string, pair<SgSymbol*, string>>& byFuncRefpl = replacedConstRef[inFunc];

        deps[s] = set<string>();
        set<SgSymbol*> tmp;
        findAllConstRef(isSgConstantSymb(elem.second)->constantValue(), tmp);
        for (auto& ref : tmp)
        {
            auto itS = byFuncRefpl.find(ref->identifier());
            if (itS != byFuncRefpl.end())
            {
                auto newName = itS->second.second.c_str();
                ref->changeName(newName);
                deps[elem.second].insert(newName);
            }
            else
                deps[s].insert(ref->identifier());
        }
    }

    if (printInternal)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    set<string> added;
    vector<SgSymbol*> result;
    for (auto& elem : deps)
    {
        bool existInRef = false;
        for (auto& dep : elem.second)
            if (constRefs.find(dep) != constRefs.end())
                existInRef = true;

        if (!existInRef)
        {
            result.push_back(elem.first);
            added.insert(elem.first->identifier());
        }
    }

    while (result.size() != constRefs.size())
    {
        for (auto& elem : deps)
        {
            if (added.find(elem.first->identifier()) != added.end())
                continue;

            bool needToAdd = true;
            for (auto& dep : elem.second)
                if (added.find(dep) == added.end() && (dep != elem.first->identifier()))
                    needToAdd = false;
            if (needToAdd)
            {
                result.push_back(elem.first);
                added.insert(elem.first->identifier());
            }
        }
    }
    return result;
}

static pair<string, int> getCommonInfo(SgSymbol* toDec)
{
    pair<string, int> info = make_pair("NULL", -1);

    auto attrsC = getAttributes<SgSymbol*, char*>(toDec, set<int> { COMM_STAT });
    if (attrsC.size() && attrsC.size() != 1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);    

    if (attrsC.size())
    {        
        auto attrsPos = getAttributes<SgSymbol*, int*>(toDec, set<int> { COMM_LIST });
        if (attrsPos.size() && attrsPos.size() != 1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        info.first = attrsC[0];
        info.second = *attrsPos[0];
    }

    return info;
}

static bool addNewCommonDecl(SgSymbol* toDec, const int posNum, const string& commName,
                             map<string, vector<SgExpression*>>& commonBlocksRef, 
                             const map<string, CommonBlock*>& commonBlocks,
                             map<string, map<int, SgSymbol*>>& commons, 
                             const string& needToRename)
{
    if (posNum != -1)
    {
        if (needToRename != "")
            toDec->changeName(needToRename.c_str());

        auto itCommRef = commonBlocksRef.find(commName);
        if (itCommRef == commonBlocksRef.end())
            commons[commName][posNum] = toDec; // sort by position
        else
        {
            auto it = commonBlocks.find(commName);
            if (it == commonBlocks.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            auto groupedByPos = it->second->getGroupedVars();
            auto itG = groupedByPos.find(posNum);

            if (itG == groupedByPos.end() || itG != groupedByPos.end() && itG->second.size() == 1)
                commons[commName][posNum] = toDec; // sort by position
            else
            {
                int idx = 0;
                SgExpression* ex = NULL;
                for (auto& elem : itCommRef->second)
                {
                    for (auto list = elem->lhs(); list && !ex; list = list->rhs(), ++idx)
                        if (idx == posNum)
                            ex = list->lhs();

                    if (ex)
                        break;
                }

                if (ex == NULL)
                    commons[commName][posNum] = toDec; // sort by position
                else
                {
                    if (ex->symbol() == NULL)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    if (ex->symbol()->identifier() != string(toDec->identifier()))
                    {
                        toDec->changeName(ex->symbol()->identifier());
                        return true;
                    }
                    else
                        commons[commName][posNum] = toDec; // sort by position
                }
            }

        }
    }
    return false;
}

static void createDeclarations(const map<SgStatement*, set<SgSymbol*>>& newSymbsToDeclare, const map<string, CommonBlock*>& commonBlocks)
{
    map<pair<string, int>, map<string, string>> preprocDataByFunc; // key is <funcName, int>
    map<pair<string, int>, map<string, SgExpression*>> initValue;

    for (auto& dataByF : dataDeclsByFunc)
        preprocDataByFunc[dataByF.first] = splitData(dataByF.second);

    for (auto& dataByF : dataDeclsByFuncS)
    {
        initValue[dataByF.first] = dataByF.second;
        for (auto& elem : dataByF.second)
            preprocDataByFunc[dataByF.first][elem.first] = "---";
    }

    map<SgSymbol*, pair<pair<string, int>, string>> originalInfo; // value is <<funcName, int>, sName>
    for (auto& elem : createdByFunc)
        for (auto& s : elem.second)
            originalInfo[s.second] = make_pair(elem.first, s.first);

    for (auto& byFunc : newSymbsToDeclare)
    {
        if (!byFunc.first->switchToFile())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        __spf_print(1, "symbols checking for function '%s'\n", byFunc.first->symbol()->identifier());
        int currFileId = byFunc.first->getFileId();
        checkSymbols(currFileId, byFunc.second);

        map<string, vector<SgExpression*>> commonBlocksRef;
        getCommonBlocksRef(commonBlocksRef, byFunc.first, byFunc.first ? byFunc.first->lastNodeOfStmt() : NULL);

        map<string, map<string, vector<SgSymbol*>>> groups;
        map<string, map<string, vector<SgSymbol*>>> groupsConstRefs;
        map<string, map<int, SgSymbol*>> commons;
        map<string, SgSymbol*> constRefs;
        map<string, SgSymbol*> saveRefs;
        map<SgSymbol*, string> declRefs;
        map<SgSymbol*, SgExpression*> declInitInfo;
        set<SgSymbol*> allocatable;
        map<string, int> commonRenames;

        SgStatement* place = byFunc.first;
        while (isSgProgHedrStmt(place) == NULL && place->variant() != MODULE_STMT)
            place = place->controlParent();
        SgStatement* scope = place;

        set<string> declared, undeclared;
        map<string, set<string>> commonsInCurrFunc;
        while (isSgExecutableStatement(place) == NULL && place != scope->lastNodeOfStmt())
        {
            if (place->variant() == VAR_DECL ||
                place->variant() == VAR_DECL_90 ||
                place->variant() == DIM_STAT)
            {
                for (auto list = place->expr(0); list; list = list->rhs())
                {
                    auto elem = list->lhs();
                    if (elem && elem->symbol())
                        declared.insert(elem->symbol()->identifier());
                }
            }
            else if (place->variant() == COMM_STAT)
            {
                for (SgExpression* exp = place->expr(0); exp; exp = exp->rhs())
                {
                    string commonName = "spf_unnamed";
                    if (exp->symbol())
                        commonName = string(exp->symbol()->identifier());
                    
                    for (auto list = exp->lhs(); list; list = list->rhs())
                    {
                        auto elem = list->lhs();
                        if (elem && elem->symbol())
                        {
                            declared.insert(elem->symbol()->identifier());
                            commonsInCurrFunc[commonName].insert(elem->symbol()->identifier());
                        }
                    }
                }
            }
            else if (place->variant() == PARAM_DECL)
            {
                SgParameterStmt* param = (SgParameterStmt*)place;
                for (int z = 0; z < param->numberOfConstants(); ++z)
                    declared.insert(param->constant(z)->identifier());
            }
            place = place->lexNext();
        }

        //find undeclared
        auto usedVars = usedByFunc.find(byFunc.first);
        if (usedVars != usedByFunc.end())
        {
            for (auto& var : usedVars->second)
                if (declared.find(var) == declared.end())
                    undeclared.insert(var);
        }

        vector<SgSymbol*> toDeclV;
        // firstly - not in common
        for (auto& toDec : byFunc.second)
        {
            const auto commInfo = getCommonInfo(toDec);
            const int posNum = commInfo.second;
            if (posNum == -1)
                toDeclV.push_back(toDec);
        }
        // secondly - in common
        for (auto& toDec : byFunc.second)
        {
            const auto commInfo = getCommonInfo(toDec);
            const int posNum = commInfo.second;
            if (posNum != -1)
                toDeclV.push_back(toDec);
        }
        if (toDeclV.size() != byFunc.second.size())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        map<string, map<int, set<SgSymbol*>>> alreadyCommonDecl;
        for (auto& toDec : toDeclV)
        {
            bool isVarFromAnotherCommon = false;
            string needToRename = "";
            int globalType = -1;

            if (declared.find(toDec->identifier()) != declared.end() || undeclared.find(toDec->identifier()) != undeclared.end())
            {
                const auto commInfo = getCommonInfo(toDec);
                const int posNum = commInfo.second;

                if (posNum == -1)
                    continue;
                else
                {
                    bool wasUnDeclared = undeclared.find(toDec->identifier()) != undeclared.end();
                    globalType = 1;
                    
                    bool already = false;
                    if (alreadyCommonDecl.find(commInfo.first) != alreadyCommonDecl.end())
                    {
                        if (alreadyCommonDecl[commInfo.first].find(posNum) != alreadyCommonDecl[commInfo.first].end())
                        {
                            auto curr = string(toDec->identifier());
                            for (auto& s : alreadyCommonDecl[commInfo.first][posNum])
                                if (s->identifier() == curr)
                                    already = true;
                        }
                    }

                    if (!already)
                    {
                        auto itC = commonsInCurrFunc.find(commInfo.first);
                        if (itC == commonsInCurrFunc.end())
                        {
                            for (auto& currC : commonBlocks)
                            {
                                if (currC.first == commInfo.first && !wasUnDeclared)
                                    continue;

                                for (auto& var : currC.second->getVariables())
                                {
                                    if (var->getName() == toDec->identifier())
                                    {
                                        const string name = var->getName();
                                        if (commonRenames.find(name) == commonRenames.end())
                                            commonRenames[name] = 0;
                                        needToRename = name + "_cr" + to_string(commonRenames[name]);
                                        commonRenames[name]++;
                                        break;
                                    }
                                }

                                if (needToRename != "")
                                    break;
                            }
                        }

                        if (itC != commonsInCurrFunc.end())
                            if (itC->second.find(toDec->identifier()) != itC->second.end())
                                continue;

                        isVarFromAnotherCommon = true;
                        alreadyCommonDecl[commInfo.first][posNum].insert(toDec);
                    }
                }
            }
            else
            {
                declared.insert(toDec->identifier());

                const auto commInfo = getCommonInfo(toDec);
                const int posNum = commInfo.second;

                if (posNum != -1)
                {
                    alreadyCommonDecl[commInfo.first][posNum].insert(toDec);
                    globalType = 1;
                    isVarFromAnotherCommon = true;
                }
            }

            if (isSgConstantSymb(toDec))
                constRefs[toDec->identifier()] = toDec;

            if (toDec->attributes())
            {
                if ((toDec->attributes() & SAVE_BIT) || (toDec->attributes() & DATA_BIT))
                    if (globalType != 1) // not in COMMON
                        saveRefs[toDec->identifier()] = toDec;

                if (isAllocated(toDec))
                    allocatable.insert(toDec);

                if (toDec->attributes() & DATA_BIT)
                {
                    auto itD = originalInfo.find(toDec);
                    if (itD == originalInfo.end())
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    auto itDF = preprocDataByFunc.find(itD->second.first);
                    if (itDF == preprocDataByFunc.end())
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    if (initValue.find(itD->second.first) == initValue.end())
                    {
                        auto info = itDF->second.find(itD->second.second);
                        if (info == itDF->second.end())
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                        declRefs[toDec] = info->second;
                    }
                    else
                    {
                        auto itDF = initValue[itD->second.first];
                        auto info = itDF.find(itD->second.second);
                        if (info == itDF.end())
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                        declInitInfo[toDec] = info->second;
                    }
                }
            }

            globalType = (globalType == -1) ? isGlobal(byFunc.first, toDec) : globalType;
            if (globalType != 0 && !isVarFromAnotherCommon)
                continue;
            else
            {
                const auto commInfo = getCommonInfo(toDec);
                const int posNum = commInfo.second;
                const string commName = commInfo.first;

                bool needContinue = addNewCommonDecl(toDec, posNum, commName, commonBlocksRef, commonBlocks, commons, needToRename);
                if (needContinue)
                    continue;

                auto attrsM = getAttributes<SgSymbol*, char*>(toDec, set<int> { MODULE_STMT });
                if (attrsM.size())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }

            string newDecl = makeDeclaration(NULL, { toDec })->unparse();
            auto it = newDecl.find("::");
            if (it == string::npos)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (isSgConstantSymb(toDec))
                groupsConstRefs[newDecl.substr(0, it)][toDec->identifier()].push_back(toDec);
            else
                groups[newDecl.substr(0, it)][toDec->identifier()].push_back(toDec);
        }

        //insert PARAMETER
        if (constRefs.size())
        {
            for (auto& toDecls : groupsConstRefs)
            {
                vector<SgSymbol*> unitedList;
                for (auto& elem : toDecls.second)
                    unitedList.push_back(elem.second[0]);
                makeDeclaration(byFunc.first, unitedList);
            }

            SgParameterStmt* param = new SgParameterStmt();
            for (auto& elem : sortConstRefs(constRefs))
                param->addConstant(elem);

            param->setlineNumber(place->lineNumber());
            place->insertStmtBefore(*param, *scope);
        }

        for (auto& toDecls : groups)
        {
            vector<SgSymbol*> unitedList;
            vector<SgExpression*> inits;

            for (auto& elem : toDecls.second)
            {
                unitedList.push_back(elem.second[0]);
                auto initI = declInitInfo.find(elem.second[0]);
                if (initI != declInitInfo.end())
                    inits.push_back(initI->second);
                else
                    inits.push_back(NULL);
            }
            auto inserted = makeDeclaration(byFunc.first, unitedList, &inits);
            
            SgExpression* list = inserted->expr(0);
            bool typeConverted = false;
            while (list)
            {
                if (list->lhs())
                {
                    if (!typeConverted)
                    {
                        typeConverted = true;
                        SgType* t = list->lhs()->symbol()->type();

                        if (t->selector())
                        {
                            if (t->selector()->variant() == CONST_REF)
                                t->setSelector(CalculateInteger(t->selector()->copyPtr()));
                        }

                        if (t->length())
                        {
                            if (t->length()->variant() == CONST_REF)
                            {
                                auto result = CalculateInteger(t->length()->copyPtr());
                                if (result->variant() == INT_VAL)
                                    t->setLength(result);
                            }
                        }
                    }
                    auto result = CalculateInteger(list->lhs()->copyPtr());
                    list->setLhs(result);
                }
                list = list->rhs();
            }

        }

        vector<SgStatement*> newCommons;
        for (auto& byCommon : commons)
        {
            vector<SgExpression*> refs;
            for (auto& s : byCommon.second)
                refs.push_back(new SgVarRefExp(s.second));
            std::reverse(refs.begin(), refs.end());

            SgStatement* common = new SgStatement(COMM_STAT);
            common->setExpression(0, new SgExpression(COMM_LIST, makeExprList(refs, false), NULL, new SgSymbol(COMMON_NAME, byCommon.first.c_str())));

            newCommons.push_back(common);
        }

        if (newCommons.size())
        {
            for (auto& elem : newCommons)
            {
                elem->setlineNumber(place->lineNumber());
                place->insertStmtBefore(*elem, *scope);
            }
        }

        //insert SAVE
        if (saveRefs.size())
        {
            SgStatement* save = new SgStatement(SAVE_DECL);
            vector<SgExpression*> refs;
            for (auto& s : saveRefs)
                refs.push_back(new SgVarRefExp(s.second));
            save->setExpression(0, makeExprList(refs));

            save->setlineNumber(place->lineNumber());
            place->insertStmtBefore(*save, *scope);
        }

        if (declRefs.size())
        {
            SgStatement* decl = new SgStatement(DATA_DECL);
            SgExpression* value = new SgExpression(STMT_STR);

            string dataS = "data ";
            int z = 0;
            for (auto& elem : declRefs)
            {
                dataS += elem.first->identifier() + string("/") + elem.second + "/";
                if (z != declRefs.size() - 1)
                    dataS += ",";
                ++z;
            }

            value->thellnd->entry.string_val = new char[dataS.size() + 1];
            strcpy(value->thellnd->entry.string_val, dataS.c_str());

            decl->setExpression(0, value);
            decl->setlineNumber(place->lineNumber());
            place->insertStmtBefore(*decl, *scope);
        }

        //insert ALLOCATABLE
        if (allocatable.size())
        {
            SgStatement* alloc_stat = new SgStatement(ALLOCATABLE_STMT);
            vector<SgExpression*> refs;
            for (auto& s : allocatable)
                refs.push_back(new SgVarRefExp(s));
            alloc_stat->setExpression(0, makeExprList(refs));

            alloc_stat->setlineNumber(place->lineNumber());
            place->insertStmtBefore(*alloc_stat, *scope);
        }
    }
}

static void convertLinesToAbsolute(const map<string, vector<FuncInfo*>>& allFuncInfo,
                                   vector<tuple<string, string, int>>& inDataProc)
{
    set<tuple<string, string, int>> added;
    for (int z = 0; z < inDataProc.size(); ++z)
    {
        if (std::get<2>(inDataProc[z]) > 0)
            continue;

        auto funcToInl = std::get<0>(inDataProc[z]);
        auto file = std::get<1>(inDataProc[z]);
        int absoluteLine = 0;
        int shilftLine = -std::get<2>(inDataProc[z]);
        
        for (auto& funcByFile : allFuncInfo)
        {
            if (funcByFile.first != file)
                continue;

            for (auto& func : funcByFile.second)
            {
                int targetLine = func->linesNum.first + shilftLine;
                //__spf_print(1, "%s target %d + %d = %d\n", func->funcName.c_str(), func->linesNum.first, shilftLine, targetLine);
                for (auto& detCall : func->callsFromDetailed)
                {
                    if (detCall.detailCallsFrom == make_pair(funcToInl, targetLine) && 
                        added.find(make_tuple(file, funcToInl, targetLine)) == added.end())
                    {
                        __spf_print(1, "%s %d (was %d) %s\n", funcToInl.c_str(), targetLine, std::get<2>(inDataProc[z]), funcByFile.first.c_str());
                        added.insert(make_tuple(file, funcToInl, targetLine));
                        absoluteLine = targetLine;
                        break;
                    }
                }
                if (absoluteLine)
                    break;
            }
            if (absoluteLine)
                break;
        }

        if (absoluteLine == 0)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        std::get<2>(inDataProc[z]) = absoluteLine;
    }
}

void callInliner(const map<string, vector<FuncInfo*>>& allFuncInfo,
                 vector<tuple<string, string, int>>& inDataProc,
                 map<string, set<pair<string, int>>>& inDataChains,
                 const set<string>& inDataChainsStart,
                 map<string, vector<Messages>>& SPF_messages,
                 const map<string, CommonBlock*>& commonBlocks)
{
    map<SgStatement*, set<SgSymbol*>> newSymbsToDeclare;
    map<string, FuncInfo*> tmpM;
    createMapOfFunc(allFuncInfo, tmpM);
    FuncInfo* mainF = NULL;
    for (auto& elem : tmpM)
        if (elem.second->isMain)
            mainF = elem.second;
    checkNull(mainF, convertFileName(__FILE__).c_str(), __LINE__);

#if 0
    //inliner(mainF->funcName, allFuncInfo, SPF_messages, newSymbsToDeclare);
#else

    if (inDataProc.size())
    {
        __spf_print(1, "count of inline data %ld\n", inDataProc.size());
        convertLinesToAbsolute(allFuncInfo, inDataProc);

        map<int, vector<int>> sortByLvl;
        int maxLvlCall = 0;

        for (int z = 0; z < inDataProc.size(); ++z)
        {
            if (std::get<2>(inDataProc[z]) != -1)
            {
                int lvl = getLvlCall(mainF, 0, std::get<0>(inDataProc[z]), std::get<1>(inDataProc[z]), std::get<2>(inDataProc[z]));
                if (lvl == -1)
                {
                    bool found = false;
                    for (auto& func : tmpM)
                    {
                        if (func.second->isMain)
                            continue;
                        int lvlTmp = getLvlCall(func.second, 0, std::get<0>(inDataProc[z]), std::get<1>(inDataProc[z]), std::get<2>(inDataProc[z]));
                        if (lvlTmp != -1)
                            lvl = std::max(lvl, lvlTmp);
                    }

                    if (lvl == -1)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }
                maxLvlCall = std::max(maxLvlCall, lvl);
                sortByLvl[lvl].push_back(z);
            }
        }

        for (int z = 0; z < inDataProc.size(); ++z)
            if (std::get<2>(inDataProc[z]) == -1)
                sortByLvl[maxLvlCall + 1].push_back(z);

        for (auto& byLvl : sortByLvl)
        {
            for (auto& idx : byLvl.second)
            {
                auto& tup = inDataProc[idx];

                if (std::get<2>(tup) != -1)
                {
                    __spf_print(1, "  call inliner with [%s %s %d]\n", std::get<1>(tup).c_str(), std::get<0>(tup).c_str(), std::get<2>(tup));
                    bool isInlined = inliner(std::get<1>(tup), std::get<0>(tup), std::get<2>(tup), allFuncInfo, SPF_messages, newSymbsToDeclare, commonBlocks);
                    if (!isInlined)
                    {
                        __spf_print(1, "    missing ...\n");
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    }
                }
            }
        }
    }
    else if (inDataChains.size())
    {
        setInlineAttributeToCalls(tmpM, inDataChains, hiddenData);

        for (auto& startPoint : inDataChainsStart)
        {
            __spf_print(1, "call inliner from '%s'\n", startPoint.c_str());
            inliner(startPoint, allFuncInfo, SPF_messages, newSymbsToDeclare, commonBlocks);
        }
    }
#endif
    createDeclarations(newSymbsToDeclare, commonBlocks);
}
