#include "leak_detector.h"

#include <vector>
#include <string>
#include <set>

#include "SgUtils.h"
#include "utils.h"
#include "expr_transform.h"

#include "checkpoints.h"

using namespace std;

enum class typeEvery { TIME, ITER };

static const vector<string> iosNames = { "spf_close_all", "spf_open_all", "spf_inc_num_part" };

static SgStatement* replaceForWithWhile(SgStatement* forSt)
{
    checkNull(forSt, convertFileName(__FILE__).c_str(), __LINE__);

    auto forStat = isSgForStmt(forSt);
    checkNull(forStat, convertFileName(__FILE__).c_str(), __LINE__);
    
    auto start = forStat->start();
    auto end = forStat->end();
    auto step = forStat->step();
    if (step == NULL)
        step = new SgValueExp(1);

    auto stepCalc = CalculateInteger(step->copyPtr());

    auto doName = forStat->doName();

    checkNull(start, convertFileName(__FILE__).c_str(), __LINE__);
    checkNull(end, convertFileName(__FILE__).c_str(), __LINE__);

    SgStatement* insert = forSt->lexPrev();
    SgStatement* doWhile = NULL;

    if (stepCalc->isInteger())
    {
        const int stepValue = stepCalc->valueInteger();
        auto cond = (stepValue > 0) ? (*new SgVarRefExp(doName) < end->copy()) : (*new SgVarRefExp(doName) > end->copy());
        auto cond2 = (stepValue > 0) ? (*new SgVarRefExp(doName) > end->copy()) : (*new SgVarRefExp(doName) < end->copy());
        SgLogIfStmt* logIf = new SgLogIfStmt(cond2, *new SgStatement(EXIT_STMT));

        doWhile = new SgWhileStmt(&cond, NULL);
        insert->insertStmtAfter(*doWhile, *insert->controlParent());
        auto insertTo = doWhile->lastNodeOfStmt();

        SgStatement* last = forSt->lastNodeOfStmt();
        while (forSt->lexNext() != last)
        {
            auto body = forSt->lexNext();
            insertTo->insertStmtBefore(*body->extractStmt(), *doWhile);
        }

        doWhile->insertStmtBefore(*new SgAssignStmt(*new SgVarRefExp(doName), start->copy() - step->copy()), *insert->controlParent());

        doWhile->insertStmtAfter(*logIf, *doWhile);
        doWhile->insertStmtAfter(*new SgAssignStmt(*new SgVarRefExp(doName), (*new SgVarRefExp(doName) + *step)), *doWhile);

        doWhile->addComment(forSt->comments());
        forSt->extractStmt();
    }
    else
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return doWhile;
}

static void checkForToReplace(SgStatement* gotoBlock, SgStatement* goPoint)
{
    //return;
    auto cp = gotoBlock->controlParent();
    while (goPoint->controlParent() != cp)
    {
        auto top = goPoint->controlParent();
        if (top->variant() == FOR_NODE)
            goPoint = replaceForWithWhile(top);
        else
            goPoint = top;
    }
}

static void createModule(SgStatement*& module, const string& name, bool withFile = true)
{
    if (module == NULL)
    {
        string full_name = name;
        if (withFile)
            full_name += "_" + to_string(current_file_id);

        module = new SgStatement(MODULE_STMT, NULL, new SgSymbol(MODULE_NAME, full_name.c_str()));
        addControlEndToStmt(module->thebif);

        SgStatement* global = current_file->firstStatement();
        global->insertStmtAfter(*module, *global);
    }
}

static SgType* createArrayCharType(int len, int dim)
{
    if (dim == 0)
    {
        auto simpleType = new SgType(*SgTypeChar());

        auto lenght = new SgValueExp(len);
        simpleType->thetype->entry.Template.kind_len = lenght->thellnd;
        return simpleType;
    }
    else
    {
        auto arrayType = new SgArrayType(*SgTypeChar());
        arrayType->addDimension(new SgValueExp(dim));

        auto lenght = new SgValueExp(len);
        arrayType->baseType()->thetype->entry.Template.kind_len = lenght->thellnd;
        return arrayType;
    }   
}

static void findDecls(SgExpression* ex, vector<SgExpression*>& local, const map<string, SgStatement*>& localParams, 
                      set<string>& added, set<string>& intentInParams)
{
    if (ex)
    {
        if (ex->variant() == VAR_REF)
        {
            if (ex->symbol()->variant() == VARIABLE_NAME &&
                localParams.find(ex->symbol()->identifier()) == localParams.end())
            {
                if (added.find(ex->symbol()->identifier()) == added.end() &&
                    intentInParams.find(ex->symbol()->identifier()) == intentInParams.end())
                {
                    added.insert(ex->symbol()->identifier());
                    local.push_back(ex);
                }
            }
        }

        if (ex->variant() == ARRAY_REF)
        {
            if (ex->symbol()->variant() == VARIABLE_NAME && 
                added.find(ex->symbol()->identifier()) == added.end() &&
                intentInParams.find(ex->symbol()->identifier()) == intentInParams.end())
            {
                added.insert(ex->symbol()->identifier());
                local.push_back(new SgArrayRefExp(*ex->symbol()));
            }
        }

        findDecls(ex->lhs(), local, localParams, added, intentInParams);
        findDecls(ex->rhs(), local, localParams, added, intentInParams);
    }
}


static FuncInfo* findFileInfoByName(SgStatement* func, const vector<FuncInfo*>& allFuncInfo)
{
    FuncInfo* funcI = NULL;
    for (const auto& funcs : allFuncInfo)
        if (funcs->funcName == func->symbol()->identifier())
            funcI = funcs;
    checkNull(funcI, convertFileName(__FILE__).c_str(), __LINE__);

    return funcI;
}

static void findLocalData(SgStatement* func, SgStatement* end, vector<SgExpression*>& local, 
                          map<string, SgStatement*>& localParams, set<string>& added, const vector<FuncInfo*>& allFuncInfo)
{
    SgStatement* start = func->lexNext();

    FuncInfo* funcI = findFileInfoByName(func, allFuncInfo);
    set<string> intentInParams;
    for (int i = 0; i < funcI->funcParams.countOfPars; ++i)
        if (funcI->funcParams.isArgIn(i) && !funcI->funcParams.isArgOut(i))
            intentInParams.insert(funcI->funcParams.identificators[i]);

    for (SgStatement* st = start; st != end; st = st->lexNext())
    {
        if (st->variant() == PARAM_DECL)
        {
            auto decl = (SgParameterStmt*)st;
            for (int z = 0; z < decl->numberOfConstants(); ++z)
                localParams[decl->constant(z)->identifier()] = st;
        }

        if (st->variant() == EXTERN_STAT)
            for (SgExpression* ex = st->expr(0); ex; ex = ex->rhs())
                added.insert(ex->lhs()->symbol()->identifier());        
    }

    
    for (SgStatement* st = start; st != end; st = st->lexNext())
    {
        //printf("line %d %s Var %s\n", st->lineNumber(), st->fileName(), tag[st->variant()]);
        if (st->variant() == VAR_DECL || st->variant() == VAR_DECL_90)
            findDecls(st->expr(0), local, localParams, added, intentInParams);
    } 
}

static vector<SgStatement*> findUseOfModules(SgStatement* start, SgStatement* end)
{
    vector<SgStatement*> ret;

    for (SgStatement* st = start; st != end; st = st->lexNext())
        if (st->variant() == USE_STMT)
            ret.push_back(st);
    return ret;
}

static vector<string> findAllModules()
{
    vector<string> ret;

    const string old_file = current_file->filename();

    for (int z = 0; z < CurrentProject->numberOfFiles(); ++z)
    {
        SgFile* file = &CurrentProject->file(z);
        vector<SgStatement*> modules;
        findModulesInFile(file, modules);

        for (auto& elem : modules)
            ret.push_back(elem->symbol()->identifier());
    }

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

    return ret;
}

static SgExpression* getAttribute(SgExpression* spec, const string& type)
{
    while (spec)
    {
        if (spec->lhs() && spec->lhs()->variant() == SPEC_PAIR)
        {
            if (spec->lhs()->lhs()->variant() == KEYWORD_VAL)
            {
                SgKeywordValExp* val = (SgKeywordValExp*)(spec->lhs()->lhs());
                if (val->value() == type)
                    return spec->lhs()->rhs();
            }
        }       
        spec = spec->rhs();
    }
    return NULL;
}

static SgSymbol* makeVar(const string& name) { return new SgSymbol(VARIABLE_NAME, name.c_str()); }

static void insertToOpenClose(const map<int, UserFiles>& needToSave, const vector<string>& names, const vector<string>& closedStatus,
                              const string& iosModule)
{
    int z = 0;
    map<string, map<SgStatement*, set<string>>> useOnlyByFileAndFunc;
    for (auto& elem : needToSave)
    {
        for (int k = 0; k < elem.second.placesOpen.size(); ++k)
        {
            auto lastFile = current_file->filename();
            SgStatement* currOpen = elem.second.placesOpen[k];
            if (!currOpen->switchToFile())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            currOpen->insertStmtAfter(*new SgAssignStmt(*new SgVarRefExp(makeVar(closedStatus[z])), *new SgValueExp(1)), *currOpen->controlParent());
            auto fileN = getAttribute(((SgIOControlStmt*)currOpen)->controlSpecList(), "file");
            checkNull(fileN, convertFileName(__FILE__).c_str(), __LINE__);
            currOpen->insertStmtAfter(*new SgAssignStmt(*new SgVarRefExp(makeVar(names[z])), *fileN), *currOpen->controlParent());

            auto fStat = getFuncStat(currOpen, { MODULE_STMT });
            checkNull(fStat, convertFileName(__FILE__).c_str(), __LINE__);

            useOnlyByFileAndFunc[currOpen->fileName()][fStat].insert(closedStatus[z]);
            useOnlyByFileAndFunc[currOpen->fileName()][fStat].insert(names[z]);

            if (SgFile::switchToFile(lastFile) == -1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
        ++z;
    }

    z = 0;
    for (auto& elem : needToSave)
    {
        for (int k = 0; k < elem.second.placesClose.size(); ++k)
        {
            auto lastFile = current_file->filename();
            SgStatement* currClose = elem.second.placesClose[k];
            if (!currClose->switchToFile())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            currClose->insertStmtAfter(*new SgAssignStmt(*new SgVarRefExp(makeVar(closedStatus[z])), *new SgValueExp(0)), *currClose->controlParent());
            
            auto fStat = getFuncStat(currClose, { MODULE_STMT });
            checkNull(fStat, convertFileName(__FILE__).c_str(), __LINE__);
            useOnlyByFileAndFunc[currClose->fileName()][fStat].insert(closedStatus[z]);

            if (SgFile::switchToFile(lastFile) == -1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
        ++z;
    }

    auto lastFile = current_file->filename();
    for (auto& byFile : useOnlyByFileAndFunc)
    {
        if (SgFile::switchToFile(byFile.first) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto& byStat : byFile.second)
        {
            vector<SgExpression*> onlyList;
            for (auto& elem : byStat.second)
                onlyList.push_back(new SgVarRefExp(new SgSymbol(VARIABLE_NAME, elem.c_str())));

            SgExpression* only = new SgExpression(ONLY_NODE, makeExprList(onlyList), NULL);
            SgStatement* use = new SgStatement(USE_STMT, NULL, makeVar(iosModule), only, NULL, NULL);

            byStat.first->insertStmtBefore(*use, *byStat.first);
        }
    }

    if (SgFile::switchToFile(lastFile) == -1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
}

static bool createForIOs(const map<int, UserFiles>& filesInfo, SgStatement* func)
{
    SgStatement* moduleF = NULL;
    map<int, UserFiles> needToSave;

    for (auto& elem : filesInfo)
    {
        if (elem.second.placesClose.size() != elem.second.placesOpen.size())
            if (elem.second.placesClose.size() > 0 && elem.second.placesOpen.size() > 0)
                needToSave[elem.first] = elem.second;

        if (elem.second.placesClose.size() == 0 && elem.second.placesWrite.size() > 0 && elem.second.placesRead.size() == 0)
            needToSave[elem.first] = elem.second;
    }

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

    const string iosModule = "spf_module_ios";
    createModule(moduleF, iosModule, false);

    vector<SgSymbol*> closedStatus;
    vector<SgExpression*> closedStatusInit;

    SgSymbol* counter = new SgSymbol(VARIABLE_NAME, "num_part", SgTypeInt(), func);

    vector<SgSymbol*> names;

    for (auto& elem : needToSave)
    {
        closedStatus.push_back(new SgSymbol(VARIABLE_NAME, ("spf_state_" + to_string(elem.first)).c_str(), SgTypeInt(), func));
        closedStatusInit.push_back(new SgValueExp(0));

        names.push_back(new SgSymbol(VARIABLE_NAME, ("spf_name_" + to_string(elem.first)).c_str(), createArrayCharType(32, 0), func));
    }

    makeDeclaration(moduleF, closedStatus, &closedStatusInit);
    makeDeclaration(moduleF, names);
    makeDeclaration(moduleF, { counter }, &closedStatusInit);


    moduleF->lastNodeOfStmt()->insertStmtBefore(*new SgStatement(CONTAINS_STMT), *moduleF);

    SgProcHedrStmt* close = new SgProcHedrStmt(iosNames[0].c_str());
    SgProcHedrStmt* open = new SgProcHedrStmt(iosNames[1].c_str());
    SgProcHedrStmt* inc = new SgProcHedrStmt(iosNames[2].c_str());

    moduleF->lastNodeOfStmt()->insertStmtBefore(*close, *moduleF);
    moduleF->lastNodeOfStmt()->insertStmtBefore(*open, *moduleF);
    moduleF->lastNodeOfStmt()->insertStmtBefore(*inc, *moduleF);

    inc->insertStmtAfter(*new SgAssignStmt(*new SgVarRefExp(counter), *new SgVarRefExp(counter) + *new SgValueExp(1)), *inc);

    int z = 0;
    for (auto& elem : needToSave)
    {
        SgIfStmt* ifstat = new SgIfStmt(*new SgVarRefExp(closedStatus[z]) != *new SgValueExp(0), *new SgIOControlStmt(CLOSE_STAT, *new SgValueExp(elem.first)));
        ifstat->insertStmtAfter(*new SgAssignStmt(*new SgVarRefExp(closedStatus[z]), *new SgValueExp(0)), *ifstat);
        close->insertStmtAfter(*ifstat, *close);
        ++z;
    }
        
    z = 0;

    auto sSTR = new SgSymbol(FUNCTION_NAME, "TO_STR", createArrayCharType(32, 0), moduleF);
    auto trim = new SgSymbol(FUNCTION_NAME, "TRIM");
    for (auto& elem : needToSave)
    {
        vector<SgExpression*> listSpec;
        /*listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("iostat"), *iostat));
        listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("status"), *new SgValueExp("old")));*/
        
        auto fCall = new SgFunctionCallExp(*trim, *new SgVarRefExp(names[z]));
        auto fCall2 = new SgFunctionCallExp(*trim, *new SgFunctionCallExp(*sSTR, *new SgVarRefExp(counter)));

        listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("file"), *new SgExpression(CONCAT_OP, fCall, fCall2)));
        listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("unit"), *new SgValueExp(elem.first)));

        SgIfStmt* ifstat = new SgIfStmt(*new SgVarRefExp(closedStatus[z]) == *new SgValueExp(0), *new SgIOControlStmt(OPEN_STAT, *makeExprList(listSpec, false)));
        ifstat->insertStmtAfter(*new SgAssignStmt(*new SgVarRefExp(closedStatus[z]), *new SgValueExp(1)), *ifstat);
        open->insertStmtAfter(*ifstat, *open);
        ++z;
    }    

    auto k_par = new SgSymbol(VARIABLE_NAME, "k");
    SgFuncHedrStmt* str_func = new SgFuncHedrStmt(*sSTR);
    moduleF->lastNodeOfStmt()->insertStmtBefore(*str_func, *moduleF);

    str_func->AddArg(*new SgVarRefExp(k_par));
    str_func->setExpression(0, NULL); //bad solution!
    str_func->insertStmtAfter(*makeDeclaration(NULL, { sSTR }), *str_func);
            
    str_func->lastNodeOfStmt()->insertStmtBefore(*new SgInputOutputStmt(WRITE_STAT, *makeExprList({ new SgKeywordValExp("*"), new SgVarRefExp(sSTR)}, false), *new SgVarRefExp(k_par)), *str_func);
    SgAssignStmt* adjustl = new SgAssignStmt(*new SgVarRefExp(sSTR), *new SgFunctionCallExp(*new SgSymbol(FUNCTION_NAME, "ADJUSTL"), *new SgVarRefExp(sSTR)));
    str_func->lastNodeOfStmt()->insertStmtBefore(*adjustl, *str_func);

    vector<string> namesS, closedStatusS;
    for (int z = 0; z < names.size(); ++z)
    {
        namesS.push_back(names[z]->identifier());
        closedStatusS.push_back(closedStatus[z]->identifier());
    }
    insertToOpenClose(needToSave, namesS, closedStatusS, iosModule);

    moduleF->insertStmtAfter(*new SgImplicitStmt(NULL), *moduleF);
    return true;
}

struct cpInfo
{
    int line = -1;
    int every = 60;
    int numOfFiles = 2;
    int unitNum = 789;
    typeEvery type = typeEvery::ITER;
};

static bool findSpfCpDir(SgFile* file, map<int, cpInfo>& info)
{
    bool found = false;
    int numF = file->numberOfFunctions();
    for (int z = 0; z < numF; ++z)
    {
        SgStatement* func = file->functions(z);
        for (SgStatement* st = func; st != func->lastNodeOfStmt(); st = st->lexNext())
        {
            if (st->variant() == CONTAINS_STMT)
                break;

            if (!isSgExecutableStatement(st))
                continue;
            vector<SgStatement*> cpDirs = getAttributes<SgStatement*, SgStatement*>(st, set<int>{ SPF_CHECKPOINT_DIR });

            if (cpDirs.size() == 1)
            {
                found = true;
                cpInfo currInfo;
                currInfo.line = st->lineNumber();
                SgExpression* ex = cpDirs[0]->expr(0);
                while (ex)
                {
                    if (ex->lhs())
                    {
                        int var = ex->lhs()->variant();
                        if (var == SPF_INTERVAL_OP)
                        {
                            int type = ex->lhs()->lhs()->variant();
                            if (!ex->lhs()->rhs()->isInteger())
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                            int interval = ex->lhs()->rhs()->valueInteger();
                            if (type == SPF_ITER_OP)
                            {
                                currInfo.type = typeEvery::ITER;
                                currInfo.every = interval;
                            }
                            else if (type == SPF_TIME_OP)
                            {
                                currInfo.type = typeEvery::TIME;
                                currInfo.every = interval;
                            }
                            else
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        }
                        else if (var == SPF_FILES_COUNT_OP)
                        {
                            if (!ex->lhs()->lhs()->isInteger())
                                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                            currInfo.numOfFiles = ex->lhs()->lhs()->valueInteger();
                        }
                    }
                    ex = ex->rhs();
                }
                info[currInfo.line] = currInfo;
            }
            else if (cpDirs.size() > 1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            deleteAttributes(st, set<int>{ SPF_CHECKPOINT_DIR });
        }
    }
    return found;
}

static void fillToProcess(FuncInfo* funcI, set<pair<FuncInfo*, FuncInfo*>>& toProcess) 
{
    int oldSize = toProcess.size();
    for (int k = 0; k < funcI->callsTo.size(); k++) 
    {
        pair<FuncInfo*, FuncInfo*> n(funcI, funcI->callsTo[k]);
        toProcess.insert(n);
        if (toProcess.size() != oldSize)
        {
            oldSize = toProcess.size();
            fillToProcess(funcI->callsTo[k], toProcess);
        }
    }
}

//TODO: reverse if 'before' == false
static int insertInitNamesOfFiles(const int numOfFiles, const string& additional, SgSymbol* files, SgArrayRefExp* journal,
                                  SgStatement* insert, bool before = true)
{
    int maxFileLen = -1;
    string tmp;

    for (int z = 1; z <= numOfFiles; ++z)
    {
        tmp = "spf_cp_file_" + to_string(z) + additional;
        maxFileLen = std::max(maxFileLen, (int)(tmp.size() + 1));

        if (insert)
        {
            if (before)
                insert->insertStmtBefore(*new SgAssignStmt(*new SgArrayRefExp(*files, *new SgValueExp(z)), *new SgValueExp(tmp.c_str())), *insert->controlParent());
            else
                insert->insertStmtAfter(*new SgAssignStmt(*new SgArrayRefExp(*files, *new SgValueExp(z)), *new SgValueExp(tmp.c_str())), *insert->controlParent());
        }
    }

    tmp = "spf_cp_journal" + additional;
    if (insert)
    {
        if (before)
            insert->insertStmtBefore(*new SgAssignStmt(*journal, *new SgValueExp(tmp.c_str())), *insert->controlParent());
        else
            insert->insertStmtAfter(*new SgAssignStmt(*journal, *new SgValueExp(tmp.c_str())), *insert->controlParent());
    }

    return maxFileLen;
}

static void replaceExprByExprInExpr(SgExpression* expr, SgExpression* from, SgExpression* to)
{
    map<SgExpression*, string> collection;
    if (!expr)
        return;

    if (isEqExpressions(expr->rhs(), from, collection))
        expr->setRhs(to);

    if (isEqExpressions(expr->lhs(), from, collection))
        expr->setLhs(to);

    replaceExprByExprInExpr(expr->rhs(), from, to);
    replaceExprByExprInExpr(expr->lhs(), from, to);
}

static void replaceExprByExprInSt(SgStatement* st, SgExpression* from, SgExpression* to)
{
    map<SgExpression*, string> collection;
    for (int i = 0; i < 3; i++)
    {
        SgExpression* expr = st->expr(i);
        if (!expr)
            continue;

        if (isEqExpressions(expr, from, collection))
            st->setExpression(i, to);

        if (isEqExpressions(expr->rhs(), from, collection))
            expr->setRhs(to);

        if (isEqExpressions(expr->lhs(), from, collection))
            expr->setLhs(to);
        else
        {
            replaceExprByExprInExpr(expr->rhs(), from, to);
            replaceExprByExprInExpr(expr->lhs(), from, to);
        }
    }
}

static SgStatement* createOpenJ_old(SgExpression* iostat, SgArrayRefExp* journal, SgExpression& unit)
{
    vector<SgExpression*> listSpec;
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("iostat"), *iostat));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("status"), *new SgValueExp("old")));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("file"), *journal));
    listSpec.push_back(&unit);

    SgIOControlStmt* openJ_old = new SgIOControlStmt(OPEN_STAT, *makeExprList(listSpec, false));
    return openJ_old;
}

static SgStatement* createOpen(SgExpression* iostat, SgSymbol* files, SgExpression* fileIdx, SgExpression& unit)
{
    vector<SgExpression*> listSpec;
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("form"), *new SgValueExp("unformatted")));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("iostat"), *iostat));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("status"), *new SgValueExp("old")));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("file"), *new SgArrayRefExp(*files, *fileIdx)));
    listSpec.push_back(&unit);

    SgIOControlStmt* open = new SgIOControlStmt(OPEN_STAT, *makeExprList(listSpec, false));
    return open;
}

static string mergedChar(const string& l, const string& r)
{
    return l + "_" + r;
}

static SgSymbol* renamedNewSymb(SgSymbol* oldSymb, const char* prefix)
{
    const string result = mergedChar(prefix, oldSymb->identifier());
    SgSymbol* newSymb = new SgSymbol(oldSymb->variant(), result.c_str(), oldSymb->type(), oldSymb->scope());

    return newSymb;
}

static void renameEx(SgExpression* sizeEx, const char* funcName) 
{
    if (sizeEx->variant() == CONST_REF)
    {
        SgSymbol* symbSize = renamedNewSymb(sizeEx->symbol(), funcName);
        sizeEx->setSymbol(symbSize);
    }

    if (sizeEx->lhs())
        renameEx(sizeEx->lhs(), funcName);

    if (sizeEx->rhs())
        renameEx(sizeEx->rhs(), funcName);
}

static void findSizeEx(SgExpression* sizeEx, const map<string, SgStatement*>& moduleStmts, 
                       const map<string, SgStatement*>& moduleParamStmts, SgStatement* proc_moduleF,
                       set<string>& addedModuleParams, SgStatement* borderStmt)
{
    if (sizeEx->variant() == CONST_REF)
    {
        if (moduleParamStmts.count(sizeEx->symbol()->identifier()) == 1 && addedModuleParams.count(sizeEx->symbol()->identifier()) == 0)
        {
            auto decl = (SgParameterStmt*)moduleParamStmts.at(sizeEx->symbol()->identifier());

            SgStatement* copyParamStmt = moduleParamStmts.at(sizeEx->symbol()->identifier())->copyPtr();
            borderStmt->insertStmtBefore(*copyParamStmt, *proc_moduleF);

            if (moduleStmts.count(sizeEx->symbol()->identifier())) 
            {
                SgStatement* copyStmt = moduleStmts.at(sizeEx->symbol()->identifier())->copyPtr();
                copyParamStmt->insertStmtBefore(*copyStmt, *proc_moduleF);
                borderStmt = copyStmt;
            }
            else
                borderStmt = copyParamStmt;

            addedModuleParams.insert(sizeEx->symbol()->identifier());
            findSizeEx(decl->value(0), moduleStmts, moduleParamStmts, proc_moduleF, addedModuleParams, borderStmt);
        }
    }

    if (sizeEx->lhs())
        findSizeEx(sizeEx->lhs(), moduleStmts, moduleParamStmts, proc_moduleF, addedModuleParams, borderStmt);

    if (sizeEx->rhs())
        findSizeEx(sizeEx->rhs(), moduleStmts, moduleParamStmts, proc_moduleF, addedModuleParams, borderStmt);
}

static void processCommonStmt(SgStatement* st, set<string>& commonVariables)
{
    if (st->expr(0))
    {
        SgExpression* ex = st->expr(0)->lhs();
        while (ex && ex->lhs())
        {
            auto lhs = ex->lhs();
            commonVariables.insert(lhs->sunparse());

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

static void processVarStmt(SgStatement* st, map<string, SgStatement*>& moduleStmts, const char* funcName, 
                           const set<string>& inFuncParam)
{
    SgExpression* ex = st->expr(0);
    SgExpression* lhs = NULL;

    SgStatement* baseStmt = st->copyPtr();
    baseStmt->expr(0)->setLhs(NULL);
    baseStmt->expr(0)->setRhs(NULL);
    baseStmt->setExpression(2, NULL);

    while (ex && ex->lhs())
    {
        lhs = ex->lhs();

        if (inFuncParam.count(lhs->symbol()->identifier()))
        {
            ex = ex->rhs();
            continue;
        }

        const string result = mergedChar(funcName, lhs->symbol()->identifier());
        SgSymbol* symb = new SgSymbol(lhs->symbol()->variant(), result.c_str(), lhs->symbol()->type(), lhs->symbol()->scope());
        SgExpression* newExpr = new SgExpression(lhs->variant());
        newExpr->setSymbol(symb);

        if (lhs->variant() == ARRAY_REF)
        {
            SgExpression* newArraySizeExpr = lhs->lhs()->copyPtr();

            SgExpression* sizeEx = newArraySizeExpr;
            SgExpression* sizeLhs;

            while (sizeEx && sizeEx->lhs())
            {
                sizeLhs = sizeEx->lhs();
                renameEx(sizeLhs, funcName);

                sizeEx = sizeEx->rhs();
            }

            newExpr->setLhs(newArraySizeExpr);
        }

        SgStatement* moduleStmt = baseStmt->copyPtr();
        moduleStmt->setExpression(0, newExpr);

        SgExpression* typeExpr = st->expr(1)->copyPtr();
        moduleStmt->setExpression(1, typeExpr);

        moduleStmts[newExpr->symbol()->identifier()] = moduleStmt;
        ex = ex->rhs();
    }
}

static void processParamStmt(SgStatement* st, map<string, SgStatement*>& moduleParamStmts, const char* funcName)
{
    auto decl = (SgParameterStmt*)st;
    int n = decl->numberOfConstants();
    for (int i = 0; i < n; ++i) 
    {
        SgSymbol* constSymb = renamedNewSymb(decl->constant(i), funcName);
        SgExpression* constExpr = new SgExpression(CONST_REF, NULL, NULL, constSymb);

        SgExpression* valueExpr = decl->value(i)->copyPtr();
        renameEx(valueExpr, funcName);

        SgConstantSymb* paramSymb = new SgConstantSymb(constSymb->identifier(), *decl->controlParent(), *valueExpr);

        SgParameterStmt* paramSt = new SgParameterStmt();
        paramSt->addConstant(paramSymb);

        moduleParamStmts[constSymb->identifier()] = paramSt;
    }
}

static void processExternStmt(SgStatement* st, set<string>& externVars)
{
    if (st->expr(0))
    {
        SgExpression* ex = st->expr(0);

        SgExpression* lhs;
        while (ex && ex->lhs())
        {
            lhs = ex->lhs();
            externVars.insert(lhs->sunparse());

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

static void processVarBlock(SgStatement* func, SgStatement* firstExec, map<string, SgStatement*>& moduleStmts,
                            map<string, SgStatement*>& moduleParamStmts, set<string>& commonVariables,
                            set<string>& externVars, FuncInfo* funcFrom, const set<string>& inFuncParam = { })
{
    const char* funcName = func->symbol()->identifier();

    for (SgStatement* st = func->lexNext(); st != firstExec; st = st->lexNext())
    {
        if (st->variant() == COMM_STAT)
            processCommonStmt(st, commonVariables);
        else if (st->variant() == VAR_DECL || st->variant() == VAR_DECL_90)
            processVarStmt(st, moduleStmts, funcName, inFuncParam);
        else if (st->variant() == PARAM_DECL)
            processParamStmt(st, moduleParamStmts, funcName);
        else if (st->variant() == EXTERN_STAT)
            processExternStmt(st, externVars);
    }
}

static void insertStmtToModule(const map<string, SgStatement*>& moduleStmts, const map<string, SgStatement*>& moduleParamStmts,
                               set<string>& addedModuleParams, const set<string>& commonVariables, SgStatement* proc_moduleF,
                               set<string>& localVarNoParams, const set<string>& externVars, const size_t prefixLen)
{
    SgStatement* endProcModuleF = proc_moduleF->lastNodeOfStmt();

    SgStatement* borderStmt = new SgStatement(VAR_DECL);
    proc_moduleF->insertStmtAfter(*borderStmt, *proc_moduleF);

    for (auto& stat : moduleStmts)
    {
        const auto& varName = stat.first;
        SgStatement* varStmt = stat.second;

        string varNameNoPref = varName;
        varNameNoPref.erase(0, prefixLen + 1);

        if (commonVariables.count(varNameNoPref) == 0 && moduleParamStmts.count(varName) == 0
            && externVars.count(varNameNoPref) == 0)
        {
            localVarNoParams.insert(varNameNoPref);

            endProcModuleF->insertStmtBefore(*varStmt, *proc_moduleF);
            if (varStmt->expr(0)->variant() == ARRAY_REF) 
            {
                SgExpression* arraySizeExpr = varStmt->expr(0)->lhs();

                SgExpression* sizeEx = arraySizeExpr;
                SgExpression* sizeLhs;

                while (sizeEx && sizeEx->lhs())
                {
                    sizeLhs = sizeEx->lhs();
                    findSizeEx(sizeLhs, moduleStmts, moduleParamStmts, proc_moduleF, addedModuleParams, borderStmt);

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

    borderStmt->deleteStmt();
}

static SgStatement* createLoadBlock(const vector<SgSymbol*>& loadS, FuncInfo*& funcI, SgExpression* iostat, SgArrayRefExp* journal, 
                                    SgExpression& frmt, SgExpression& unit, SgSymbol* files, SgExpression* fileIdx, const int numOfFiles,
                                    const vector<SgSymbol*>& profS, SgExpression& frmtProf, SgExpression& unitNull,
                                    SgStatement* profCallS, SgStatement* profCallE, const set<string>& localVarNoParams,
                                    const map<string, SgStatement*>& moduleStmts, const set<string>& commonVariables, bool createdModuleForIO,
                                    const vector<string>& moduleNames, const int unitNum, 
                                    const string& baseFunc, vector<vector<string>>& chainLocalVarNoParams,
                                    bool isBaseFunc = true, SgSymbol* procLabelSymb = NULL)
{
    const char* funcName = funcI->funcName.c_str();
    vector<SgStatement*> insertToLoadS;

    SgStatement* loadBlock = new SgIfStmt(*new SgVarRefExp(loadS[0]) == *new SgValueExp(1)); //*new SgVarRefExp(loadS[0])
    loadBlock->addComment("! LOAD CHECKPOINT\n");

    if (funcI->isMain)
    {
        SgAssignStmt* init = new SgAssignStmt(*new SgVarRefExp(loadS[0]), *new SgValueExp(0));
        insertToLoadS.push_back(init);
        insertToLoadS.push_back(createOpenJ_old(iostat, journal, unit));
    }

    vector<SgStatement*> insertToifLoadOk;
    SgIfStmt* ifLoadOk = new SgIfStmt(*iostat == *new SgValueExp(0));
    insertToLoadS.push_back(ifLoadOk);

    if (funcI->isMain)
    {
        SgInputOutputStmt* read = new SgInputOutputStmt(READ_STAT, *makeExprList({ &frmt, &unit }, false), *fileIdx); //*makeExprList({ fileIdx, labelIdx })
        insertToifLoadOk.push_back(read);
        insertToifLoadOk.push_back(new SgIOControlStmt(CLOSE_STAT, unit));
        insertToifLoadOk.push_back(createOpen(iostat, files, fileIdx, unit));
    }

    SgIfStmt* ifLoadOk1 = new SgIfStmt(*iostat == *new SgValueExp(0)); //, *new SgIOControlStmt(CLOSE_STAT, unit)

    insertToifLoadOk.push_back(ifLoadOk1);

    if (funcI->isMain)
    {
        SgAssignStmt* init = new SgAssignStmt(*new SgVarRefExp(loadS[0]), *new SgValueExp(1));
        ifLoadOk1->insertStmtAfter(*init, *ifLoadOk1);
    }

    if (isBaseFunc)
    {
        ifLoadOk1->insertStmtAfter(*new SgIOControlStmt(CLOSE_STAT, unit), *ifLoadOk1);
        ifLoadOk1->insertStmtAfter(*new SgIfStmt(*fileIdx == *new SgValueExp(numOfFiles + 1), *new SgAssignStmt(*fileIdx, *new SgValueExp(1))), *ifLoadOk1);
        ifLoadOk1->insertStmtAfter(*new SgAssignStmt(*fileIdx, *fileIdx + *new SgValueExp(1)), *ifLoadOk1);
    }

    ifLoadOk1->addComment("! LOAD DATA FROM CHECKPOINT\n"); 

    vector<SgExpression*> commentArgs;
    commentArgs.push_back(new SgValueExp(" SECONDS"));
    commentArgs.push_back(&(*new SgVarRefExp(profS[1]) - *new SgVarRefExp(profS[0])));
    commentArgs.push_back(new SgArrayRefExp(*files, *fileIdx));
    commentArgs.push_back(new SgValueExp("SPF CHECKPOINT LOADED FROM "));

    ifLoadOk1->insertStmtAfter(*new SgInputOutputStmt(WRITE_STAT, *makeExprList({ &frmtProf, &unitNull }, false), *makeExprList(commentArgs, false)), *ifLoadOk1);
    ifLoadOk1->insertStmtAfter(*profCallE->copyPtr(), *ifLoadOk1);

    // insert copy_block
    for (auto localVar : localVarNoParams)
    {
        SgSymbol* leftSymb = new SgSymbol(VARIABLE_NAME, localVar.c_str());
        SgExpression* leftEx = new SgVarRefExp(leftSymb);
        SgSymbol* rightSymb = renamedNewSymb(leftSymb, funcName);
        SgExpression* rightEx = new SgVarRefExp(rightSymb);
        ifLoadOk1->insertStmtAfter(*new SgAssignStmt(*leftEx, *rightEx), *ifLoadOk1);
    }

    //open all files
    if (createdModuleForIO)
    {
        SgCallStmt* call = new SgCallStmt(*new SgSymbol(FUNCTION_NAME, iosNames[1].c_str()));
        ifLoadOk1->insertStmtAfter(*call, *ifLoadOk1);
    }

    //READ LOCAL DATA
    vector<string> varNames;
    if (moduleStmts.size())
    {
        vector<SgExpression*> variablesVec;
        for (auto localVar : localVarNoParams)
        {
            const char* varName = localVar.c_str();
            const string varNameWithPref = mergedChar(funcName, varName);
            SgSymbol* symbVar = new SgSymbol(VARIABLE_NAME, varNameWithPref.c_str());
            SgExpression* exVar = new SgVarRefExp(symbVar);
            variablesVec.push_back(exVar);

            varNames.push_back(varNameWithPref);
        }
        auto dataRead = new SgInputOutputStmt(READ_STAT, unit, *makeExprList(variablesVec, false));
        ifLoadOk1->insertStmtAfter(*dataRead, *ifLoadOk1);

    }
    chainLocalVarNoParams.push_back(varNames);

    if (!isBaseFunc)
    {
        SgExpression* procLabelExpr = new SgVarRefExp(procLabelSymb);
        auto labelRead = new SgInputOutputStmt(READ_STAT, unit, *procLabelExpr);
        ifLoadOk1->insertStmtAfter(*labelRead, *ifLoadOk1);
    }

    //READ from modules
    for (auto& mod : moduleNames)
    {
        SgCallStmt* call = new SgCallStmt(*new SgSymbol(FUNCTION_NAME, ("spf_cp_" + mod).c_str()));
        call->addArg(*new SgValueExp(unitNum));
        call->addArg(*new SgValueExp(0));
        ifLoadOk1->insertStmtAfter(*call, *ifLoadOk1);
    }

    //READ COMMON DATA
    if (funcI->isMain)
        if (commonVariables.size())
        {
            vector<SgExpression*> commanVariablesVec;
            for (auto commanVar : commonVariables)
            {
                SgSymbol* symbCommon = new SgSymbol(VARIABLE_NAME, commanVar.c_str());
                SgExpression* exCommon = new SgVarRefExp(symbCommon);
                commanVariablesVec.push_back(exCommon);
            }
            auto commonDataRead = new SgInputOutputStmt(READ_STAT, unit, *makeExprList(commanVariablesVec, false));
            ifLoadOk1->insertStmtAfter(*commonDataRead, *ifLoadOk1);
        }

    ifLoadOk1->insertStmtAfter(*profCallS->copyPtr(), *ifLoadOk1);

    for (int z = insertToLoadS.size() - 1; z >= 0; --z)
        loadBlock->insertStmtAfter(*insertToLoadS[z], *loadBlock);

    for (int z = insertToifLoadOk.size() - 1; z >= 0; --z)
        ifLoadOk->insertStmtAfter(*insertToifLoadOk[z], *ifLoadOk);

    return loadBlock;
}

static SgSymbol* createLabel(const string namelabel, SgStatement*& proc_moduleF)
{
    SgSymbol* procLabelSymb = new SgSymbol(VARIABLE_NAME, namelabel.c_str(), SgTypeInt(), proc_moduleF);
    SgExpression* procLabel0 = new SgValueExp(0);
    vector<SgSymbol*> vecLabelSymb = { procLabelSymb };
    vector<SgExpression*> vecLabel0 = { procLabel0 };
    makeDeclaration(proc_moduleF, vecLabelSymb, &vecLabel0);

    return procLabelSymb;
}

static SgStatement* createSaveBlock(const vector<SgSymbol*>& loadS, FuncInfo*& funcI, SgExpression* iostat, SgArrayRefExp* journal,
                                    SgExpression& frmt, SgExpression& unit, SgSymbol* files, SgExpression* fileIdx, const int numOfFiles,
                                    const vector<SgSymbol*>& profS, SgExpression& frmtProf, SgExpression& unitNull,
                                    SgStatement* profCallS, SgStatement* profCallE, const set<string>& localVarNoParams,
                                    const map<string, SgStatement*>& moduleStmts, const set<string>& commonVariables, bool createdModuleForIO,
                                    const vector<string>& moduleNames, const int unitNum, const vector<vector<string>>& chainLocalVarNoParams,
                                    const vector<string>& chainLabel)
{
    SgStatement* storeBlock = new SgIfStmt((SgExpression*)NULL);

    vector<SgExpression*> listSpec;
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("form"), *new SgValueExp("unformatted")));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("iostat"), *iostat));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("file"), *new SgArrayRefExp(*files, *fileIdx)));
    listSpec.push_back(&unit);

    SgIOControlStmt* open = new SgIOControlStmt(OPEN_STAT, *makeExprList(listSpec, false));
    storeBlock->insertStmtAfter(*open, *storeBlock);

    SgIfStmt* ifStoreOk = new SgIfStmt(*iostat == *new SgValueExp(0));
    open->insertStmtAfter(*ifStoreOk, *storeBlock);
    ifStoreOk->insertStmtAfter(*new SgIfStmt(*fileIdx == *new SgValueExp(numOfFiles + 1), *new SgAssignStmt(*fileIdx, *new SgValueExp(1))), *ifStoreOk);
    ifStoreOk->insertStmtAfter(*new SgAssignStmt(*fileIdx, *fileIdx + *new SgValueExp(1)), *ifStoreOk);
    ifStoreOk->addComment(("! STORE DATA TO CHECKPOINT " + to_string(localVarNoParams.size() + commonVariables.size()) + " items\n").c_str());
    
    //commentArgs.clear();
    vector<SgExpression*> commentArgs;
    commentArgs.push_back(new SgValueExp(" SECONDS"));
    commentArgs.push_back(&(*new SgVarRefExp(profS[1]) - *new SgVarRefExp(profS[0])));
    commentArgs.push_back(new SgArrayRefExp(*files, *fileIdx));
    commentArgs.push_back(new SgValueExp("SPF CHECKPOINT STORED TO "));

    ifStoreOk->insertStmtAfter(*new SgInputOutputStmt(WRITE_STAT, *makeExprList({ &frmtProf, &unitNull }, false), *makeExprList(commentArgs, false)), *ifStoreOk);
    ifStoreOk->insertStmtAfter(profCallE->copy(), *ifStoreOk);

    SgStatement* assign = ifStoreOk->lexNext();

    //open all files
    if (createdModuleForIO)
    {
        SgCallStmt* call = new SgCallStmt(*new SgSymbol(FUNCTION_NAME, iosNames[1].c_str()));
        ifStoreOk->insertStmtAfter(*call, *ifStoreOk);
    }
    //WRITE from modules
    for (auto& mod : moduleNames)
    {
        SgCallStmt* call = new SgCallStmt(*new SgSymbol(FUNCTION_NAME, ("spf_cp_" + mod).c_str()));
        call->addArg(*new SgValueExp(unitNum));
        call->addArg(*new SgValueExp(1));
        ifStoreOk->insertStmtAfter(*call, *ifStoreOk);
    }

    //close and inc
    if (createdModuleForIO)
    {
        SgCallStmt* call = new SgCallStmt(*new SgSymbol(FUNCTION_NAME, iosNames[2].c_str()));
        ifStoreOk->insertStmtAfter(*call, *ifStoreOk);
        call = new SgCallStmt(*new SgSymbol(FUNCTION_NAME, iosNames[0].c_str()));
        ifStoreOk->insertStmtAfter(*call, *ifStoreOk);
    }
    
    if (moduleStmts.size())
    {
        vector<SgExpression*> variablesVec;
        for (auto localVar : localVarNoParams)
        {
            const char* varName = localVar.c_str();
            const string varNameWithPref = mergedChar(funcI->funcName.c_str(), varName);
            SgSymbol* symbVar = new SgSymbol(VARIABLE_NAME, varNameWithPref.c_str());
            SgExpression* exVar = new SgVarRefExp(symbVar);
            variablesVec.push_back(exVar);
        }
        auto dataWrite = new SgInputOutputStmt(WRITE_STAT, unit, *makeExprList(variablesVec, false));
        ifStoreOk->insertStmtAfter(*dataWrite, *ifStoreOk);
    }

    for (size_t i = 0; i < chainLabel.size(); i++)
    {
        if (!chainLocalVarNoParams[i].empty())
        {
            vector<SgExpression*> variablesVec;
            for (auto localVar : chainLocalVarNoParams[i])
            {
                const char* varNameWithPref = localVar.c_str();
                SgSymbol* symbVar = new SgSymbol(VARIABLE_NAME, varNameWithPref);
                SgExpression* exVar = new SgVarRefExp(symbVar);
                variablesVec.push_back(exVar);
            }
            auto dataWrite = new SgInputOutputStmt(WRITE_STAT, unit, *makeExprList(variablesVec, false));
            ifStoreOk->insertStmtAfter(*dataWrite, *ifStoreOk);
        }

        SgSymbol* procLabelSymb = new SgSymbol(VARIABLE_NAME, chainLabel[i].c_str());
        SgExpression* procLabelExpr = new SgVarRefExp(procLabelSymb);
        auto labelRead = new SgInputOutputStmt(WRITE_STAT, unit, *procLabelExpr);
        ifStoreOk->insertStmtAfter(*labelRead, *ifStoreOk);
    }
    
    
    if (commonVariables.size())
    {
        vector<SgExpression*> commanVariablesVec;
        for (auto commanVar : commonVariables)
        {
            SgSymbol* symbCommon = new SgSymbol(VARIABLE_NAME, commanVar.c_str());
            SgExpression* exCommon = new SgVarRefExp(symbCommon);
            commanVariablesVec.push_back(exCommon);
        }
        auto commonDataWrite = new SgInputOutputStmt(WRITE_STAT, unit, *makeExprList(commanVariablesVec, false));
        ifStoreOk->insertStmtAfter(*commonDataWrite, *ifStoreOk);
    }
    

    // insert copy_block
    for (auto localVar : localVarNoParams)
    {
        SgSymbol* rightSymb = new SgSymbol(VARIABLE_NAME, localVar.c_str());
        SgExpression* rightEx = new SgVarRefExp(rightSymb);
        SgSymbol* leftSymb = renamedNewSymb(rightSymb, funcI->funcName.c_str());
        SgExpression* leftEx = new SgVarRefExp(leftSymb);
        ifStoreOk->insertStmtAfter(*new SgAssignStmt(*leftEx, *rightEx), *ifStoreOk);
    }

    listSpec.clear();
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("iostat"), *iostat));
    listSpec.push_back(&SgAssignOp(*new SgKeywordValExp("file"), *journal));
    listSpec.push_back(&unit);

    SgIOControlStmt* openJ_new = new SgIOControlStmt(OPEN_STAT, *makeExprList(listSpec, false));

    assign->insertStmtBefore(*new SgIOControlStmt(CLOSE_STAT, unit), *ifStoreOk);
    assign->insertStmtBefore(*openJ_new, *ifStoreOk);
    assign->insertStmtBefore(*new SgInputOutputStmt(WRITE_STAT, *makeExprList({ &frmt, &unit }, false), *fileIdx), *ifStoreOk);
    assign->insertStmtBefore(*new SgIOControlStmt(CLOSE_STAT, unit), *ifStoreOk);

    ifStoreOk->insertStmtAfter(profCallS->copy(), *ifStoreOk);

    return storeBlock;
}

static void saveVarToModule(const set<string>& localVarNoParams, SgStatement* callPU, const char* funcName, 
                            SgSymbol* procLabelSymb, const int labNum)
{
    for (auto localVar : localVarNoParams)
    {
        SgSymbol* rightSymb = new SgSymbol(VARIABLE_NAME, localVar.c_str());
        SgExpression* rightEx = new SgVarRefExp(rightSymb);
        SgSymbol* leftSymb = renamedNewSymb(rightSymb, funcName);
        SgExpression* leftEx = new SgVarRefExp(leftSymb);
        callPU->insertStmtBefore(*new SgAssignStmt(*leftEx, *rightEx), *callPU->controlParent());
    }

    SgExpression* procLabelExpr = new SgVarRefExp(procLabelSymb);
    SgExpression* labNumExpr = new SgValueExp(labNum);
    callPU->insertStmtBefore(*new SgAssignStmt(*procLabelExpr, *labNumExpr), *callPU->controlParent());
}

static void processAllCalls(SgStatement* firstExec, const string funcToName, const string funcFromName,
                            SgStatement* gotoBlock, const set<string> localVarNoParams, SgSymbol* procLabelSymb)
{
    SgStatement* execStmt = firstExec;
    while (execStmt && isSgExecutableStatement(execStmt))
    {
        if (execStmt->variant() == PROC_STAT && execStmt->symbol()->identifier() == funcToName) 
        {
            const int labNum = getNextFreeLabel();
            auto nextPULabel = new SgLabel(labNum);
            execStmt->setLabel(*nextPULabel);

            SgStatement* gotoNextPU = new SgIfStmt(*new SgVarRefExp(procLabelSymb) == *new SgValueExp(labNum));
            gotoNextPU->insertStmtAfter(*new SgGotoStmt(*nextPULabel), *gotoNextPU);

            gotoBlock->insertStmtBefore(*gotoNextPU, *gotoBlock);
            saveVarToModule(localVarNoParams, execStmt, funcFromName.c_str(), procLabelSymb, labNum);

            checkForToReplace(gotoBlock, execStmt);
        }

        execStmt = execStmt->lexNext();
    }
}

static void processInFuncParam(FuncInfo* funcI, set<string>& inFuncParam)
{
    for (int i = 0; i < funcI->funcParams.countOfPars; ++i)
        if (funcI->funcParams.isArgIn(i) && !funcI->funcParams.isArgOut(i))
            inFuncParam.insert(funcI->funcParams.identificators[i]);
}

static SgStatement* getFirstExecStat(SgStatement* func)
{
    SgStatement* firstExec = func->lexNext();
    SgStatement* last = func->lastNodeOfStmt();
    while (firstExec && firstExec != last && !isSgExecutableStatement(firstExec))
    {
        firstExec = firstExec->lastNodeOfStmt();
        firstExec = firstExec->lexNext();
    }
    return firstExec;
}

static void processFunctionCallChain(SgStatement* func, const vector<FuncInfo*>& allFuncInfo, SgStatement* moduleF,
                                     const vector<SgSymbol*>& loadS, SgExpression* iostat, SgArrayRefExp* journal, 
                                     SgExpression& frmt, SgExpression& unit,
                                     SgSymbol* files, SgExpression* fileIdx, const int every,
                                     const int numOfFiles, const string additional, const vector<SgSymbol*> profS, 
                                     SgExpression& frmtProf, SgExpression& unitNull, const int unitNum,
                                     bool createdModuleForIO, const vector<string>& moduleNames, 
                                     vector<vector<string>>& chainLocalVarNoParams, vector<string>& chainLabel)
{
    //find function structure
    FuncInfo* funcI = findFileInfoByName(func, allFuncInfo);

    set<pair<FuncInfo*, FuncInfo*>> toProcess;
    fillToProcess(funcI, toProcess);

    map<FuncInfo*, SgStatement*> processedFrom;
    for (auto& pairs : toProcess)
    {
        FuncInfo* funcTo = pairs.first;
        FuncInfo* funcFrom = pairs.second;

        SgFile::switchToFile(funcFrom->fileName);

        SgStatement* hedrFrom = funcFrom->funcPointer->GetOriginal();
        SgStatement* firstExec = getFirstExecStat(hedrFrom);

        if (!processedFrom.count(funcFrom))
        {
            if (funcFrom->isMain)
                insertInitNamesOfFiles(numOfFiles, additional, files, journal, firstExec);

            SgSymbol* timeF = new SgSymbol(FUNCTION_NAME, "omp_get_wtime", SgTypeDouble(), func); // OR dvtime        

            SgStatement* profCallS = new SgAssignStmt(*new SgVarRefExp(profS[0]), *new SgFunctionCallExp(*timeF));
            SgStatement* profCallE = new SgAssignStmt(*new SgVarRefExp(profS[1]), *new SgFunctionCallExp(*timeF));
            
            const string funcName = funcFrom->funcName;

            SgStatement* proc_moduleF = NULL;
            const string proc_cpModule = "spf_module_" + funcName;
            createModule(proc_moduleF, proc_cpModule);
            SgSymbol* proc_mainModuleCpS = proc_moduleF->symbol();

            set<string> inFuncParam;
            processInFuncParam(funcFrom, inFuncParam);

            map<string, SgStatement*> moduleStmts;
            map<string, SgStatement*> moduleParamStmts;
            set<string> commonVariables;
            set<string> externVars;
            processVarBlock(hedrFrom, firstExec, moduleStmts, moduleParamStmts, commonVariables, externVars, funcFrom, inFuncParam);

            set<string> addedModuleParams;
            set<string> localVarNoParams;
            insertStmtToModule(moduleStmts, moduleParamStmts, addedModuleParams, commonVariables, proc_moduleF, localVarNoParams,
                               externVars, funcName.size());

            const string namelabelSymb = funcFrom->funcName + "_label";
            SgSymbol* procLabelSymb = createLabel(namelabelSymb, proc_moduleF);
                
            chainLabel.push_back(namelabelSymb);

            hedrFrom->insertStmtAfter(*new SgStatement(USE_STMT, NULL, proc_mainModuleCpS), *hedrFrom);
            
            SgFile::switchToFile(func->fileName());
            SgStatement* useModuleSt = new SgStatement(USE_STMT);
            useModuleSt->setSymbol(*proc_mainModuleCpS);
            func->insertStmtAfter(*useModuleSt, *func);

            SgSymbol* modS = moduleF->symbol();
            SgFile::switchToFile(funcFrom->fileName);
            SgStatement* useSt = new SgStatement(USE_STMT);
            useSt->setSymbol(*modS);
            hedrFrom->insertStmtAfter(*useSt, *hedrFrom);


            SgStatement* loadBlock = createLoadBlock(loadS, funcFrom, iostat, journal, frmt, unit, files, fileIdx,
                                                     numOfFiles, profS, frmtProf, unitNull, profCallS, profCallE, 
                                                     localVarNoParams, moduleStmts, commonVariables, createdModuleForIO, 
                                                     moduleNames, unitNum, funcI->funcName, chainLocalVarNoParams, false, procLabelSymb);
            firstExec->insertStmtBefore(*loadBlock, *firstExec->controlParent());

                
            SgStatement* gotoBlock = new SgStatement(IF_NODE);
            gotoBlock->addComment("! GOTO NEXT PROGRAM UNIT\n");
            gotoBlock->setExpression(0, *new SgVarRefExp(loadS[0]) == *new SgValueExp(1));
            loadBlock->insertStmtAfter(*gotoBlock, *loadBlock->controlParent());

            // insert gotoBlock and save to module
            processAllCalls(firstExec, funcTo->funcName, funcFrom->funcName, gotoBlock, localVarNoParams, procLabelSymb);

            makeDeclaration(hedrFrom, { timeF });
        }
    }

    SgFile::switchToFile(func->fileName());
}

static void processModules(SgFile* file, const vector<FuncInfo*>& allFuncInfo)
{
    vector<SgStatement*> modules;
    findModulesInFile(file, modules);
    for (auto& mod : modules)
    {
        const string name = mod->symbol()->identifier();
        auto pos = name.find("spf_");
        if (pos != string::npos && pos == 0)
            continue;

        bool hasContains = false;
        SgStatement* st = mod->lexNext();
        while (st != mod->lastNodeOfStmt() && !hasContains)
        {
            hasContains = st->variant() == CONTAINS_STMT;
            st = st->lexNext();
        }

        vector<SgExpression*> local;
        map<string, SgStatement*> localParams;
        set<string> addedToList;
        findLocalData(mod, hasContains ? st : mod->lastNodeOfStmt(), local, localParams, addedToList, allFuncInfo);

        SgProcHedrStmt* newF = new SgProcHedrStmt(("spf_cp_" + name).c_str());
        if (!hasContains)
            mod->lastNodeOfStmt()->insertStmtBefore(*new SgStatement(CONTAINS_STMT), *mod);
        mod->lastNodeOfStmt()->insertStmtBefore(*newF, *mod);

        newF->AddArg("unit_f", *SgTypeInt());
        newF->AddArg("type_of_op", *SgTypeInt());

        if (local.size())
        {
            SgExpression& unit = SgAssignOp(*new SgKeywordValExp("unit"), *new SgVarRefExp(*newF->parameter(0)));
            auto dataRead = new SgInputOutputStmt(READ_STAT, unit, *makeExprList(local));
            auto dataWrite = new SgInputOutputStmt(WRITE_STAT, unit, *makeExprList(local));

            SgIfStmt* ifBlock = new SgIfStmt(*new SgVarRefExp(*newF->parameter(1)) == *new SgValueExp(0), *dataRead, *dataWrite);
            newF->lastNodeOfStmt()->insertStmtBefore(*ifBlock, *newF);

            ifBlock->addComment(("!STORE OR LOAD " + to_string(local.size()) + " ITEMS\n").c_str());
        }
    }
}

void createCheckpoints(SgFile* file, const map<string, CommonBlock*>& commonBlocks, const map<int, UserFiles>& filesInfo,
                       const vector<FuncInfo*>& allFuncInfo)
{
    map<int, cpInfo> inFileCp;
    bool inFile = findSpfCpDir(file, inFileCp);

    if (!inFile)
    {
        processModules(file, allFuncInfo);
        return;
    }

    set<string> commonVars;
    for (auto& common : commonBlocks)
        for (auto& elem : common.second->getVariables())
            if (elem->getType() != CONST)
                commonVars.insert(elem->getName());

    const vector<string> moduleNames = findAllModules();

    SgStatement* moduleF = NULL;
    createModule(moduleF, "spf_module_checkpoint");
    SgSymbol* mainModuleCpS = moduleF->symbol();
    
    for (auto& checkps : inFileCp)
    {
        const int cpLine = checkps.first;
        const int every = checkps.second.every;
        const int numOfFiles = checkps.second.numOfFiles;
        const int unitNum = checkps.second.unitNum;
        const string additional = string("_") + to_string(current_file_id) + "_" + to_string(cpLine);
        const typeEvery type = checkps.second.type;

        const int numF = file->numberOfFunctions();
        SgStatement* func = NULL;
        SgStatement* point = NULL;
        for (int z = 0; z < numF; ++z)
        {
            func = file->functions(z);
            point = func->lexNext();
            while (point && ((point->fileName() != file->filename()) || (point->lineNumber() < cpLine)) && point != func->lastNodeOfStmt() && point->variant() != CONTAINS_STMT)
                point = point->lexNext();

            //cp place was found 
            if (point != func->lastNodeOfStmt())
                break;
            point = NULL;
            func = NULL;
        }
        checkNull(func, convertFileName(__FILE__).c_str(), __LINE__);
        checkNull(point, convertFileName(__FILE__).c_str(), __LINE__);

        bool createdModuleForIO = createForIOs(filesInfo, func);

        SgStatement* lastDecl = func->lexNext();
        while (lastDecl && !isSgExecutableStatement(lastDecl->lexNext()))
            lastDecl = lastDecl->lexNext();

        checkNull(lastDecl, convertFileName(__FILE__).c_str(), __LINE__);
        SgStatement* firstExec = lastDecl->lexNext();

        vector<SgExpression*> local;
        map<string, SgStatement*> localParams;
        set<string> addedToList;
        findLocalData(func, firstExec, local, localParams, addedToList, allFuncInfo);

        const string funcName = func->symbol()->identifier();
        
        SgStatement* proc_moduleF = NULL;
        const string proc_cpModule = "spf_module_" + funcName;
        createModule(proc_moduleF, proc_cpModule);
        SgSymbol* proc_mainModuleCpS = proc_moduleF->symbol();

        FuncInfo* funcI = findFileInfoByName(func, allFuncInfo);
        set<string> intentInParams;
        for (int i = 0; i < funcI->funcParams.countOfPars; ++i)
            if (funcI->funcParams.isArgIn(i) && !funcI->funcParams.isArgOut(i))
                intentInParams.insert(funcI->funcParams.identificators[i]);

        set<string> inFuncParam;
        processInFuncParam(funcI, inFuncParam);

        map<string, SgStatement*> moduleStmts;
        map<string, SgStatement*> moduleParamStmts;
        set<string> commonVariables;
        set<string> externVars;
        processVarBlock(func, firstExec, moduleStmts, moduleParamStmts, commonVariables, externVars, funcI, inFuncParam);

        set<string> addedModuleParams;
        set<string> localVarNoParams;
        insertStmtToModule(moduleStmts, moduleParamStmts, addedModuleParams, commonVariables, proc_moduleF, localVarNoParams,
                           externVars, funcName.size());
                
        const vector<SgStatement*> useOfMods = findUseOfModules(func->lexNext(), firstExec);

        string filesS = "spf_cp_files" + additional;
        SgSymbol* files = new SgSymbol(VARIABLE_NAME, filesS.c_str(), createArrayCharType(32, numOfFiles + 1), func);
        SgArrayRefExp* journal = new SgArrayRefExp(*files, *new SgValueExp(numOfFiles + 1));

        //give max len, dont insert
        int maxFileLen = insertInitNamesOfFiles(numOfFiles, additional, files, journal, NULL);

        vector<SgSymbol*> profS;

        string profSs = "spf_cp_prof_s" + additional;
        string profEs = "spf_cp_prof_e" + additional;
        profS.push_back(new SgSymbol(VARIABLE_NAME, profSs.c_str(), SgTypeFloat(), func));
        profS.push_back(new SgSymbol(VARIABLE_NAME, profEs.c_str(), SgTypeFloat(), func));

        SgSymbol* timeF = new SgSymbol(FUNCTION_NAME, "omp_get_wtime", SgTypeDouble(), func); // OR dvtime        

        SgStatement* profCallS = new SgAssignStmt(*new SgVarRefExp(profS[0]), *new SgFunctionCallExp(*timeF));
        SgStatement* profCallE = new SgAssignStmt(*new SgVarRefExp(profS[1]), *new SgFunctionCallExp(*timeF));

        vector<SgSymbol*> loadS;
        vector<SgExpression*> initLoadS;

        string cpLoadS = "spf_cp_load" + additional;
        loadS.push_back(new SgSymbol(VARIABLE_NAME, cpLoadS.c_str(), SgTypeInt(), func));
        initLoadS.push_back(new SgValueExp(1));

        string fileNS = "spf_cp_file_n" + additional;
        loadS.push_back(new SgSymbol(VARIABLE_NAME, fileNS.c_str(), SgTypeInt(), func));
        initLoadS.push_back(new SgValueExp(1));

        string iostatS = "spf_cp_iostat" + additional;
        loadS.push_back(new SgSymbol(VARIABLE_NAME, iostatS.c_str(), SgTypeInt(), func));
        initLoadS.push_back(NULL);

        string loadLabelS = "spf_cp_load_label" + additional;
        loadS.push_back(new SgSymbol(VARIABLE_NAME, loadLabelS.c_str(), SgTypeInt(), func));
        initLoadS.push_back(new SgValueExp(0));
                

        SgExpression& unitNull = SgAssignOp(*new SgKeywordValExp("unit"), *new SgKeywordValExp("*"));
        SgExpression& unit = SgAssignOp(*new SgKeywordValExp("unit"), *new SgValueExp(unitNum));
        SgExpression& frmt = SgAssignOp(*new SgKeywordValExp("fmt"), *new SgKeywordValExp("*"));
        SgExpression& frmtProf = SgAssignOp(*new SgKeywordValExp("fmt"), *new SgValueExp(("(A,A" + to_string(maxFileLen) + ",F4.2,A)").c_str()));

        SgExpression* labelIdx = new SgVarRefExp(loadS[3]);
        SgExpression* iostat = new SgVarRefExp(loadS[2]);
        SgExpression* fileIdx = new SgVarRefExp(loadS[1]);

        vector<vector<string>> chainLocalVarNoParamsa;
        SgStatement* loadBlock = createLoadBlock(loadS, funcI, iostat, journal, frmt, unit, files, fileIdx, 
                                                 numOfFiles, profS, frmtProf, unitNull, profCallS, profCallE, localVarNoParams, moduleStmts,
                                                 commonVariables, createdModuleForIO, moduleNames, unitNum, funcName, chainLocalVarNoParamsa);
        lastDecl->insertStmtAfter(*loadBlock, *func);

        if (funcI->isMain)
            insertInitNamesOfFiles(numOfFiles, additional, files, journal, lastDecl, false);

        // make all new declarations
        makeDeclaration(moduleF, loadS, &initLoadS);
        makeDeclaration(moduleF, profS);
        makeDeclaration(moduleF, { files });
        makeDeclaration(func, { timeF });

        func->insertStmtAfter(*new SgStatement(USE_STMT, NULL, mainModuleCpS), *func);
        func->insertStmtAfter(*new SgStatement(USE_STMT, NULL, proc_mainModuleCpS), *func);

        //check use
        map<string, bool> modulesDone;
        for (auto& elem : moduleNames)
            modulesDone[elem] = false;

        //check with ONLY
        for (auto& use : useOfMods)
        {
            const string modName = use->symbol()->identifier();
            if (!modulesDone[modName])
            {
                if (use->expr(0)) // has ONLY
                {
                    SgExpression* ex = use->expr(0);
                    if (ex && ex->variant() == ONLY_NODE)
                    {
                        modulesDone[modName] = true;
                        auto callName = new SgSymbol(VARIABLE_NAME, ("spf_cp_" + modName).c_str());
                        ex->setLhs(new SgExpression(EXPR_LIST, new SgVarRefExp(callName), ex->lhs()));
                    }
                }
            }
        }

        //check other
        for (auto& use : useOfMods)
        {
            const string modName = use->symbol()->identifier();
            if (!modulesDone[modName])
                modulesDone[modName] = true;
        }

        for (auto& elem : modulesDone)
        {
            if (!elem.second)
            {
                auto callName = new SgSymbol(VARIABLE_NAME, ("spf_cp_" + elem.first).c_str());
                SgSymbol* modS = new SgSymbol(VARIABLE_NAME, elem.first.c_str());

                vector<SgExpression*> onlyList;
                onlyList.push_back(new SgVarRefExp(callName));
                if (elem.first == "spf_module_ios")
                    for (int z = 0; z < 3; ++z)
                        onlyList.push_back(new SgVarRefExp(new SgSymbol(VARIABLE_NAME, iosNames[z].c_str())));

                SgExpression* only = new SgExpression(ONLY_NODE, makeExprList(onlyList), NULL);
                SgStatement* use = new SgStatement(USE_STMT, NULL, modS, only, NULL, NULL);
                func->insertStmtAfter(*use, *func);
            }
        }
        
        vector<vector<string>> chainLocalVarNoParams;
        vector<string> chainLabel;

        processFunctionCallChain(func, allFuncInfo, moduleF, loadS, iostat, journal, frmt, unit, files,
                                 fileIdx, every, numOfFiles, additional, profS, frmtProf, unitNull, 
                                 unitNum, createdModuleForIO, moduleNames, chainLocalVarNoParams, chainLabel);
        
        SgStatement* storeBlock = createSaveBlock(loadS, funcI, iostat, journal, frmt, unit, files, fileIdx,
                                                  numOfFiles, profS, frmtProf, unitNull, profCallS, profCallE, 
                                                  localVarNoParams, moduleStmts, commonVariables, createdModuleForIO, 
                                                  moduleNames, unitNum, chainLocalVarNoParams, chainLabel);

        point->insertStmtBefore(*storeBlock, *point->controlParent());

        vector<SgSymbol*> everyS;
        vector<SgExpression*> initS;
        if (type == typeEvery::TIME)
        {
            string everySs = "spf_cp_start" + additional;
            string everyEs = "spf_cp_end" + additional;
            everyS.push_back(new SgSymbol(VARIABLE_NAME, everySs.c_str(), SgTypeFloat(), func));
            everyS.push_back(new SgSymbol(VARIABLE_NAME, everyEs.c_str(), SgTypeFloat(), func));

            initS.push_back(NULL);
            initS.push_back(NULL);

            SgStatement* call = new SgAssignStmt(*new SgVarRefExp(everyS[0]), *new SgFunctionCallExp(*timeF));
            lastDecl->insertStmtAfter(*call, *func);
            lastDecl->insertStmtAfter(*new SgStatement(DVM_BARRIER_DIR), *func);

            storeBlock->insertStmtAfter(call->copy(), *storeBlock);
            storeBlock->insertStmtAfter(*new SgStatement(DVM_BARRIER_DIR), *storeBlock);

            call = new SgAssignStmt(*new SgVarRefExp(everyS[1]), *new SgFunctionCallExp(*timeF));
            storeBlock->insertStmtBefore(*new SgStatement(DVM_BARRIER_DIR), *storeBlock->controlParent());
            storeBlock->insertStmtBefore(*call, *storeBlock->controlParent());
            call->lexPrev()->addComment("! STORE CHECKPOINT\n");

            storeBlock->setExpression(0, *new SgVarRefExp(everyS[1]) - *new SgVarRefExp(everyS[0]) >= *new SgValueExp(every));
        }
        else if (type == typeEvery::ITER)
        {
            string everyIs = "spf_cp_interval" + additional;
            everyS.push_back(new SgSymbol(VARIABLE_NAME, everyIs.c_str(), SgTypeInt(), func));
            initS.push_back(new SgValueExp(0));
            SgAssignStmt* init = new SgAssignStmt(*new SgVarRefExp(everyS[0]), *new SgValueExp(0));
            storeBlock->insertStmtAfter(*init, *storeBlock);

            SgAssignStmt* inc = new SgAssignStmt(*new SgVarRefExp(everyS[0]), *new SgVarRefExp(everyS[0]) + *new SgValueExp(1));
            storeBlock->insertStmtBefore(*inc, *storeBlock->controlParent());
            inc->addComment("! STORE CHECKPOINT\n");

            storeBlock->setExpression(0, *new SgVarRefExp(everyS[0]) >= *new SgValueExp(every));
        }

        makeDeclaration(moduleF, everyS, &initS);

        const int labNum = getNextFreeLabel();
        auto nextStLab = new SgLabel(labNum);
        point->setLabel(*nextStLab);

        SgStatement* gotoBlock = new SgIfStmt(*new SgVarRefExp(loadS[0]) == *new SgValueExp(1));
        gotoBlock->addComment("! GOTO CP\n");

        gotoBlock->insertStmtAfter(*new SgGotoStmt(*nextStLab), *gotoBlock);
        SgAssignStmt* init = new SgAssignStmt(*new SgVarRefExp(loadS[0]), *new SgValueExp(0));
        gotoBlock->insertStmtAfter(*init, *gotoBlock);
        loadBlock->insertStmtAfter(*gotoBlock, *loadBlock->controlParent());

        checkForToReplace(gotoBlock, point);
    }

    processModules(file, allFuncInfo);
}

static string cuttingType(const string& all_decl_with_init)
{
    auto iter = all_decl_with_init.find("::");
    if (iter == string::npos)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return all_decl_with_init.substr(0, iter);
}

static SgExpression* moveSaveAssignToMod(SgExpression* ex, SgStatement*& saveModule, 
                                         map<string, SgStatement*>& declLists, const string& procName, bool withInit)
{
    if (saveModule == NULL)
        createModule(saveModule, "spf_module_save_");

    if (withInit)
    {
        if (ex->variant() != ASSGN_OP)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }
    else
    {
        if (ex->variant() != VAR_REF && ex->variant() != ARRAY_REF)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }
    
    SgSymbol* copy = (withInit) ? &ex->lhs()->symbol()->copy() : &ex->symbol()->copy();
    copy->changeName((string(copy->identifier()) + "_" + procName).c_str());

    SgStatement* decl = NULL;
    if (withInit)
    {
        vector<SgExpression*> inits = { ex->rhs()->copyPtr() };
        decl = makeDeclaration(NULL, { ex->lhs()->symbol() }, &inits);
    }
    else
        decl = makeDeclaration(NULL, { ex->symbol() }, NULL);
    
    const string typeKey = cuttingType(decl->unparse());    
    if (withInit)
    {
        decl->expr(0)->lhs()->setLhs(withInit ? ex->lhs()->copyPtr() : ex->copyPtr());
        decl->expr(0)->lhs()->lhs()->setSymbol(copy);
    }
    else
        decl->expr(0)->lhs()->setSymbol(copy);

    auto it = declLists.find(typeKey);
    if (it == declLists.end())
    {
        declLists.insert(it, make_pair(typeKey, decl));
        saveModule->insertStmtAfter(*decl, *saveModule);
    }
    else
        it->second->setExpression(0, new SgExpression(EXPR_LIST, decl->expr(0)->lhs(), it->second->expr(0)));

    return new SgExpression(RENAME_NODE, new SgVarRefExp((withInit) ? ex->lhs()->symbol() : ex->symbol()), new SgVarRefExp(copy));
}

void convertSaveToModule(SgFile* file)
{
    int numF = file->numberOfFunctions();
    SgStatement* saveModule = NULL;
    map<string, SgStatement*> declLists;

    for (int ff = 0; ff < numF; ++ff)
    {
        vector<SgExpression*> renames;

        SgStatement* func = file->functions(ff);
        
        string procName = file->functions(ff)->symbol()->identifier();
        set<SgStatement*> toRem;
        vector<SgValueExp*> dataValues;
        map<string, string> dataRenames;

        set<string> symbolsDone;

        vector<int> varsToProcess = { VAR_DECL_90 , DIM_STAT };
        for (int z = 0; z < 2; ++z)
        {
            SgStatement* st = func->lexNext();
            while (st && !isSgExecutableStatement(st))
            {
                if (st->variant() == SAVE_DECL)
                {
                    printf("save in %d %s\n", st->lineNumber(), st->fileName());
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }

                if (st->variant() == DATA_DECL && z == 0)
                {
                    if (saveModule == NULL)
                        createModule(saveModule, "spf_module_save_");

                    SgStatement* copyData = st->copyPtr();
                    dataValues.push_back((SgValueExp*)copyData->expr(0));
                    saveModule->lastNodeOfStmt()->insertStmtBefore(*copyData, *saveModule);
                    toRem.insert(st);
                }

                if (st->variant() == varsToProcess[z])
                {
                    SgExpression* list = st->expr(0);
                    vector<SgExpression*> newList;
                    bool changed = false;
                    while (list)
                    {
                        if (list->lhs()->variant() == ASSGN_OP)
                        {
                            changed = true;
                            if (symbolsDone.find(list->lhs()->lhs()->symbol()->identifier()) != symbolsDone.end())
                            {
                                list = list->rhs();
                                continue;
                            }
                            symbolsDone.insert(list->lhs()->lhs()->symbol()->identifier());
                            //printf("here %d %s - %s\n", st->lineNumber(), st->fileName(), list->lhs()->unparse());
                            renames.push_back(moveSaveAssignToMod(list->lhs(), saveModule, declLists, procName, true));
                        }
                        else
                        {
                            if (list->lhs()->symbol())
                            {
                                bool isInData = (list->lhs()->symbol()->attributes() & DATA_BIT) != 0;
                                if (isInData)
                                {
                                    changed = true;
                                    if (symbolsDone.find(list->lhs()->symbol()->identifier()) != symbolsDone.end())
                                    {
                                        list = list->rhs();
                                        continue;
                                    }
                                    symbolsDone.insert(list->lhs()->symbol()->identifier());

                                    renames.push_back(moveSaveAssignToMod(list->lhs(), saveModule, declLists, procName, false));
                                    string left = renames.back()->lhs()->symbol()->identifier();
                                    convertToUpper(left);
                                    string right = renames.back()->rhs()->symbol()->identifier();
                                    convertToUpper(right);
                                    dataRenames[left] = right;
                                }
                                else
                                    newList.push_back(list->lhs());
                            }
                            else
                                newList.push_back(list->lhs());
                        }
                        list = list->rhs();
                    }

                    if (newList.size() && changed)
                    {
                        reverse(newList.begin(), newList.end());
                        st->setExpression(0, makeExprList(newList));
                    }
                    else if (newList.size() == 0 && changed)
                        toRem.insert(st);
                }
                st = st->lexNext();
            }
        }

        for (auto& elem : toRem)
            elem->deleteStmt();

        if (renames.size())
        {
            SgSymbol* modS = new SgSymbol(VARIABLE_NAME, saveModule->symbol()->identifier());
            SgStatement* use = new SgStatement(USE_STMT, NULL, modS, makeExprList(renames), NULL, NULL);
            func->insertStmtAfter(*use, *func);
            
            for (auto& dataV : dataValues)
            {
                char* value = dataV->thellnd->entry.string_val;
                string dataS(value);
                convertToUpper(dataS);
                dataS = preprocDataString(dataS);

                for (auto& elem : dataRenames)
                {
                    auto it = dataS.find(elem.first);
                    if (it != string::npos && 
                        (dataS[it + elem.first.size()] == ' ' || dataS[it + elem.first.size()] == '/') &&
                        (dataS[it - 1] == ',' || dataS[it - 1] == ' ' || dataS[it - 1] == '/'))
                    {
                        dataS = dataS.substr(0, it) + elem.second + dataS.substr(it + elem.first.size());
                    }
                }

                value = (char*)malloc(dataS.size() + 1);
                value[dataS.size()] = '\0';
                memcpy(value, dataS.c_str(), sizeof(char) * dataS.size());
                dataV->thellnd->entry.string_val = value;
            }
        }
    }

    if (saveModule)
        saveModule->insertStmtAfter(*new SgImplicitStmt(NULL), *saveModule);
}

static int getUnit(SgExpression* spec)
{
    int unit = -1;    
    while (spec)
    {
        if (spec->lhs() && spec->lhs()->variant() == SPEC_PAIR)
        {
            if (spec->lhs()->lhs()->variant() == KEYWORD_VAL)
            {                
                SgKeywordValExp* val = (SgKeywordValExp*)(spec->lhs()->lhs());
                if (spec->lhs()->rhs()->variant() == INT_VAL)
                {
                    if (val->value() == string("unit"))
                        return spec->lhs()->rhs()->valueInteger();
                }
                else if (spec->lhs()->rhs()->variant() == KEYWORD_VAL)
                {
                    SgKeywordValExp* valR = (SgKeywordValExp*)(spec->lhs()->rhs());
                    if (val->value() == string("unit") && valR->value() == string("*"))
                        return -2;
                }
            }
        }
        else if (spec->variant() == INT_VAL)
            return spec->valueInteger();
        spec = spec->rhs();
    }

    return unit;
}

void preprocessOpenOperators(SgFile* file, map<int, UserFiles>& filesInfo)
{
    int numF = file->numberOfFunctions();
    set<SgStatement*> unrec;
    for (int ff = 0; ff < numF; ++ff)
    {
        SgStatement* func = file->functions(ff);
        for (SgStatement* st = func->lexNext(); st != func->lastNodeOfStmt(); st = st->lexNext())
        {
            if (st->variant() == OPEN_STAT)
            {
                int unit = getUnit(((SgIOControlStmt*)st)->controlSpecList());
                if (unit >= 0)
                    filesInfo[unit].placesOpen.push_back(st);
                else if (unit != -2)
                    unrec.insert(st);
            }
            else if (st->variant() == CLOSE_STAT)
            {
                int unit = getUnit(((SgIOControlStmt*)st)->controlSpecList());
                if (unit >= 0)
                    filesInfo[unit].placesClose.push_back(st);
                else if (unit != -2)
                    unrec.insert(st);
            }
            else if (st->variant() == WRITE_STAT)
            {
                int unit = getUnit(((SgInputOutputStmt*)st)->specList());
                if (unit >= 0)
                    filesInfo[unit].placesWrite.push_back(st);
                else if (unit != -2)
                    unrec.insert(st);
            }
            else if (st->variant() == READ_STAT)
            {
                int unit = getUnit(((SgInputOutputStmt*)st)->specList());
                if (unit >= 0)
                    filesInfo[unit].placesRead.push_back(st);
                else if (unit != -2)
                    unrec.insert(st);
            }
            else if (st->variant() == CONTAINS_STMT)
                break;
        }
    }

    for (auto& elem : unrec)
        elem->unparsestdout();

    /*for (auto& elem : filesInfo)
    {
        if (elem.second.placesClose.size() != elem.second.placesOpen.size())
            if (elem.second.placesClose.size() > 0 && elem.second.placesOpen.size() > 0)
                printf("UNIT = %d has not equal open and close\n", elem.first);

        if (elem.second.placesClose.size() == 0 && elem.second.placesWrite.size() > 0 && elem.second.placesRead.size() == 0)
            printf("UNIT = %d need to save\n", elem.first);
    }*/
}