#include "fix_common_blocks.h"

using std::vector;
using std::map;
using std::set;
using std::string;
using std::to_string;
using std::pair;
using std::make_pair;
using std::stack;
using std::deque;
using std::reverse;
using std::tuple;

//// main function --> fixCommonBlocks

////// step1: build union for each common block

/// main function:
// renew unions for all common blocks in the file
//void BuildNewCommDecls

// get names of variables and array elements, which were referenced in programm unit
//set<string> getUses
//void getUsesFromExpr

// splits arrays into elements and replaces not used vars with empty constraints
//bool splitType
 
// create constraits set
//deque<CommConstraint> makeConstraints
 
// build union
//bool buildConstraintsUnion
//bool docheckUnequalConstraints
//bool check

/// small help functions:
//string getParentName
//bool equalDims
//bool equalConstraints
//void addElem

// change names of variables in 'constraints'
//void fixNames

////// step2: transformation
/// main function
// peform transformation on every program unit in the file
//void fixFunctions

// get pairs of names (namesOldToNew) for renaming
//bool getNamesOldToNew

// create new symbols for new variables in new common declaration (constraints)
//void makeCommVarSymbs

// delete from program unit all references to names in commVarNames
//void deleteOldVars

// calls fixExpression for each statement, replaces names in data statement
//void renameVariables

// replacing variables or array elements in expression expr if their names are in namesOldToNew
//SgExpression* fixExpression

// make new exprList exprssion for new declaration decl with symbols from newSymbs
//SgExpression* makeExprListForCommon

// replace old common declarations with new ones
//void rewriteCommon

/// help functions:
//SgExpression* makeIdxFromStr(const string& str);

// make new expression of array element
//SgExpression* newArrElemExpr
//bool variablePositionComp
//////

CommConstraint::CommConstraint(const Variable* var, bool u, const string& funcName, const string& fileName)
{
    used = u;
    for (const auto& use : var->getAllUse())
    {
        if (use.getFunctionName() == funcName)
        {
            SgSymbol* symb = use.getUseS();
            identifier = symb->identifier();
            DeclInfo dInfo = DeclInfo(identifier, fileName, use.getDeclaratedPlace()->lineNumber());
            uses.push_back(dInfo);
            if ((symb->attributes() & DATA_BIT) != 0)
                used = true;
            type = symb->type();
            typeVariant = type->variant();
            vector<SgStatement*> decls;
            declaratedInStmt(symb, &decls, false);
            Distribution::Array* arr = NULL;
            for (auto decl : decls)
            {
                arr = getArrayFromDeclarated(decl, identifier); 
                // TODO: for arrays located in the same place in common, with the same names, the same structure will be given,
                // even if they have DIFFERENT dimensions (should be different structures)
                if (arr != NULL)
                    break;
            }
            if (arr != NULL)
            {
                arrayInfo = arr;
                size = arr->GetTypeSize();
                for (auto x : arr->GetSizes())
                    size *= x.second - x.first + 1;
                SgArrayType* arrType = isSgArrayType(type);
                if (arrType != NULL)
                    typeVariant = arrType->baseType()->variant();
                else
                    __spf_print(1, "! array with no array type!\n");
            }
            else
                size = getSizeOfType(type);
        }
    }
}

CommConstraint::CommConstraint(const string& name, SgType* t, bool u) : used(u), type(t), identifier(name)
{
    typeVariant = type->variant();
    size = getSizeOfType(type);
}


CommConstraint::CommConstraint(const string& name, SgType* t, bool u, vector<DeclInfo>& us) : used(u), type(t), identifier(name), uses(us)
{
    typeVariant = type->variant();
    size = getSizeOfType(type);
}

static string getParentName(const string& name)
{
    size_t len = name.find("%");
    size_t posB = name.find("(");
    if (len == string::npos)
        if (posB == string::npos)
            return name;
        else
            len = posB;
    else if (posB != string::npos && posB < len)
            len = posB;
    return name.substr(0, len);
}

static void getUsesFromExpr(SgExpression* expr, const set<string>& commonVarNames, set<string>& used)
{
    if (expr == NULL)
        return;
    if (expr->variant() == VAR_REF || expr->variant() == ARRAY_REF)
    {
        string name = expr->symbol()->identifier();
        if (commonVarNames.find(getParentName(name)) != commonVarNames.end())
        {
            if (expr->variant() == VAR_REF)
                used.insert(name);
            else if (expr->variant() == ARRAY_REF)
            {
                SgArrayRefExp* arrExpr = (SgArrayRefExp*)expr;
                if (arrExpr->lhs() == NULL)
                    used.insert(name);
                else  // it's array element
                {
                    SgExpression* exprList = arrExpr->lhs();
                    bool ok = true;
                    int x;
                    while (exprList != NULL && ok)  // TODO: evaluate indexes of elements if they are written using loop variable
                    {
                        if (-1 == CalculateInteger(exprList->lhs(), x))
                            ok = false;
                        exprList = exprList->rhs();
                    }
                    if (ok)
                        used.insert(expr->sunparse());
                    else
                        used.insert(name);
                }
            }
        }
    }
    getUsesFromExpr(expr->lhs(), commonVarNames, used);
    getUsesFromExpr(expr->rhs(), commonVarNames, used);
}

static set<string> getUses(SgStatement* firstSt, const set<string>& commonVarNames)
{
    set<string> used;
    SgStatement* lastSt = firstSt->lastNodeOfStmt();
    for (SgStatement* curSt = firstSt; curSt != lastSt; curSt = curSt->lexNext())
    {
        int var = curSt->variant();
        if (var != COMM_STAT && var != VAR_DECL && var != VAR_DECL_90 && var != DIM_STAT) //
        {
            for (int i = 0; i < 3; i++)
                if (curSt->expr(i) != NULL)
                    getUsesFromExpr(curSt->expr(i), commonVarNames, used);
        }
    }
    return used;
}

static bool equalDims(const CommConstraint& a, const CommConstraint& b)
{
    const vector<pair<int, int>>& adim = a.arrayInfo->GetSizes();
    const vector<pair<int, int>>& bdim = b.arrayInfo->GetSizes();
    if (adim.size() != bdim.size())
        return false;
    for (int i = 0; i < adim.size(); i++)
    {
        if (adim[i].second - adim[i].first != bdim[i].second - bdim[i].first)
            return false;
    }

    return true;
}

// TODO: add attributes to CommConstraints, check if a and b have equal attributes
static bool equalConstraints(const CommConstraint& a, const CommConstraint& b)
{
    if ((a.arrayInfo != NULL && b.arrayInfo == NULL) || ((a.arrayInfo == NULL && b.arrayInfo != NULL)))
        return false;
    if (a.typeVariant != b.typeVariant || a.size != b.size)
        return false;
    if (a.arrayInfo != NULL)
        return a.arrayInfo->GetTypeSize() == b.arrayInfo->GetTypeSize() && equalDims(a, b);
    return true;
}

static void addElem(deque<CommConstraint>& comm, const CommConstraint& elem)
{
    if (elem.typeVariant == 0 && !comm.empty() && comm.back().typeVariant == 0)
        comm.back().size += elem.size;
    else
        comm.push_back(elem);
}

// TODO: check attributes: do not split arrays with pointer or target attributes if check_use == true
static bool splitType(deque<CommConstraint>& d, bool check_use, const set<string>& namesOfUsedVars = {})
{
    CommConstraint var = d.front();
    string name = var.identifier;
    if (var.typeVariant == 0 || (check_use && var.used))
        return false;
    if (var.arrayInfo != NULL)  // TODO: arrays can be split not only into individual elements, but also into smaller arrays
    {
        d.pop_front();
        const auto dims = var.arrayInfo->GetSizes();
        int numOfDims = dims.size();
        SgType* elemType = new SgType(var.typeVariant);
        int elemNum = var.size / var.arrayInfo->GetTypeSize();
        for (int k = elemNum - 1; k >= 0; k--)
        {
            string newName = name + "(";
            int ind = k;
            for (int i = 0; i < numOfDims; i++)
            {
                auto d = dims[i];
                int dimLen = d.second - d.first + 1;
                newName += to_string(ind % dimLen + d.first);
                if (i < numOfDims - 1)
                    newName += ",";
                ind = ind / dimLen;
            }
            newName += ")";
            CommConstraint newVar = CommConstraint(newName, elemType, var.used, var.uses);
            if (check_use && !newVar.used && namesOfUsedVars.find(newName) != namesOfUsedVars.end())
                newVar.used = true;
            d.push_front(newVar);
        }
        return true;
    }
    else
    {
        if (check_use && (var.used == false))
        {
            d.pop_front();
            CommConstraint newVar = CommConstraint(var.identifier, var.size);
            d.push_front(newVar);
            return true;
        }
        else
            return false;
    }
}

static deque<CommConstraint> makeConstraints(deque<CommConstraint>& constraints, const set<string>& namesOfUsedVars, set<string>& notUsedVars)
{
    deque<CommConstraint> res;
    while (!constraints.empty())
    {
        string curName = constraints.front().identifier;
        if (!splitType(constraints, true, namesOfUsedVars))
        {
            addElem(res, constraints.front());
            constraints.pop_front();
        }
        else
            notUsedVars.insert(curName);
    }
    return res;
}

static bool check(deque<CommConstraint>& A, deque<CommConstraint>& B, pair<CommConstraint, CommConstraint>& problemConstraints)
{
    while (!A.empty() && !B.empty())
    {
        if (B.front().size > A.front().size)
        {
            if (!splitType(B, false))
            {
                problemConstraints.second = A.front();
                return false;
            }
        }
        else if (B.front().size < A.front().size)
        {
            if (A.front().typeVariant == 0)
            {
                A.front().size -= B.front().size;
                B.pop_front();
            }
            else
            {
                problemConstraints.second = A.front();
                return false;
            }
        }
        else
        {
            if (A.front().typeVariant != 0 && B.front().typeVariant != 0 && !equalConstraints(A.front(), B.front()))
            {
                problemConstraints.second = A.front();
                return false;
            }
            B.pop_front();
            A.pop_front();
        }
    }
    return true;
}

static bool docheckUnequalConstraints(deque<CommConstraint>& U, deque<CommConstraint>& B, deque<CommConstraint>& newU, 
                                      pair<CommConstraint, CommConstraint>& problemConstraints)
{
    if (U.front().typeVariant == 0)
    {
        addElem(newU, B.front());
        U.front().size -= B.front().size;
        B.pop_front();
    }
    else
    {
        deque<CommConstraint> temp;
        temp.push_front(U.front());
        if (!check(B, temp, problemConstraints))
        {
            problemConstraints.first = U.front();
            return false;
        }
        addElem(newU, U.front());
        U.pop_front();
    }
    return true;
}

static bool buildConstraintsUnion(deque<CommConstraint>& U, deque<CommConstraint> B, 
                                  const set<string>& namesOfUsedVars, pair<CommConstraint, CommConstraint>& problemConstraints)
{
    deque<CommConstraint> newU;
    while (!U.empty() && !B.empty())
    {
        if (U.front().size < B.front().size)
        {
            if (!docheckUnequalConstraints(B, U, newU, problemConstraints))
                return false;
        }
        else if (U.front().size > B.front().size)
        {
            if (!docheckUnequalConstraints(U, B, newU, problemConstraints))
                return false;
        }
        else
        {
            if (U.front().typeVariant == 0 || B.front().typeVariant == 0
                || equalConstraints(U.front(), B.front()))
            {
                if (U.front().typeVariant == 0)
                    addElem(newU, B.front());
                else if (B.front().typeVariant == 0)
                    addElem(newU, U.front());
                else
                {
                    U.front().uses.insert(U.front().uses.end(), B.front().uses.begin(), B.front().uses.end()); // adding to funcs
                    addElem(newU, U.front());
                }
                U.pop_front();
                B.pop_front();
            }
            else
            {
                problemConstraints = make_pair(B.front(), U.front());
                return false;
            }
        }
    }
    while (!B.empty())
    {
        addElem(newU, B.front());
        B.pop_front();
    }
    while (!U.empty())
    {
        addElem(newU, U.front());
        U.pop_front();
    }
    if (!newU.empty() && newU.back().typeVariant == 0)
        newU.pop_back();
    U = newU;
    return true;
}

//TODO: check this: newDecl and oldDecl => do these variables need references?
static bool getNamesOldToNew(deque<CommConstraint> newDecl, deque<CommConstraint> oldDecl, map<string, string>& namesOldToNew)
{
    bool needChange = false;
    map<string, string> rename;
    while (!oldDecl.empty() && !newDecl.empty())
    {
        if (newDecl.front().typeVariant == 0) // => oldDecl.front().typeVariant == 0
        {
            if (oldDecl.front().size > newDecl.front().size)
            {
                oldDecl.front().size -= newDecl.front().size;
                needChange = true;
            }
            else
                oldDecl.pop_front();
            newDecl.pop_front();
        }
        else if (newDecl.front().size > oldDecl.front().size)
        {
            needChange = true;
            deque<CommConstraint> tmp;
            tmp.push_front(newDecl.front());
            splitType(tmp, false);
            while (!tmp.empty() && !oldDecl.empty())
            {
                if (oldDecl.front().typeVariant == 0)
                {
                    if (oldDecl.front().size > tmp.front().size)
                        oldDecl.front().size -= tmp.front().size;
                    else
                        oldDecl.pop_front();
                }
                else
                {
                    rename[oldDecl.front().identifier] = tmp.front().identifier;
                    oldDecl.pop_front();
                }
                tmp.pop_front();
            }
            newDecl.pop_front();
        }
        else if (oldDecl.front().size > newDecl.front().size)  // => oldDecl.front().typeVariant == 0
        {
            needChange = true;
            oldDecl.front().size -= newDecl.front().size;
            newDecl.pop_front();
        }
        else // == and newDecl.front().typeVariant != 0
        {
            if (oldDecl.front().typeVariant == 0)
                needChange = true;
            else
            {
                if (oldDecl.front().identifier.find('(') != string::npos)
                {
                    needChange = true;
                    rename[oldDecl.front().identifier] = newDecl.front().identifier;
                }
                else
                    rename[oldDecl.front().identifier] = newDecl.front().identifier;
            }
            oldDecl.pop_front();
            newDecl.pop_front();
        }
    }

    if (!oldDecl.empty() || !newDecl.empty())
        needChange = true;

    if (needChange)
        namesOldToNew.insert(rename.begin(), rename.end());

    return needChange;
}

static void makeCommVarSymbs(const deque<CommConstraint>& constraints, SgFile* file, SgStatement* func, const string& commName,
                             map<string, SgSymbol*>& symbs, vector<SgSymbol*>& needNewDecl)
{
    for (const CommConstraint& var : constraints)
    {
        if (var.typeVariant != 0)
        {
            SgSymbol* symb = findSymbolOrCreate(file, var.identifier, var.type->copyPtr(), func);
            symbs[var.identifier] = symb;
            needNewDecl.push_back(symb);
        }
    }
}

static void deleteOldVars(SgStatement* firstSt, const set<string>& commVarNames)
{
    SgStatement* lastSt = firstSt->lastNodeOfStmt();
    vector<SgStatement*> stmtsToDelete;
    for (SgStatement* curSt = firstSt; curSt != lastSt; curSt = curSt->lexNext())
    {
        if (curSt->variant() == VAR_DECL || curSt->variant() == VAR_DECL_90)
        {
            SgVarDeclStmt* varDeclSt = (SgVarDeclStmt*)curSt;
            vector<SgExpression*> varsToDelete;
            for (int i = 0; i < varDeclSt->numberOfVars(); i++)
            {
                SgExpression* var = varDeclSt->var(i);
                string varName = "";
                if (var->variant() == ASSGN_OP)
                    varName = var->lhs()->symbol()->identifier();
                else
                    varName = var->symbol()->identifier();
                if (commVarNames.find(varName) != commVarNames.end())
                    varsToDelete.push_back(var);
            }
            if (varDeclSt->numberOfVars() == varsToDelete.size())
                stmtsToDelete.push_back(curSt);
            else
                for (SgExpression* var : varsToDelete)
                    varDeclSt->deleteTheVar(*var);
        }
        else if (curSt->variant() == DIM_STAT)
        {
            SgExpression* ex = curSt->expr(0);
            bool first = true;
            vector<SgExpression*> leftExprs;
            while (ex != NULL)
            {
                string name = ex->lhs()->symbol()->identifier();
                if (commVarNames.find(name) == commVarNames.end())
                    leftExprs.push_back(ex->lhs());
                ex = ex->rhs();
            }
            if (leftExprs.empty())
                stmtsToDelete.push_back(curSt);
            else
                curSt->setExpression(0, makeExprList(leftExprs));
        }
        // TODO: delete common variables form attributes statements (like DIM_STAT)
    }

    for (SgStatement* st : stmtsToDelete)
        st->deleteStmt();
}

static SgExpression* makeIdxFromStr(const string& str)
{
    vector<SgExpression*> items;
    int num = 0;
    for (char c : str)
    {
        if ('0' <= c && c <= '9')
            num = num * 10 + (c - '0');
        else if (c == ',' || c == ')')
        {
            SgExpression* ex = new SgValueExp(num);
            items.push_back(ex);
            num = 0;
        }
    }

    reverse(items.begin(), items.end());
    SgExpression* exprList = makeExprList(items, false);
    return exprList;
}

static SgExpression* newArrElemExpr(const string& newName, const map<string, SgSymbol*>& newSymbs)
{
    size_t pos = newName.find('(');
    SgExpression* newExpr = new SgArrayRefExp(*newSymbs.at(newName.substr(0, pos)));
    newExpr->setLhs(makeIdxFromStr(newName.substr(pos)));
    return newExpr;
}

static SgExpression* fixExpression(SgExpression* expr, const map<string, SgSymbol*>& newSymbs, const map<string, string>& namesOldToNew)
{
    if (expr == NULL)
        return NULL;

    if (expr->variant() == VAR_REF || expr->variant() == ARRAY_REF)
    {
        string name = expr->symbol()->identifier();
        auto nameIt = namesOldToNew.find(name);
        if (nameIt != namesOldToNew.end())
        {
            string newName = nameIt->second;
            auto symbIt = newSymbs.find(newName);
            if (symbIt == newSymbs.end())   // variable -> array element
                return newArrElemExpr(newName, newSymbs);
            else    // variable -> variable or array name -> array name
            {
                SgSymbol* newSymb = symbIt->second;
                expr->setSymbol(newSymb);
            }
        }
        else
        {
            string fullName = expr->sunparse();
            auto fullNameIt = namesOldToNew.find(fullName);
            if (fullNameIt != namesOldToNew.end())
            {
                string newName = fullNameIt->second;
                auto symbIt = newSymbs.find(newName);
                if (symbIt == newSymbs.end())    // array element -> array element
                    return newArrElemExpr(newName, newSymbs);
                else    // array element -> variable
                {
                    SgVariableSymb* varSymb = (SgVariableSymb*)symbIt->second;
                    SgExpression* newExpr = new SgVarRefExp(*varSymb);
                    return newExpr;
                }
            }
        }
    }

    SgExpression* lhs = fixExpression(expr->lhs(), newSymbs, namesOldToNew);
    if (lhs != NULL)
        expr->setLhs(lhs);

    SgExpression* rhs = fixExpression(expr->rhs(), newSymbs, namesOldToNew);
    if (rhs != NULL)
        expr->setRhs(rhs);
    return NULL;
}

static void renameVariables(SgStatement* firstSt, const map<string, SgSymbol*>& newVarSymbs, const map<string, string>& namesOldToNew)
{
    SgStatement* lastSt = firstSt->lastNodeOfStmt();
    for (SgStatement* curSt = firstSt; curSt != NULL && curSt != lastSt; curSt = curSt->lexNext())
    {
        if (curSt->variant() == DATA_DECL)
        {
            SgValueExp* dataExpr = (SgValueExp*)curSt->expr(0);
            map<string, string> data = splitData({ dataExpr });
            string newDataStr = "data ";
            bool needChange = false;
            int left = data.size();
            for (pair<const string, string>& item : data)
            {
                auto nameIt = namesOldToNew.find(item.first);
                if (nameIt != namesOldToNew.end())
                {
                    newDataStr += nameIt->second + "/" + item.second + "/";
                    needChange = true;
                }
                else
                    newDataStr += item.first + "/" + item.second + "/";
                left--;
                if (left != 0)
                    newDataStr += ", ";
            }
            if (needChange)
            {
                SgExpression* es = new SgExpression(STMT_STR);
                char* value = (char*)malloc(newDataStr.size() + 1);
                value[newDataStr.size()] = '\0';
                memcpy(value, newDataStr.c_str(), sizeof(char) * newDataStr.size());
                es->thellnd->entry.string_val = value;
                curSt->setExpression(0, es);
            }
        }
        else if (isSgExecutableStatement(curSt) || curSt->variant() == EQUI_STAT)
        {
            for (int i = 0; i < 3; i++)
            {
                SgExpression* expr = fixExpression(curSt->expr(i), newVarSymbs, namesOldToNew);
                if (expr != NULL)
                    curSt->setExpression(i, expr);
            }
        }
    }
}

static SgExpression* makeExprListForCommon(const deque<CommConstraint>& decl, const map<string, SgSymbol*>& newSymbs,
    SgFile* file, SgStatement* firstSt)
{
    vector<SgExpression*> items;
    for (auto it = decl.rbegin(); it != decl.rend(); it++)
    {
        if (it->typeVariant == 0)
            continue;
        SgSymbol* symb = NULL;
        bool old = false;
        auto symbIt = newSymbs.find(it->identifier);
        if (symbIt != newSymbs.end())
            symb = symbIt->second;
        else
        {
            symb = findSymbolOrCreate(file, it->identifier, it->type, firstSt);
            old = true;
        }
        SgVariableSymb* varSymb = isSgVariableSymb(symb);
        if (varSymb->type()->variant() == T_ARRAY)
        {
            SgExpression* newExpr = new SgArrayRefExp(*varSymb);
            items.push_back(newExpr);
        }
        else
        {
            SgExpression* newExpr = new SgVarRefExp(symb);
            items.push_back(newExpr);
        }
    }
    SgExpression* exprList = makeExprList(items, false);
    return exprList;
}

static void rewriteCommon(SgStatement* firstSt, map<string, SgExpression*>& commListExprs)
{
    vector<SgStatement*> commonStmtsToDelete;
    for (SgStatement* st = firstSt; st != firstSt->lastDeclaration()->lexNext(); st = st->lexNext())
    {
        if (st->variant() == COMM_STAT)
        {
            SgExpression* ex = st->expr(0);
            bool first = true;
            SgExpression* prev = NULL;
            while (ex != NULL)
            {
                string commName = "";
                SgSymbol* s = ex->symbol();
                if (s == NULL)
                    commName = "spf_unnamed";
                else
                    commName = s->identifier();
                auto commIt = commListExprs.find(commName);
                if (commIt != commListExprs.end())
                {
                    if (commIt->second != NULL)
                    {
                        ex->setLhs(commIt->second);
                        commIt->second = NULL;
                        if (first)
                            first = false;
                        prev = ex;
                        ex = ex->rhs();
                    }
                    else
                    {
                        if (first)
                        {
                            st->setExpression(0, ex->rhs());
                            ex = st->expr(0);
                        }
                        else
                        {
                            prev->setRhs(ex->rhs());
                            ex = prev->rhs();
                        }
                    }
                }
                else
                    ex = ex->rhs();
            }
            if (st->expr(0) == NULL)
                commonStmtsToDelete.push_back(st);
        }
    }

    for (SgStatement* st : commonStmtsToDelete)
        st->deleteStmt();
}

static void fixNames(deque<CommConstraint>& constraints, const string& commName)
{
    for (auto& var : constraints)
    {
        for (char& c : var.identifier)
        {
            if (c == ')' || c == '(')
                c = 'l';
            if (c == ',')
                c = '_';
        }
        var.identifier = commName + "_" + var.identifier;
    }
}

static bool variablePositionComp(const Variable* lhs, const Variable* rhs)
{
    return lhs->getPosition() < rhs->getPosition();
}

static void fixFunctions(SgFile* file, const vector<SgStatement*>& programUnits, map<string, deque<CommConstraint>>& newCommonDecls,
                         map<string, map<string, deque<CommConstraint>>>& commDecls, const set<string>& badCommon, map<string, set<string>>& notUsedVars)
{
    for (SgStatement* unitSt : programUnits)
    {
        string funcName = unitSt->symbol()->identifier();
        if (commDecls.find(funcName) == commDecls.end())
            continue;

        SgStatement* firstSt = unitSt;
        map<string, SgExpression*> commListExprs;
        map<string, SgSymbol*> newVarSymbs; // new symbols for new variables
        map<string, string> namesOldToNew; // for ranaming: old name -> new name
        vector<SgSymbol*> needNewDecl;

        for (auto& common : commDecls[funcName])
        {
            string commName = common.first;
            if (badCommon.find(commName) != badCommon.end())
                continue;
            const deque<CommConstraint>& newDecl = newCommonDecls.at(commName);
            vector<CommConstraint> varsNeedNewSymb;
            bool needChange = getNamesOldToNew(newDecl, common.second, namesOldToNew);
            if (!needChange)
                continue;

            makeCommVarSymbs(newDecl, file, firstSt, commName, newVarSymbs, needNewDecl);
            commListExprs[commName] = makeExprListForCommon(newDecl, newVarSymbs, file, firstSt);
        }

        if (!commListExprs.empty())
        {
            for (const auto& item : commListExprs)
                for (const auto& x : commDecls[funcName][item.first])
                    notUsedVars[funcName].insert(x.identifier);
            deleteOldVars(unitSt, notUsedVars[funcName]);
            renameVariables(unitSt, newVarSymbs, namesOldToNew);
            makeDeclaration(needNewDecl, unitSt);
            rewriteCommon(firstSt, commListExprs);
        }
    }
}

static void buildNewCommDecls(SgFile* file, const map<string, CommonBlock*>& allCommonBlocks,
                              map<string, deque<CommConstraint>>& newCommonDecls, map<string, map<string, deque<CommConstraint>>>& commDecls,
                              set<string>& badCommon, map<string, set<string>>& notUsedVars, vector<SgStatement*>& programUnits)
{
    string fileName = file->filename();
    SgStatement* curSt = file->firstStatement();
    while (curSt != NULL)
    {
        if (curSt->variant() == PROG_HEDR || curSt->variant() == PROC_HEDR || curSt->variant() == FUNC_HEDR || curSt->variant() == BLOCK_DATA || curSt->variant() == MODULE_STMT)
        {
            programUnits.push_back(curSt);
            string funcName = curSt->symbol()->identifier();
            for (auto item : allCommonBlocks)
            {
                string commName = item.first;
                if (badCommon.find(commName) != badCommon.end())
                    continue;
                CommonBlock* commonBlock = item.second;
                vector<const Variable*> vars = commonBlock->getVariables(fileName, funcName);
                if (vars.size() == 0)
                    continue;
                sort(vars.begin(), vars.end(), variablePositionComp);
                set<string> varNames;
                for (const Variable* var : vars)
                    varNames.insert(var->getName());
                set<string> namesOfUsedVars = getUses(curSt, varNames);
                bool hasChar = false;
                bool hasNotChar = false;
                deque<CommConstraint> constraints;
                for (const Variable* var : vars)
                {
                    CommConstraint newConstr = CommConstraint(var, false, funcName, fileName);
                    if (newConstr.typeVariant == T_STRING || newConstr.typeVariant == T_ARRAY) // ignore common blocks with strings
                        hasChar = true;
                    else
                        hasNotChar = true;
                    if (namesOfUsedVars.find(newConstr.identifier) != namesOfUsedVars.end())
                        newConstr.used = true;
                    constraints.push_back(newConstr);
                }
                if (hasChar && hasNotChar)  // TDOO: make proper warning message or separate such common blocks
                    __spf_print(1, "common block '%s' ('%s':%d) contains variables of symbolic and numeric types. It is required to divide\n", 
                                    commName.c_str(), fileName.c_str(), constraints.back().uses.back().getLineNum());
                if (hasChar)
                {
                    badCommon.insert(commName);
                    continue;
                }
                deque<CommConstraint> curComm = makeConstraints(constraints, namesOfUsedVars, notUsedVars[funcName]);
                commDecls[funcName][commName] = curComm;
                bool res;
                pair<CommConstraint, CommConstraint> problemConstraints;
                res = buildConstraintsUnion(newCommonDecls[commName], curComm, namesOfUsedVars, problemConstraints);
                if (!res)
                {
                    badCommon.insert(commName);
                    for (auto x : problemConstraints.first.uses)         // TODO: make proper warning message
                        for (auto y : problemConstraints.second.uses)
                            __spf_print(1, "variables '%s' and '%s' in one storage association (common block '%s') have different types (files - %s:%d and %s:%d)\n",
                                            x.getVarName(), y.getVarName(), commName.c_str(), x.getFileName(), x.getLineNum(), y.getFileName(), y.getLineNum());
                }
            }
            curSt = curSt->lastNodeOfStmt();
        }
        else
            curSt = curSt->lexNext();
    }
}


// main function
void fixCommonBlocks(const map<string, vector<FuncInfo*>>& allFuncInfo, const map<string, CommonBlock*>& allCommonBlocks, SgProject* project)  // TODO: separate into 2 steps?
{
    int filesNum = project->numberOfFiles();
    map<string, map<string, map<string, deque<CommConstraint>>>> commDecls; // file_name -> function_name -> common block name -> old declaration of common block
    map<string, deque<CommConstraint>> newCommonDecls; // name of common block -> cur builded declaration
    map<string, set<string>> notUsedVars;
    map<string, vector<SgStatement*>> programUnitsInFile;
    set<string> badCommon;

    for (int i = 0; i < filesNum; i++)  // first step
    {
        SgFile* file = &project->file(i);
        string fileName = file->filename();
        file->switchToFile(fileName);
        buildNewCommDecls(file, allCommonBlocks, newCommonDecls, commDecls[fileName], badCommon, notUsedVars, programUnitsInFile[fileName]);
    }

    for (auto& elem : newCommonDecls)
        fixNames(elem.second, elem.first);

    for (int i = 0; i < filesNum; i++)  // second step
    {
        SgFile* file = &project->file(i);
        string fileName = file->filename();
        file->switchToFile(fileName);
        fixFunctions(file, programUnitsInFile[fileName], newCommonDecls, commDecls[fileName], badCommon, notUsedVars);
    }
}