#include "leak_detector.h"

#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <set>

#include "SgUtils.h"
#include "CommonBlock.h"
#include "graph_calls.h"
#include "expr_transform.h"

#include "dvm.h"
#include "IR.h"
#include "CFGraph.h"

using namespace std;
using namespace SAPFOR;

static map<string, pair<SAPFOR::Argument*, pair<string, SgSymbol*>>> dictArgs;
static map<SAPFOR::Argument*, pair<string, SgSymbol*>> dictArgsByArg;

static int lastNumReg = 0;

int Argument::lastNumArg = 0;
int Instruction::lastNumInstr = 0;

static SAPFOR::Argument* processExpression(SgExpression* ex, vector<IR_Block*>& blocks, const FuncInfo* func, 
                                           const vector<pair<const Variable*, CommonBlock*>>& commonVars, SAPFOR::Argument *isLeft = NULL);

static SAPFOR::Argument* createRegister()
{
    const string newName = "_reg" + to_string(lastNumReg++);
    auto it = dictArgs.find(newName);
    if (it == dictArgs.end())
        it = dictArgs.insert(it, make_pair(newName, make_pair(new SAPFOR::Argument(CFG_ARG_TYPE::REG, CFG_MEM_TYPE::LOCAL_, newName), make_pair("reg", (SgSymbol*)NULL))));
    else if (it->second.first->getType() != CFG_ARG_TYPE::REG)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return it->second.first;
}

string getNameByArg(SAPFOR::Argument* arg)
{
    auto it = dictArgsByArg.find(arg);
    if (it != dictArgsByArg.end())
        return it->second.first;
    return "";
}

SgSymbol* getSybolByArg(SAPFOR::Argument* arg)
{
    auto it = dictArgsByArg.find(arg);
    if (it != dictArgsByArg.end())
        return it->second.second;
    return NULL;
}

string createName(const vector<pair<const Variable*, CommonBlock*>>& commonVars, const FuncInfo* func,
                         SgSymbol* s, SgStatement* scope, CFG_MEM_TYPE& mType)
{
    SgSymbol* resultName = NULL;
    auto isFunc = isSgFuncHedrStmt(func->funcPointer->GetOriginal());
    if (isFunc)
        resultName = isFunc->resultName();

    string newName = "_";
    SgSymbol* var = OriginalSymbol(s);

    checkNull(var, convertFileName(__FILE__).c_str(), __LINE__);

    bool inCommon = false;
    if (commonVars.size() && var == s)
    {
        const string name = s->identifier();
        for (auto& var : commonVars)
        {
            if (var.first->getName() == name)
            {
                const int pos = var.first->getPosition();
                auto groupedByPos = var.second->getGroupedVars();
                if (groupedByPos.count(pos) == 0)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                string firstSynonim = groupedByPos[pos][0]->getName();

                newName += var.second->getName() + string("%") + firstSynonim;
                inCommon = true;
                mType = CFG_MEM_TYPE::COMMON_;
                break;
            }
        }
    }
    
    if (!inCommon)
    {
        if (s->variant() == FUNCTION_NAME || (var == resultName && resultName))
        {
            newName += func->funcName;
            mType = CFG_MEM_TYPE::FUNC_RES_;
        }
        else if (s->variant() == PROCEDURE_NAME)
        {
            newName += var->identifier();
            mType = CFG_MEM_TYPE::LOCAL_;
        }
        else if (s->variant() == FIELD_NAME)
        {
            newName += var->identifier();
            mType = CFG_MEM_TYPE::FILED_;
        }
        else
        {
            auto prog = isSgProgHedrStmt(scope);
            if (prog)
            {
                const string varName = var->identifier();
                for (int z = 0; z < func->funcParams.identificators.size(); ++z)
                {
                    if (func->funcParams.identificators[z] == varName)
                    {
                        newName += prog->nameWithContains() + string("%") + var->identifier() + "%" + to_string(z);
                        mType = CFG_MEM_TYPE::FUNC_PARAM_;
                        break;
                    }
                }

                if (newName == "_") // not inited
                {
                    newName += prog->nameWithContains() + string("%") + var->identifier();
                    mType = CFG_MEM_TYPE::LOCAL_;
                }
            }
            else
            {
                newName += scope->symbol()->identifier() + string("%") + var->identifier();
                mType = CFG_MEM_TYPE::MODULE_;
            }
        }
    }

    return newName;
}

SAPFOR::Argument* createArg(const string& fullName, const string& name, CFG_MEM_TYPE mType)
{
    auto it = dictArgs.find(fullName);
    if (it == dictArgs.end())
        it = dictArgs.insert(it, make_pair(fullName, make_pair(new SAPFOR::Argument(CFG_ARG_TYPE::VAR, mType, fullName), make_pair(name, (SgSymbol*)NULL))));
    else if (it->second.first->getType() != CFG_ARG_TYPE::VAR && it->second.first->getMemType() != mType)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return it->second.first;
}

static SAPFOR::Argument* createArg(SgSymbol* var, const vector<pair<const Variable*, CommonBlock*>>& commonVars, const FuncInfo* func)
{
    SgStatement* where = OriginalSymbol(var)->scope();
    if (!where)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    CFG_MEM_TYPE mType(CFG_MEM_TYPE::NONE_);
    const string newName = createName(commonVars, func, var, where, mType);
    auto it = dictArgs.find(newName);
    if (it == dictArgs.end())
        it = dictArgs.insert(it, make_pair(newName, make_pair(new SAPFOR::Argument(CFG_ARG_TYPE::VAR, mType, newName), make_pair(var->identifier(), var))));
    else if (it->second.first->getType() != CFG_ARG_TYPE::VAR && it->second.first->getMemType() != mType)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return it->second.first;
}

static SAPFOR::Argument* createConstArg(SgExpression* exp)
{
    string newName = "---";

    auto value = isSgValueExp(exp);
    int var = exp->variant();
    CFG_ARG_TYPE type = CFG_ARG_TYPE::CONST;

    if (var == INT_VAL)
        newName = to_string(value->intValue());
    else if (var == FLOAT_VAL)
        newName = value->floatValue();
    else if (var == DOUBLE_VAL)
        newName = value->doubleValue();
    else if (var == CHAR_VAL)
        newName = value->charValue();
    else if (var == COMPLEX_VAL)
    {
        const string real = value->realValue()->unparse();
        const string im = value->imaginaryValue()->unparse();
        newName = real + "%" + im;
    }
    else if (var == BOOL_VAL)
        newName = value->boolValue() ? "TRUE" : "FALSE";
    else if (var == KEYWORD_VAL)
        newName = string("%") + "key_arg_" + isSgKeywordValExp(exp)->value();
    else if (var == CONSTRUCTOR_REF)
    {
        type = CFG_ARG_TYPE::CONSTR_REF;
        newName = string("%") + "constructor_ref_" + to_string(SgStatement::getCurrProcessLine()) + "_" + string(exp->unparse());
    }    
    else if (var == STRING_VAL)
    {
        type = CFG_ARG_TYPE::CONST_STR;
        newName = string("%") + value->stringValue();
    }
    else
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    auto it = dictArgs.find(newName);
    if (it == dictArgs.end())
        it = dictArgs.insert(it, make_pair(newName, make_pair(new SAPFOR::Argument(type, CFG_MEM_TYPE::LOCAL_, type == CFG_ARG_TYPE::CONSTR_REF ? exp->unparse() : newName), make_pair("const", (SgSymbol*)NULL))));
    else if (it->second.first->getType() != type)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return it->second.first;
}

static SAPFOR::Argument* createArrayArg(SgExpression* array_ref, vector<IR_Block*>& blocks, const FuncInfo* func, int& numArgs,
                                        const vector<pair<const Variable*, CommonBlock*>>& commonVars)
{
    auto ref = isSgArrayRefExp(array_ref);
    if (ref == NULL)
    {
        if (array_ref->variant() == STRING_VAL)
            return createConstArg(array_ref);
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }

    SgSymbol* var = ref->symbol();
    SgStatement* where = OriginalSymbol(var)->scope();
    if (!where)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    vector<tuple<int, SAPFOR::Argument*, SAPFOR::Argument*, SAPFOR::Argument*>> args;
    SAPFOR::Argument* empty = NULL;
    numArgs = ref->numberOfSubscripts();
    for (int z = 0; z < numArgs; ++z)
    {
        auto arg = ref->subscript(z);
        if (arg->variant() != DDOT)
            args.push_back(make_tuple(arg->variant(), processExpression(arg, blocks, func, commonVars), empty, empty));
        else
        {
            SAPFOR::Argument *left = NULL, *right = NULL, *step = NULL;
            bool fullRange = false;
            if (arg->lhs())
            {
                if (arg->lhs()->variant() == DDOT)
                {
                    fullRange = true;
                    if (arg->lhs()->lhs())
                        left = processExpression(arg->lhs()->lhs(), blocks, func, commonVars);
                    if (arg->lhs()->rhs())
                        right = processExpression(arg->lhs()->rhs(), blocks, func, commonVars);
                }
                else
                    left = processExpression(arg->lhs(), blocks, func, commonVars);
            }

            if (arg->rhs())
            {
                if (fullRange)
                    step = processExpression(arg->rhs(), blocks, func, commonVars);
                else
                    right = processExpression(arg->rhs(), blocks, func, commonVars);
            }

            args.push_back(make_tuple(DDOT, left, right, step));
        }
    }

    for (auto& arg : args)
    {
        Instruction* instr = NULL;
        if (get<0>(arg) != DDOT)
            instr = new Instruction(CFG_OP::REF, get<1>(arg));
        else
            instr = new Instruction(CFG_OP::RANGE, get<1>(arg), get<2>(arg), get<3>(arg));
        blocks.push_back(new IR_Block(instr));
    }

    CFG_MEM_TYPE mType(CFG_MEM_TYPE::NONE_);
    const string newName = createName(commonVars, func, var, where, mType);
    auto it = dictArgs.find(newName);
    if (it == dictArgs.end())
        it = dictArgs.insert(it, make_pair(newName, make_pair(new SAPFOR::Argument(CFG_ARG_TYPE::ARRAY, mType, newName), make_pair(var->identifier(), var))));
    else if (it->second.first->getType() != CFG_ARG_TYPE::ARRAY && it->second.first->getMemType() != mType)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return it->second.first;
}

static SAPFOR::Argument* createRefArg(SgExpression* exp)
{
    //TODO: assign true type!
    string newName = exp->unparse();
    CFG_ARG_TYPE type = CFG_ARG_TYPE::RECORD;

    auto it = dictArgs.find(newName);
    if (it == dictArgs.end())
        it = dictArgs.insert(it, make_pair(newName, make_pair(new SAPFOR::Argument(type, CFG_MEM_TYPE::LOCAL_, newName), make_pair("ref", (SgSymbol*)NULL))));
    else if (it->second.first->getType() != type)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return it->second.first;
}

static SAPFOR::Argument* createConstArg(int value)
{
    string s = to_string(value);

    auto itS = dictArgs.find(s);
    if (itS == dictArgs.end())
        itS = dictArgs.insert(itS, make_pair(s, make_pair(new SAPFOR::Argument(CFG_ARG_TYPE::CONST, CFG_MEM_TYPE::LOCAL_, s), make_pair("const", (SgSymbol*)NULL))));
    else if (itS->second.first->getType() != CFG_ARG_TYPE::CONST)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return itS->second.first;
}

static SAPFOR::Argument* getFuncArg(const string& fName)
{
    auto it = dictArgs.find(fName);
    if (it == dictArgs.end())
        it = dictArgs.insert(it, make_pair(fName, make_pair(new SAPFOR::Argument(CFG_ARG_TYPE::FUNC, fName), make_pair(fName, (SgSymbol*)NULL))));
    else if (it->second.first->getType() != CFG_ARG_TYPE::FUNC)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    return it->second.first;
}

template<typename funcStatement>
static bool processArgs(funcStatement* call, int num, vector<IR_Block*>& blocks, const FuncInfo* func,
                        const vector<pair<const Variable*, CommonBlock*>>& commonVars, vector<int>* labels = NULL)
{
    bool hasLabelArg = false;

    vector<SAPFOR::Argument*> args;
    for (int z = 0; z < num; ++z)
    {
        if (call->arg(z)->variant() == LABEL_ARG)
        {
            hasLabelArg = true;
            auto labRef = isSgLabelRefExp(call->arg(z)->lhs());
            if (labRef == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            args.push_back(createConstArg(labRef->label()->getLabNumber()));

            if (labels)
                labels->push_back(labRef->label()->getLabNumber());
        }
        else
            args.push_back(processExpression(call->arg(z), blocks, func, commonVars));
    }

    for (auto& arg : args)
    {
        Instruction* instr = new Instruction(CFG_OP::PARAM, arg);
        blocks.push_back(new IR_Block(instr));
    }

    return hasLabelArg;
}

static SAPFOR::Argument* processExpression(SgExpression* ex, vector<IR_Block*>& blocks, const FuncInfo* func, 
                                           const vector<pair<const Variable*, CommonBlock*>>& commonVars, SAPFOR::Argument* isLeft)
{
    static map<int, CFG_OP> typeMap = { { ADD_OP, CFG_OP::ADD },
                                        { MULT_OP, CFG_OP::MULT },
                                        { SUBT_OP, CFG_OP::SUBT },
                                        { DIV_OP, CFG_OP::DIV },
                                        { UNARY_ADD_OP, CFG_OP::UN_ADD },
                                        { MINUS_OP, CFG_OP::UN_MINUS },
                                        { GT_OP, CFG_OP::GT },
                                        { GE_OP, CFG_OP::GE },
                                        { GTEQL_OP, CFG_OP::GE },
                                        { LT_OP, CFG_OP::LT },
                                        { LE_OP, CFG_OP::LE },
                                        { LTEQL_OP, CFG_OP::LE },
                                        { OR_OP, CFG_OP::OR },
                                        { AND_OP, CFG_OP::AND },
                                        { EQ_OP, CFG_OP::EQ },
                                        { EQV_OP, CFG_OP::EQV },
                                        { NEQV_OP, CFG_OP::NEQV },
                                        { NOTEQL_OP, CFG_OP::NEQV },
                                        { NOT_OP, CFG_OP::NOT },
                                        { FUNC_CALL, CFG_OP::F_CALL },
                                        { EXP_OP, CFG_OP::POW },
                                        { CONCAT_OP, CFG_OP::CONCAT } };

    if (ex)
    {
        const int var = ex->variant();
        if ((var == VAR_REF || var == CONST_REF || var == LABEL_REF) && !ex->lhs() && !ex->rhs()) // variable reference
        {
            if (var == CONST_REF)
            {
                SgExpression* value = CalculateInteger(ex->copyPtr());
                if (isSgValueExp(value))
                    return createConstArg(value);
                else
                    return createArg(ex->symbol(), commonVars, func);
            }
            else if (var == LABEL_REF)
            {
                auto labRef = isSgLabelRefExp(ex);
                if (labRef == NULL)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                return createConstArg(labRef->label()->getLabNumber());
            }
            else
                return createArg(ex->symbol(), commonVars, func);
        }
        else if (isSgValueExp(ex))
            return createConstArg(ex);
        else if (var == ADD_OP || var == MULT_OP || var == SUBT_OP || var == DIV_OP ||
                 var == GT_OP || var == GE_OP || var == LT_OP || var == LE_OP ||
                 var == OR_OP || var == AND_OP || var == EQ_OP || var == EQV_OP ||
                 var == NEQV_OP || var == EXP_OP || var == NOTEQL_OP || var == LTEQL_OP ||
                 var == GTEQL_OP || var == CONCAT_OP)
        {
            auto arg1 = processExpression(ex->lhs(), blocks, func, commonVars);
            auto arg2 = processExpression(ex->rhs(), blocks, func, commonVars);
            auto reg = createRegister();
            Instruction* instr = new Instruction(typeMap[var], arg1, arg2, reg);
            blocks.push_back(new IR_Block(instr));

            return reg;
        }
        else if (var == MINUS_OP || var == UNARY_ADD_OP || var == NOT_OP)
        {
            auto arg1 = processExpression(ex->lhs(), blocks, func, commonVars);
            auto reg = createRegister();
            Instruction* instr = new Instruction(typeMap[var], arg1, NULL, reg);
            blocks.push_back(new IR_Block(instr));

            return reg;
        }
        else if (var == ARRAY_REF)
        {
            int numArgs = 0;
            auto arg1 = createArrayArg(ex, blocks, func, numArgs, commonVars);
            
            if (numArgs == 0)
                return arg1;

            auto reg = isLeft ? NULL : createRegister();
            Instruction* instr = new Instruction(isLeft ? CFG_OP::STORE : CFG_OP::LOAD, arg1, createConstArg(numArgs), isLeft ? isLeft : reg, NULL, ex);
            blocks.push_back(new IR_Block(instr));
            return reg;
        }
        else if (var == ARRAY_OP) // A([L] : [R] [:[Step]])
        {
            SgExpression* ref = ex->lhs();
            SAPFOR::Argument* arrayRef = NULL;
            if (ref->lhs() || ref->rhs())
                arrayRef = processExpression(ref, blocks, func, commonVars);

            SgExpression* range = ex->rhs();
            if (range->variant() != DDOT)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            bool hasStep = range->lhs()->variant() == DDOT;
            SAPFOR::Argument *step = NULL, *left = NULL, *right = NULL;
            if (hasStep)
            {
                step = processExpression(range->rhs(), blocks, func, commonVars);
                range = range->lhs();
            }

            if (range->lhs())
                left = processExpression(range->lhs(), blocks, func, commonVars);
            if (range->rhs())
                right = processExpression(range->rhs(), blocks, func, commonVars);
            
            Instruction* instr = new Instruction(CFG_OP::RANGE, left, right, step);
            blocks.push_back(new IR_Block(instr));

            int numArgs = 0;
            auto arg1 = arrayRef ? arrayRef : createArrayArg(ref, blocks, func, numArgs, commonVars);

            auto reg = isLeft ? NULL : createRegister();
            instr = new Instruction(isLeft ? CFG_OP::STORE : CFG_OP::LOAD, arg1, createConstArg(1), isLeft ? isLeft : reg, NULL, ex);
            blocks.push_back(new IR_Block(instr));
            return reg;
        }
        else if (var == FUNC_CALL)
        {
            auto fCall = isSgFunctionCallExp(ex);
            bool hasLabelArgs = processArgs(fCall, fCall->numberOfArgs(), blocks, func, commonVars);

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

            string callName = "_ERROR_";
            for (int z = 0; z < func->callsFromDetailed.size() && callName == "_ERROR_"; ++z)
                if (func->callsFromDetailed[z].pointerDetailCallsFrom.first == ex)
                    callName = func->callsFromDetailed[z].detailCallsFrom.first;

            SAPFOR::Argument* fArg = getFuncArg(callName);

            auto reg = createRegister();
            Instruction* instr = new Instruction(typeMap[var], fArg, createConstArg(fCall->numberOfArgs()), reg, NULL, ex);
            blocks.push_back(new IR_Block(instr));
            return reg;
        }
        else if (var == KEYWORD_ARG)
        {
            auto keyW = isSgKeywordArgExp(ex);

            if (!keyW->lhs() || !keyW->rhs())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (keyW->lhs()->variant() != KEYWORD_VAL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            auto reg = createRegister();
            Instruction* instr = NULL;

            auto kewVal = isSgKeywordValExp(keyW->lhs());
            if (kewVal->value() == string("kind") || kewVal->value() == string("len"))
                instr = new Instruction(CFG_OP::CAST, processExpression(keyW->value(), blocks, func, commonVars), createConstArg(keyW->lhs()), reg);
            else
                instr = new Instruction(CFG_OP::ASSIGN, processExpression(keyW->value(), blocks, func, commonVars), createConstArg(keyW->lhs()), reg);

            blocks.push_back(new IR_Block(instr));
            return reg;
        }
        else if (var == RECORD_REF)
        {
            auto reg = isLeft ? NULL : createRegister();
            Instruction* instr = new Instruction(isLeft ? CFG_OP::REC_REF_STORE : CFG_OP::REC_REF_LOAD, createRefArg(ex), NULL, isLeft ? isLeft : reg);
            blocks.push_back(new IR_Block(instr));
            return reg;


            //TODO: need to add more detalization
            vector<SgExpression*> listOfRecods;
            SgExpression* curr = ex;

            while (curr->variant() == RECORD_REF)
            {
                listOfRecods.push_back(curr->rhs());
                curr = curr->lhs();
            }
            listOfRecods.push_back(curr);
            std::reverse(listOfRecods.begin(), listOfRecods.end());

            //recExpressionPrint(ex);

            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            
            SAPFOR::Argument* returnReg = NULL;
            for (int z = 0; z < listOfRecods.size(); z++)
            {
                auto& elem = listOfRecods[z];
                bool isLast = (listOfRecods.size() - 1) == z;

                SAPFOR::Argument* arg = NULL;
                if (elem->variant() == ARRAY_REF && elem->lhs())
                {
                    auto copy = elem->copyPtr();
                    SgExpression* left = copy->lhs();
                    copy->setLhs(NULL);
                    arg = processExpression(copy, blocks, func, commonVars, isLeft);

                    auto reg = createRegister();
                    if (returnReg == NULL)
                    {
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    }
                    else
                    {
                        Instruction* instr = new Instruction(CFG_OP::REC_REF, returnReg, arg, reg);
                        blocks.push_back(new IR_Block(instr));
                    }
                    returnReg = reg;

                    if (isLast)
                    {
                        copy->setLhs(left);
                        processExpression(copy, blocks, func, commonVars, isLeft);

                        auto lastType = blocks.back()->getInstruction()->getOperation();
                        if (lastType != CFG_OP::STORE && lastType != CFG_OP::LOAD)
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        blocks.back()->getInstruction()->setArg1(reg);

                        arg = NULL;
                    }
                }
                else
                    arg = processExpression(elem, blocks, func, commonVars, isLeft);

                auto reg = createRegister();
                if (arg)
                {
                    if (returnReg == NULL)
                    {
                        Instruction* instr = new Instruction(CFG_OP::LOAD, arg, NULL, reg, NULL, ex);
                        blocks.push_back(new IR_Block(instr));
                    }
                    else
                    {
                        Instruction* instr = new Instruction(CFG_OP::REC_REF, returnReg, arg, reg);
                        blocks.push_back(new IR_Block(instr));
                    }
                }
                returnReg = reg; // return last
            }
            return returnReg;
        }
        else if (var == CONSTRUCTOR_REF)
            return createConstArg(ex);
        else if (var == SPEC_PAIR)
            return processExpression(ex->rhs(), blocks, func, commonVars);
        else
        {
            __spf_print(1, "unknown expression '%s'\n", tag[ex->variant()]);
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
    }
    
    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    return NULL;
}

static SgStatement* processLabel(SgStatement* st, const int firstInstr, const vector<IR_Block*>& blocks, map<int, Instruction*>& labels)
{
    const int var = st->variant();
    if (var == FORMAT_STAT)
        return st;

    if (st->label())
    {
        int lab = st->label()->getLabNumber();
        if (labels.count(lab) == 0)
            labels[lab] = blocks[firstInstr]->getInstruction();
    }
    return st;
}

static SgStatement* getNearestDo(SgStatement* st, SgSymbol* named)
{
    while (st)
    {
        int var = st->variant();
        if (var == FOR_NODE || var == WHILE_NODE)
        {
            if (var == FOR_NODE)
            {                
                SgSymbol* symb = isSgForStmt(st)->constructName();
                if (!named || !symb)
                    break;
                else if (symb->identifier() == string(named->identifier()))
                    break;
            }
            else
                break;
        }

        st = st->controlParent();
        if (st == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }
    return st;
}

static void findCycleExit(int start, int end, int type, const vector<IR_Block*>& blocks, SgStatement* curr, int jumpTo)
{    
    for (int z = start; z < end; ++z)
    {
        if (blocks[z]->getInstruction()->getOperation() == CFG_OP::JUMP)
            if (blocks[z]->getInstruction()->getOperator())
                if (blocks[z]->getInstruction()->getOperator()->variant() == type)
                {
                    SgSymbol* named = NULL;
                    if (type == CYCLE_STMT)
                        named = isSgCycleStmt(blocks[z]->getInstruction()->getOperator())->constructName();
                    else if (type == EXIT_STMT)
                        named = isSgExitStmt(blocks[z]->getInstruction()->getOperator())->constructName();

                    if (getNearestDo(blocks[z]->getInstruction()->getOperator(), named) == curr)
                        blocks[z]->getInstruction()->setArg1(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jumpTo)));
                }
    }
}

static void findReturn(int start, int end, const vector<IR_Block*>& blocks, int jumpTo)
{
    for (int z = start; z < end; ++z)
    {
        if (blocks[z]->getInstruction()->getOperation() == CFG_OP::JUMP)
            if (blocks[z]->getInstruction()->getOperator())
                if (blocks[z]->getInstruction()->getOperator()->variant() == RETURN_STAT)
                    blocks[z]->getInstruction()->setArg1(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jumpTo)));
    }
}

static void addGotoTo(int labNum, vector<IR_Block*>& blocks, SgStatement* st = NULL)
{
    auto arg1 = new SAPFOR::Argument(CFG_ARG_TYPE::LAB, CFG_MEM_TYPE::NONE_, to_string(labNum));
    blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP, arg1, NULL, NULL, st)));
}

static int getInstructionNumAfterIfJump(int currBlockSize, int shiftBlockNumbers, int countAfterIfInstr)
{
    return currBlockSize + 1 + countAfterIfInstr + shiftBlockNumbers;
}

static void createCompoundGoto(int num, SgExpression* lab, SgStatement* st, 
                               vector<IR_Block*>& blocks, const int blockShift, 
                               SAPFOR::Argument* arg, bool isComputed)
{
    for (int z = 0; z < num; ++z, lab = lab->rhs())
    {
        SgLabel* lbl = ((SgLabelRefExp*)(lab->lhs()))->label();
        auto reg = createRegister();
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::EQ, arg, createConstArg(isComputed ? (z + 1) : (lbl->getLabNumber())), reg)));

        const int jump_to = getInstructionNumAfterIfJump(blocks.size(), blockShift, 1);
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, reg, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jump_to)))));

        addGotoTo(lbl->getLabNumber(), blocks, st);
    }
}

static SgStatement* processStatement(SgStatement* st, vector<IR_Block*>& blocks, map<int, Instruction*>& labels, const FuncInfo* func,
                                     const vector<pair<const Variable*, CommonBlock*>>& commonVars, const CFG_Settings& settings)
{
    const bool aloil_flag = settings.atLeastOneIterInLoop;

    const int var = st->variant();
    //printf("%d %d\n", st->lineNumber(), st->variant());

    static const set<int> skip = { FORMAT_STAT, OPEN_STAT, CLOSE_STAT, INQUIRE_STAT, REWIND_STAT, BACKSPACE_STAT,
                                   ENDFILE_STAT, PAUSE_NODE // <--- TODO
                                  };

    const int blockShift = blocks.size() != 0 ? blocks[0]->getNumber() : Instruction::getNextInstrNum();
    const int firstBlock = blocks.size();
    if (var == ASSIGN_STAT)
    {
        if (!(st->expr(1)->variant() == CONSTRUCTOR_REF && st->lineNumber() < 0)) // skip init: h(2) = /(2,3)
        {
            auto arg1 = processExpression(st->expr(1), blocks, func, commonVars);
            auto res = processExpression(st->expr(0), blocks, func, commonVars, arg1);

            if (res)
                blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, arg1, NULL, res)));

            for (int z = firstBlock; z < blocks.size(); ++z)
                blocks[z]->getInstruction()->setOperator(st);

            processLabel(st, firstBlock, blocks, labels);
        }
    }
    else if (var == GOTO_NODE)
    {
        addGotoTo(((SgGotoStmt*)st)->branchLabel()->getLabNumber(), blocks, st);
        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == COMGOTO_NODE)
    {
        auto comGoto = isSgComputedGotoStmt(st);
        auto regCond = processExpression(comGoto->exp(), blocks, func, commonVars);
        
        createCompoundGoto(comGoto->numberOfTargets(), comGoto->labelList(), st, blocks, blockShift, regCond, true);
        
        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);
        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == ASSGOTO_NODE)
    {
        auto ass = isSgAssignedGotoStmt(st);
        auto var = createArg(ass->symbol(), commonVars, func);

        createCompoundGoto(ass->numberOfTargets(), ass->labelList(), st, blocks, blockShift, var, false);
        
        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);
        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == IF_NODE || var == ELSEIF_NODE)
    {
        auto ifSt = (SgIfStmt*)(st);
        auto last = st->lastNodeOfStmt();

        for (SgStatement* s = st; s != st->lastNodeOfStmt(); s = s->lexNext())
        {
            if (s->variant() == ELSEIF_NODE && s != st && s->controlParent() == st)
            {
                last = s->lexPrev();
                break;
            }
        }
        if (ifSt->falseBody())
            last = ifSt->falseBody()->lexPrev();

        auto regCond = processExpression(ifSt->conditional(), blocks, func, commonVars);
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, regCond)));
        int jump_if = blocks.size() - 1;

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        if (ifSt != last) // not empty
        {
            SgStatement* s = st;
            do
            {
                s = s->lexNext();
                const int firstInstr = blocks.size();
                s = processLabel(processStatement(s, blocks, labels, func, commonVars, settings), firstInstr, blocks, labels);
            } while (s != last);
        }

        if (ifSt->falseBody())
        {
            if (var == IF_NODE)
                addGotoTo(-st->lineNumber(), blocks, st);
            else // else_if
            {
                auto cp = st->controlParent();
                while (cp && cp->variant() != IF_NODE)
                    cp = cp->controlParent();
                addGotoTo(-cp->lineNumber(), blocks, st);
            }
        }

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, (ifSt->falseBody()) ? ifSt->falseBody() : st->lastNodeOfStmt())));
        blocks[jump_if]->getInstruction()->setArg2(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(blocks.back()->getNumber())));

        processLabel(st, firstBlock, blocks, labels);
        st = last;
    }
    else if (var == LOGIF_NODE)
    {
        auto ifLog = isSgLogIfStmt(st);

        auto regCond = processExpression(ifLog->conditional(), blocks, func, commonVars);
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, regCond)));
        int jump_if = blocks.size() - 1;

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);
        
        processLabel(processStatement(ifLog->lexNext(), blocks, labels, func, commonVars, settings), blocks.size(), blocks, labels);

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, st)));
        blocks[jump_if]->getInstruction()->setArg2(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(blocks.back()->getNumber())));

        processLabel(ifLog, firstBlock, blocks, labels);
        st = st->lexNext();
    }
    else if (var == ARITHIF_NODE)
    {
        SgExpression* cond = st->expr(0);
        SgExpression* lb = st->expr(1);
        SgLabel* arith_lab[3];

        int i = 0;
        while (lb)
        {
            SgLabel* lab = ((SgLabelRefExp*)(lb->lhs()))->label();
            arith_lab[i] = lab;
            i++;
            lb = lb->rhs();
        }
        
        auto regCond = processExpression(st->expr(0), blocks, func, commonVars);
        auto argZero = createConstArg(0);

        if (arith_lab[1]->getLabNumber() == arith_lab[2]->getLabNumber())
        {            
            auto reg = createRegister();
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::LT, regCond, argZero, reg)));
            const int jump_to = getInstructionNumAfterIfJump(blocks.size(), blockShift, 1);
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, reg, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jump_to)))));

            addGotoTo(arith_lab[0]->getLabNumber(), blocks);
            addGotoTo(arith_lab[1]->getLabNumber(), blocks);
        }
        else if (arith_lab[0]->getLabNumber() == arith_lab[1]->getLabNumber())
        {
            auto reg = createRegister();
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::LE, regCond, argZero, reg)));
            const int jump_to = getInstructionNumAfterIfJump(blocks.size(), blockShift, 1);
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, reg, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jump_to)))));

            addGotoTo(arith_lab[1]->getLabNumber(), blocks);
            addGotoTo(arith_lab[2]->getLabNumber(), blocks);
        }
        else
        {
            auto reg = createRegister();
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::LT, regCond, argZero, reg)));
            int jump_to = getInstructionNumAfterIfJump(blocks.size(), blockShift, 1);
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, reg, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jump_to)))));
            addGotoTo(arith_lab[0]->getLabNumber(), blocks);

            auto reg1 = createRegister();
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::EQ, regCond, argZero, reg1)));
            jump_to = getInstructionNumAfterIfJump(blocks.size(), blockShift, 1);
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, reg1, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jump_to)))));
            addGotoTo(arith_lab[1]->getLabNumber(), blocks);
            addGotoTo(arith_lab[2]->getLabNumber(), blocks);
        }

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == FOR_NODE) // MAX (INT ((end - start + step) / step), 0)
    {        
        SgForStmt* forSt = isSgForStmt(st);
        SgStatement* last = forSt->lastNodeOfStmt();

        auto argZero = createConstArg(0);
        auto idxArg = createRegister();
        
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, argZero, NULL, idxArg)));

        auto forArg = createArg(forSt->doName(), commonVars, func);
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, processExpression(forSt->start(), blocks, func, commonVars), NULL, forArg)));

        auto regEnd = createRegister();
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, processExpression(forSt->end(), blocks, func, commonVars), NULL, regEnd)));

        auto step = forSt->step();
        if (!step)
            step = new SgValueExp(1);

        auto regStep = createRegister();
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, processExpression(step, blocks, func, commonVars), NULL, regStep)));

        auto iters = createRegister();
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::SUBT, regEnd, forArg, iters)));
        auto iters1 = createRegister();
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ADD, iters, regStep, iters1)));
        auto iters2 = createRegister();
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::DIV, iters1, regStep, iters2)));

        int jump_cond = -1;
        int jump_if_N = -1;
        int loop_body = -1;
        int loop_body_N = -1;
        auto regCond = createRegister();
        if (aloil_flag == false)
        {
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::LT, idxArg, iters2, regCond)));
            jump_cond = blocks.back()->getNumber();

            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, regCond)));
            jump_if_N = blocks.size() - 1;
        }        

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        loop_body = blocks.size();
        loop_body_N = blocks.size();

        for (auto stF = forSt->lexNext(); stF != last; stF = stF->lexNext())
        {
            const int firstInstr = blocks.size();
            stF = processLabel(processStatement(stF, blocks, labels, func, commonVars, settings), firstInstr, blocks, labels);
        }

        if (loop_body < blocks.size())
            loop_body = blocks[loop_body]->getNumber();
        else
            loop_body = -1; // empty loop

        int gotoNextIter = blocks.back()->getNumber() + 1;
        int gotoNextIter_N = blocks.size();

        int lastNum = blocks.size();
        auto tmpReg = createRegister();
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ADD, forArg, regStep, tmpReg, st)));
        if (loop_body == -1) // empty loop
            loop_body = blocks.back()->getNumber();

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ADD, idxArg, createConstArg(1), idxArg, st)));

        for (int z = lastNum; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, tmpReg, NULL, forArg, st)));
        if (aloil_flag == false)
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jump_cond)), NULL, NULL, st)));
        else
        {
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::GE, idxArg, iters2, regCond, st)));
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, regCond, NULL, NULL, st)));
            jump_if_N = blocks.size() - 1;
        }

        findCycleExit(loop_body_N, gotoNextIter_N, CYCLE_STMT, blocks, st, gotoNextIter);

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, last)));
        if (aloil_flag == false)
            blocks[jump_if_N]->getInstruction()->setArg2(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(blocks.back()->getNumber())));
        else
            blocks[jump_if_N]->getInstruction()->setArg2(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(loop_body)));

        findCycleExit(loop_body_N, gotoNextIter_N, EXIT_STMT, blocks, st, blocks.back()->getNumber());

        processLabel(forSt, firstBlock, blocks, labels);
        st = last;
    }
    else if (var == WHILE_NODE)
    {
        SgWhileStmt* forSt = isSgWhileStmt(st);
        SgStatement* last = forSt->lastNodeOfStmt();

        int firstBlock = blocks.size();

        if (forSt->conditional())
        {
            auto regCond = createRegister();
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, processExpression(forSt->conditional(), blocks, func, commonVars), NULL, regCond)));
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, regCond, NULL, NULL)));
        }
        else
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY)));

        int jump_if_N = blocks.size() - 1;
        int first = blocks[firstBlock]->getNumber();

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        for (auto stF = forSt->lexNext(); stF != last; stF = stF->lexNext())
        {
            const int firstInstr = blocks.size();
            stF = processLabel(processStatement(stF, blocks, labels, func, commonVars, settings), firstInstr, blocks, labels);
        }

        findCycleExit(jump_if_N, blocks.size(), CYCLE_STMT, blocks, st, first);
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(first)), NULL, NULL, st)));

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, last)));
        if (blocks[jump_if_N]->getInstruction()->getOperation() == CFG_OP::JUMP_IF)
            blocks[jump_if_N]->getInstruction()->setArg2(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(blocks.back()->getNumber())));
        findCycleExit(jump_if_N, blocks.size(), EXIT_STMT, blocks, st, blocks.back()->getNumber());

        processLabel(forSt, firstBlock, blocks, labels);
        st = last;
    }
    else if (var == STOP_STAT)
    {
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::EXIT, st)));
        processLabel(st, firstBlock, blocks, labels);
    }
    else if ((var == CONT_STAT || var == CONTROL_END))
    {
        if (isSgProgHedrStmt(st->controlParent()))
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, st)));
        else if (st->label())
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, st)));

        if (var == CONTROL_END)
        {
            auto cp = st->controlParent();
            if (cp->variant() == ELSEIF_NODE)
            {
                while (cp && cp->variant() != IF_NODE)
                    cp = cp->controlParent();
            }

            if (cp->variant() == IF_NODE)
            {
                if (!st->label())
                    blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, st)));
                labels[-cp->lineNumber()] = blocks.back()->getInstruction();
            }
        }
    }
    else if (var == RETURN_STAT)
    {
        auto ret = isSgReturnStmt(st);
        if (ret->returnValue())
        {
            auto retVal = processExpression(ret->returnValue(), blocks, func, commonVars);
            
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::ASSIGN, retVal, NULL, getFuncArg(func->funcName), st)));
        }

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP, st))); // need to add argument 
        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == PROC_STAT)
    {
        auto call = isSgCallStmt(st);
        vector<int> labelsOfArgs;
        bool hasLabelArgs = processArgs(call, call->numberOfArgs(), blocks, func, commonVars, &labelsOfArgs);

        string callName = "_ERROR_";
        for (int z = 0; z < func->callsFromDetailed.size() && callName == "_ERROR_"; ++z)
            if (func->callsFromDetailed[z].pointerDetailCallsFrom.first == st)
                callName = func->callsFromDetailed[z].detailCallsFrom.first;

        SAPFOR::Argument* fArg = getFuncArg(callName);

        SAPFOR::Argument* reg = NULL;
        if (hasLabelArgs)
            reg = createRegister();

        Instruction* instr = new Instruction(CFG_OP::F_CALL, fArg, createConstArg(call->numberOfArgs()), reg);
        blocks.push_back(new IR_Block(instr));

        if (hasLabelArgs)
        {
            if (labelsOfArgs.size() == 0 || reg == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        
            for (int z = 0; z < labelsOfArgs.size(); ++z)
            {
                auto& lab = labelsOfArgs[z];
                auto cond = createRegister();
                blocks.push_back(new IR_Block(new Instruction(CFG_OP::EQ, reg, createConstArg(z), cond)));
                const int jump_to = getInstructionNumAfterIfJump(blocks.size(), blockShift, 1);
                blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, cond, new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(jump_to)))));
                addGotoTo(lab, blocks);
            }
        }

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        processLabel(call, firstBlock, blocks, labels);
    }
    else if (var == PRINT_STAT || var == WRITE_STAT || var == READ_STAT)
    {
        SgInputOutputStmt* io = isSgInputOutputStmt(st);

        SgExpression* item = io->itemList();
        vector<vector<SgExpression*>> items;

        vector<SgExpression*> tmp;
        while (item)
        {
            if (item->lhs()->variant() == IOACCESS ||
                item->lhs()->variant() == EXPR_LIST && item->lhs()->lhs()->variant() == IOACCESS ||
                item->lhs()->variant() == EXPR_LIST)
            {
                if (tmp.size()) // flush tmp
                    items.push_back(tmp);
                tmp.clear();

                if (item->lhs()->variant() == EXPR_LIST)
                    tmp.push_back(item->lhs()->lhs());
                else
                    tmp.push_back(item->lhs());

                items.push_back(tmp);
                tmp.clear();
            }
            else
                tmp.push_back(item->lhs());
            item = item->rhs();
        }
        if (tmp.size())
            items.push_back(tmp);
        else if (tmp.size() == 0 && items.size() == 0)
            items.push_back(tmp); // empty call

        for (auto& item : items)
        {            
            if (item.size() == 1 && item[0]->variant() == IOACCESS)
            {
                auto ioAccess = isSgIOAccessExp(item[0]);
                vector<pair<SgSymbol*, tuple<SgExpression*, SgExpression*, SgExpression*>>> loops;

                SgExpression* body = ioAccess->lhs();
                loops.push_back(make_pair(ioAccess->symbol(), make_tuple(ioAccess->rhs()->lhs()->lhs(), ioAccess->rhs()->lhs()->rhs(), ioAccess->rhs()->rhs())));

                while (body->variant() == EXPR_LIST)
                {
                    ioAccess = isSgIOAccessExp(body->lhs());
                    if (ioAccess)
                    {
                        loops.push_back(make_pair(ioAccess->symbol(), make_tuple(ioAccess->rhs()->lhs()->lhs(), ioAccess->rhs()->lhs()->rhs(), ioAccess->rhs()->rhs())));
                        body = body->lhs();
                    }
                    body = body->lhs();
                }

                SgStatement* copyOfSt = st->copyPtr();
                ((SgInputOutputStmt*)copyOfSt)->setItemList(*new SgExprListExp(*body));

                SgStatement* tmpForStat = NULL;
                for (int z = loops.size() - 1; z >= 0; --z)
                {
                    if (z == loops.size() - 1)
                        tmpForStat = new SgForStmt(loops[z].first, get<0>(loops[z].second), get<1>(loops[z].second), get<2>(loops[z].second), copyOfSt);
                    else
                        tmpForStat = new SgForStmt(loops[z].first, get<0>(loops[z].second), get<1>(loops[z].second), get<2>(loops[z].second), tmpForStat);
                }

                processStatement(tmpForStat, blocks, labels, func, commonVars, settings);
            }
            else
            {
                SgExpression* spec = io->specList();
                vector<SAPFOR::Argument*> specArgs;

                if (spec->variant() == SPEC_PAIR)
                    specArgs.push_back(processExpression(spec, blocks, func, commonVars));
                else
                {
                    while (spec)
                    {
                        specArgs.push_back(processExpression(spec->lhs(), blocks, func, commonVars));
                        spec = spec->rhs();
                    }
                }

                vector<SAPFOR::Argument*> args;
                for (auto& par : item)
                    args.push_back(processExpression(par, blocks, func, commonVars));

                for (auto& arg : specArgs)
                {
                    Instruction* instr = new Instruction(CFG_OP::IO_PARAM, arg);
                    blocks.push_back(new IR_Block(instr));
                }

                for (auto& arg : args)
                {
                    Instruction* instr = new Instruction(CFG_OP::PARAM, arg);
                    blocks.push_back(new IR_Block(instr));
                }

                SAPFOR::Argument* fArg = NULL;

                if (var == PRINT_STAT)
                    fArg = getFuncArg("_PRINT");
                else if (var == WRITE_STAT)
                    fArg = getFuncArg("_WRITE");
                else if (var == READ_STAT)
                    fArg = getFuncArg("_READ");
                else
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                Instruction* instr = new Instruction(CFG_OP::F_CALL, fArg, createConstArg(args.size()));
                blocks.push_back(new IR_Block(instr));
            }
        }

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        processLabel(io, firstBlock, blocks, labels);
    }
    else if (var == CYCLE_STMT || var == EXIT_STMT)
    {
        blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP, st))); // need to add argument 
        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == SWITCH_NODE)
    {
        auto select = isSgSwitchStmt(st);
        SgStatement* lastSelect = select->lastNodeOfStmt();

        int lastNum = blocks.size();
        auto condSelect = processExpression(select->selector(), blocks, func, commonVars);
        for (int z = lastNum; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(select);

        vector<int> jumpsToEnd;
        bool hasDef = select->defOption();
        int numOfCases = select->numberOfCaseOptions();
        for (int z = 0; z < numOfCases; ++z)
        {
            auto caseOp = isSgCaseOptionStmt(select->caseOption(z));
            SgExpression* caseCond = caseOp->caseRangeList();
            if (caseCond->variant() == EXPR_LIST)
                caseCond = caseCond->lhs();

            auto regCond = createRegister();
            lastNum = blocks.size();
            if (caseCond->variant() == DDOT)
            {
                SAPFOR::Argument *left = NULL, *right = NULL;
                if (caseCond->lhs())
                    left = processExpression(caseCond->lhs(), blocks, func, commonVars);
                if (caseCond->rhs())
                    right = processExpression(caseCond->rhs(), blocks, func, commonVars);

                if (left && right)
                {
                    auto tmp1 = createRegister();
                    auto tmp2 = createRegister();

                    blocks.push_back(new IR_Block(new Instruction(CFG_OP::GE, condSelect, left, tmp1)));
                    blocks.push_back(new IR_Block(new Instruction(CFG_OP::LE, condSelect, right, tmp2)));
                    blocks.push_back(new IR_Block(new Instruction(CFG_OP::AND, tmp1, tmp2, regCond)));
                }
                else if (left)
                    blocks.push_back(new IR_Block(new Instruction(CFG_OP::GE, condSelect, left, regCond)));
                else
                    blocks.push_back(new IR_Block(new Instruction(CFG_OP::LE, condSelect, right, regCond)));
            }
            else
            {
                auto condCaseReg = processExpression(caseCond, blocks, func, commonVars);
                blocks.push_back(new IR_Block(new Instruction(CFG_OP::EQ, condSelect, condCaseReg, regCond)));
            }

            blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP_IF, regCond)));
            for (int z = lastNum; z < blocks.size(); ++z)
                blocks[z]->getInstruction()->setOperator(caseOp);

            int jump_if = blocks.size() - 1;

            SgStatement* body = caseOp->lexNext();
            while (body != lastSelect &&
                   body->variant() != CASE_NODE &&
                   body->variant() != DEFAULT_NODE)
            {
                const int firstInstr = blocks.size();
                body = processLabel(processStatement(body, blocks, labels, func, commonVars, settings), firstInstr, blocks, labels);

                body = body->lexNext();
            }

            if (hasDef || z != numOfCases - 1)
            {
                blocks.push_back(new IR_Block(new Instruction(CFG_OP::JUMP, NULL, NULL, NULL, caseOp))); // to end
                jumpsToEnd.push_back(blocks.size() - 1);
            }

            blocks[jump_if]->getInstruction()->setArg2(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(Instruction::getNextInstrNum())));
        }

        if (hasDef)
        {
            SgStatement* body = select->defOption()->lexNext();

            while (body != lastSelect &&
                body->variant() != CASE_NODE &&
                body->variant() != DEFAULT_NODE)
            {
                const int firstInstr = blocks.size();
                body = processLabel(processStatement(body, blocks, labels, func, commonVars, settings), firstInstr, blocks, labels);

                body = body->lexNext();
            }
        }

        blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, lastSelect)));

        for (auto& elem : jumpsToEnd)
            blocks[elem]->getInstruction()->setArg1(new SAPFOR::Argument(CFG_ARG_TYPE::INSTR, to_string(blocks.back()->getNumber())));

        processLabel(st, firstBlock, blocks, labels);
        st = lastSelect;
    }
    else if (var == ALLOCATE_STMT || var == DEALLOCATE_STMT)
    {    
        vector<SAPFOR::Argument*> args;
        for (SgExpression* ex = st->expr(0); ex; ex = ex->rhs())
            args.push_back(processExpression(ex->lhs(), blocks, func, commonVars));

        for (auto& arg : args)
        {
            Instruction* instr = new Instruction(CFG_OP::PARAM, arg);
            blocks.push_back(new IR_Block(instr));
        }

        SAPFOR::Argument* fArg = NULL;

        if (var == ALLOCATE_STMT)
            fArg = getFuncArg("_ALLOC");
        else if (var == DEALLOCATE_STMT)
            fArg = getFuncArg("_DEALLOC");
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        Instruction* instr = new Instruction(CFG_OP::F_CALL, fArg, createConstArg(args.size()));
        blocks.push_back(new IR_Block(instr));

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == ASSLAB_STAT)
    {
        auto arg = createArg(st->symbol(), commonVars, func);
        auto labRef = isSgLabelRefExp(st->expr(0));
        if (!labRef)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        Instruction* instr = new Instruction(CFG_OP::ASSIGN, createConstArg(labRef->label()->getLabNumber()), NULL, arg, st);
        blocks.push_back(new IR_Block(instr));
    }
    else if (var == ENTRY_STAT)
    {
        auto arg = createArg(st->symbol(), commonVars, func);
        Instruction* instr = new Instruction(CFG_OP::ENTRY, arg, NULL, NULL, st);
        blocks.push_back(new IR_Block(instr));

        processLabel(st, firstBlock, blocks, labels);
    }
    else if (var == POINTER_ASSIGN_STAT)
    {
        auto pointerAss = isSgPointerAssignStmt(st);
        SAPFOR::Argument *left, *right;

        int lVar = pointerAss->lhs()->variant();
        if (lVar == VAR_REF || lVar == ARRAY_REF)
            left = createArg(pointerAss->lhs()->symbol(), commonVars, func);
        else if (lVar == RECORD_REF)
            left = processExpression(isSgRecordRefExp(pointerAss->lhs()), blocks, func, commonVars);
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        int rVar = pointerAss->rhs()->variant();
        if (rVar == VAR_REF || rVar == ARRAY_REF)
            right = createArg(pointerAss->rhs()->symbol(), commonVars, func);
        else if (rVar == RECORD_REF)
            right = processExpression(isSgRecordRefExp(pointerAss->rhs()), blocks, func, commonVars);
        else
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        Instruction* instr = new Instruction(CFG_OP::POINTER_ASS, left, NULL, right, st);
        blocks.push_back(new IR_Block(instr));

        for (int z = firstBlock; z < blocks.size(); ++z)
            blocks[z]->getInstruction()->setOperator(st);

        processLabel(st, firstBlock, blocks, labels);
    }
    else if (isSPF_stat(st) )
    {
        // TODO
    }
    else if (isDVM_stat(st))
    {
        if (settings.withDVM)
        {
            const int var = st->variant();
            // TODO
            if (var == DVM_PARALLEL_ON_DIR || var == DVM_REDISTRIBUTE_DIR || var == DVM_REALIGN_DIR)
            {
                Instruction* instr = new Instruction(CFG_OP::DVM_DIR, NULL, NULL, NULL, st);
                blocks.push_back(new IR_Block(instr));
            }
        }
        
    }
    else if (skip.find(var) == skip.end() && var > 0)
    {
        st->unparsestdout();
        __spf_print(1, "unknown statement '%s' on line %d and file %s\n", tag[st->variant()], st->lineNumber(), st->fileName());
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }
    else
    {
        if (var != FORMAT_STAT && var > 0)
            blocks.push_back(new IR_Block(new Instruction(CFG_OP::EMPTY, st)));
    }
    
    return st;
}

Instruction* getInstructionByNumber(const vector<IR_Block*>& blocks, int num)
{
    if (blocks.size() == 0)
        return NULL;

    const int first = blocks[0]->getNumber();
    const int diff = num - first;
    if (diff < 0)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    if (diff >= blocks.size() || diff < blocks.size() && blocks[diff]->getNumber() != num)
    {
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto& elem : blocks)
            if (elem->getNumber() == num)
                return elem->getInstruction();
    }
    else
        return blocks[diff]->getInstruction();
}

pair<SAPFOR::Instruction*, SAPFOR::BasicBlock*> getInstructionAndBlockByNumber(const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& CFGraph, int num)
{
    if (num < 0)
        return { NULL, NULL };
    
    for (const auto& byFunc : CFGraph)
        for (auto byBB : byFunc.second)
            if (byBB->getInstructions().front()->getNumber() <= num && byBB->getInstructions().back()->getNumber() >= num)
                return make_pair(getInstructionByNumber(byBB->getInstructions(), num), byBB);
    
    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    return { NULL, NULL };
}

pair<SAPFOR::Instruction*, SAPFOR::BasicBlock*> getInstructionAndBlockByStatement(const map<FuncInfo*, vector<SAPFOR::BasicBlock*>>& CFGraph, SgStatement* stmt)
{
    SgStatement* cur = stmt;
    while (cur->variant() != PROC_HEDR && cur->variant() != PROG_HEDR && cur->variant() != FUNC_HEDR)
        cur = cur->controlParent();

    string funcName = ((SgProcHedrStmt*)cur)->nameWithContains();

    int stmtID = stmt->id();
    for (const auto& funcCFG : CFGraph)
    {
        if (funcCFG.first->funcName != funcName)
            continue;

        for (auto basicBlock : funcCFG.second)
            for (auto ins : basicBlock->getInstructions())
                if (stmtID == ins->getInstruction()->getOperator()->id())
                    return make_pair(ins->getInstruction(), basicBlock);
    }

    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    return { NULL, NULL };
}

int getParamIndex(SAPFOR::Argument* func_param, int max_index)
{
    if (func_param->getMemType() != SAPFOR::CFG_MEM_TYPE::FUNC_PARAM_)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    string name = func_param->getValue();
    int num = stoi(name.substr(name.rfind('%') + 1));
    if (num < 0 || num >= max_index)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    
    return num;
}

vector<IR_Block*> buildIR(SgStatement* function, const FuncInfo* func, const vector<pair<const Variable*, CommonBlock*>>& commonVars, const CFG_Settings settings)
{
    vector<IR_Block*> blocks;
    map<int, Instruction*> labels;

    SgStatement* end = function->lastNodeOfStmt();
    SgStatement* st = function;
    do 
    {
        st = st->lexNext();
        if (st->variant() == CONTAINS_STMT)
            st = end;

        if (!isSgExecutableStatement(st) && st->variant() != ENTRY_STAT)
            continue;
        
        const int firstInstr = blocks.size() ? blocks.size() : 1;
        st = processLabel(processStatement(st, blocks, labels, func, commonVars, settings), firstInstr, blocks, labels);
    } while (st != end);

    if (blocks.back()->getInstruction()->getOperator() != end)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    else
        findReturn(0, blocks.size(), blocks, blocks.back()->getNumber());

    // adding links by GOTO and jumps
    for (int z = 0; z < blocks.size(); ++z)
    {
        auto op = blocks[z]->getInstruction()->getOperation();
        if (op == CFG_OP::JUMP || op == CFG_OP::JUMP_IF)
        {
            auto arg = (op == CFG_OP::JUMP) ? blocks[z]->getInstruction()->getArg1() : blocks[z]->getInstruction()->getArg2();
            if (arg == NULL)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            const int lab = std::stoi(arg->getValue());

            if (arg->getType() == CFG_ARG_TYPE::LAB)
            {
                auto it = labels.find(lab);
                if (it == labels.end())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                blocks[z]->setJump(it->second);

                // replacing the label with the instruction number
                arg->setValue(to_string(it->second->getNumber())); 
                arg->setType(CFG_ARG_TYPE::INSTR);
            }
            else if (arg->getType() == CFG_ARG_TYPE::INSTR)
                blocks[z]->setJump(getInstructionByNumber(blocks, lab));
            else
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        }
    }


    for (auto& ir : blocks)
        if (ir->getInstruction()->getOperator() == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (auto& elem : dictArgs)
        dictArgsByArg[elem.second.first] = elem.second.second;

    return blocks;
}
