#include "leak_detector.h"

#include <cstdio>
#include <cstring>
#include <cstring>
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <set>
#include <vector>
#include <algorithm>
#include <tuple>

#include "dvm.h"
#include "errors.h"
#include "SgUtils.h"

#include "uniq_call_chain_dup.h"
#include "graph_calls.h"
#include "graph_calls_func.h"
#include "expr_transform.h"
#include "../VerificationCode/verifications.h"

using namespace std;

struct callVars
{
    callVars() { }
    callVars(const pair<void*, int>& place, const vector<void*>& var, void* parentSt) : callVariant(var)
    {
        callPlaces.push_back(make_tuple(place.first, place.second, parentSt));
    }

    vector<tuple<void*, int, void*>> callPlaces;    
    vector<void*> callVariant;
    string copiedName;
};

struct callInfo
{
    callInfo() { countCalls = 0; }

    int countCalls;
    vector<callVars> variantCall;
};

static bool checkUniqAndAdd(vector<callVars> &vars, const vector<void*> &current, const pair<void*, int> &callPlace, void* parentSt)
{
    for (auto& elem : vars)
    {
        if (elem.callVariant == current)
        {
            elem.callPlaces.push_back(make_tuple(callPlace.first, callPlace.second, parentSt));            
            return false;
        }
    }

    vars.push_back(callVars(callPlace, current, parentSt));
    return true;
}

static vector<void*> createParamCalls(const pair<void*, int> &callPointer, const string &fileN)
{
    if (SgFile::switchToFile(fileN) == -1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SgExpression *callList = NULL;
    if (callPointer.second == PROC_STAT)
        callList = ((SgStatement*)callPointer.first)->expr(0);
    else if (callPointer.second == FUNC_CALL)
        callList = ((SgExpression*)callPointer.first)->lhs();
    else
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    vector<void*> packCall;

    while (callList)
    {
        auto curr = callList->lhs();
        if (curr->variant() == ARRAY_REF)
        {
            SgSymbol* par = OriginalSymbol(callList->lhs()->symbol());
            DIST::Array* array = getArrayFromDeclarated(declaratedInStmt(par), par->identifier());
            if (array)
            {
                if (array->IsNotDistribute() == false)
                    packCall.push_back(array);
                else
                    packCall.push_back(NULL);
            }
            else
                packCall.push_back(NULL);
        }
        else
            packCall.push_back(NULL);
        callList = callList->rhs();
    }

    return packCall;
}

static map<FuncInfo*, callInfo> countOfCalls(const map<string, vector<FuncInfo*>> &allFuncs, const map<string, FuncInfo*> &mapOfFunc)
{
    map<FuncInfo*, int> count;    
    map<FuncInfo*, vector<callVars>> variantCall;

    for (auto& fromFile : allFuncs)
    {
        for (auto& elem : fromFile.second)
        {
            count[elem] = 0;
            variantCall[elem] = vector<callVars>();
        }
    }

    for (auto& fromFile : allFuncs)
    {
        for (auto& elem : fromFile.second)
        {
            int p = 0;
            for (auto& callInfo : elem->callsFromDetailed)
            {
                auto& detailed = callInfo.detailCallsFrom;
                auto it = mapOfFunc.find(detailed.first);
                if (it != mapOfFunc.end())
                {
                    auto var = variantCall.find(it->second);
                    auto createPackCall = createParamCalls(elem->callsFromDetailed[p].pointerDetailCallsFrom, elem->fileName);

                    if (checkUniqAndAdd(var->second, createPackCall, elem->callsFromDetailed[p].pointerDetailCallsFrom, elem->callsFromDetailed[p].parentForPointer))
                        count[it->second]++;
                }
                ++p;
            }
        }
    }

    map<FuncInfo*, callInfo> retInfo;
    for (auto& elem : count)
        retInfo[elem.first].countCalls = elem.second;
    for (auto& elem : variantCall)
        retInfo[elem.first].variantCall = elem.second;
    return retInfo;
}

static int getRealCallCount(const callInfo &info, const map<DIST::Array*, set<DIST::Array*>> &arrayLinksByFuncCall)
{
    if (info.variantCall.size() > 1)
        return info.countCalls;
    else if (info.variantCall.size() == 0)
        return 0;
    else
    {
        const callVars& var = info.variantCall[0];
        for (int z = 0; z < var.callVariant.size(); ++z)
        {
            if (var.callVariant[z])
            {
                DIST::Array* ref = (DIST::Array*)var.callVariant[z];
                set<DIST::Array*> realRefs;
                getRealArrayRefs(ref, ref, realRefs, arrayLinksByFuncCall);
                if (realRefs.size() > 1)
                    return 2;
            }
        }

        return 1;
    }
}

static vector<FuncInfo*> detect(const map<string, vector<FuncInfo*>> &allFuncs, const map<string, FuncInfo*> &mapOfFunc, 
                                map<FuncInfo*, callInfo> &funcInfoOfCall, const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCall)
{    
    vector<FuncInfo*> retVal;
    funcInfoOfCall = countOfCalls(allFuncs, mapOfFunc);

    for (auto &fromFile : allFuncs)
    {
        if (SgFile::switchToFile(fromFile.first) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto &elem : fromFile.second)
        {
            SgProgHedrStmt *prog = isSgProgHedrStmt(elem->funcPointer->GetOriginal());
            checkNull(prog, convertFileName(__FILE__).c_str(), __LINE__);

            if (prog->variant() == PROG_HEDR)
                continue;

            if (getRealCallCount(funcInfoOfCall[elem], arrayLinksByFuncCall) <= 1)
                continue;

            for (int z = 0; z < prog->numberOfParameters(); ++z)
            {
                SgSymbol *par = OriginalSymbol(prog->parameter(z));
                DIST::Array *array = getArrayFromDeclarated(declaratedInStmt(par), par->identifier());

                if (array)
                {
                    if (array->IsNotDistribute() == false)
                    {
                        retVal.push_back(elem);
                        break;
                    }
                }
            }
        }
    }

    return retVal;
}

static set<FuncInfo*> getFullCallsFrom(FuncInfo *from, const map<string, FuncInfo*> &mapOfFunc)
{
    set<FuncInfo*> callsFromFull;
    callsFromFull.insert(from);

    bool change = true;
    while (change)
    {
        change = false;
        set<FuncInfo*> newAdd;
        for (auto& func: callsFromFull)
        {
            for (auto& elem : func->callsFrom)
            {
                auto it = mapOfFunc.find(elem);
                if (it != mapOfFunc.end())
                {
                    auto it2 = callsFromFull.find(it->second);
                    if (callsFromFull.end() == it2)
                    {
                        newAdd.insert(it->second);
                        change = true;
                    }
                }
            }
        }

        for (auto& elem : newAdd)
            callsFromFull.insert(elem);
    }

    return callsFromFull;
}

static bool isAllDone(const vector<bool>& done)
{
    for (const auto& elem : done)
        if (elem == false)
            return false;
    return true;
}

static map<FuncInfo*, set<FuncInfo*>> groupByMainCall(const vector<FuncInfo*> &toCopy, const map<string, FuncInfo*> &mapOfFunc)
{
    set<FuncInfo*> copySet(toCopy.begin(), toCopy.end());

    vector<bool> done(toCopy.size());
    std::fill(done.begin(), done.end(), false);

    map<FuncInfo*, set<FuncInfo*>> groups;

    while (!isAllDone(done))
    {
        int z = 0;
        for (; z < done.size(); ++z)
        {
            if (!done[z])
            {
                for (auto& callsTo : toCopy[z]->callsTo)
                {
                    if (copySet.find(callsTo) != copySet.end())
                        continue;
                }
                done[z] = true;
                break;
            }
        }

        if (z == done.size() + 1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        groups[toCopy[z]] = set<FuncInfo*>();

        set<FuncInfo*> fullCalls = getFullCallsFrom(toCopy[z], mapOfFunc);
        for (int p = 0; p < done.size(); ++p)
        {
            //self
            if (p == z)
                continue;

            if (fullCalls.find(toCopy[p]) != fullCalls.end())
            {
                groups[toCopy[z]].insert(toCopy[p]);
                done[p] = true;
            }
        }
    }

    return groups;
}

static void fillOrigCopyEx(SgExpression *orig, SgExpression *copy, map<SgExpression*, SgExpression*> &origCopyEx)
{
    if (orig)
    {
        origCopyEx[orig] = copy;

        fillOrigCopyEx(orig->lhs(), copy->lhs(), origCopyEx);
        fillOrigCopyEx(orig->rhs(), copy->rhs(), origCopyEx);
    }
}

static void findAllFunctionCalls(SgExpression* ex, const vector<void*> &paramVar, 
                                 const string& file, set<SgExpression*>& toChange)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
        {
            pair<void*, int> callPointer = make_pair(ex, FUNC_CALL);
            if (createParamCalls(callPointer, file) == paramVar)
                toChange.insert(ex);
        }

        if (ex->lhs())
            findAllFunctionCalls(ex->lhs(), paramVar, file, toChange);
        if (ex->rhs())
            findAllFunctionCalls(ex->rhs(), paramVar, file, toChange);
    }
}

static void findDeclAndDuplicate(SgStatement* func, const string& funcName, const string& funcNameCopy, int variant)
{
    if (SgFile::switchToFile(func->fileName()) == -1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    if (variant != FUNC_HEDR)
        return;

    SgStatement* end = func->lastNodeOfStmt();
    bool hasImplicit = false;
    for (auto st = func; st != end; st = st ->lexNext())
    {
        if (isSgExecutableStatement(st))
            break;
        if (st->variant() == IMPL_DECL)
        {
            hasImplicit = true;
            break;
        }
    }

    if (!hasImplicit)
        return;

    for (auto st = func; st != end; st = st->lexNext())
    {
        if (isSgExecutableStatement(st))
            break;

        if (st->variant() == VAR_DECL || st->variant() == VAR_DECL_90)
        {
            SgExpression* ex = st->expr(0);
            while (ex)
            {
                auto s = ex->lhs()->symbol();
                if (s && s->identifier() == funcName)
                {
                    SgExpression* copy = ex->lhs()->copyPtr();
                    copy->setSymbol(s->copy());
                    copy->symbol()->changeName(funcNameCopy.c_str());
                    SgExpression* next = ex->rhs();

                    ex->setRhs(new SgExpression(ex->variant(), copy, next));
                    break;
                }
                ex = ex->rhs();
            }
        }
    }
}

static void findInterfaceBlockAndDuplicate(SgStatement* func, const string& ifaceName, const string& ifaceCopy, int variant)
{
    if (SgFile::switchToFile(func->fileName()) == -1)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SgStatement* end = func->lastNodeOfStmt();
    for ( ; func != end; func = func->lexNext())
    {
        if (func->variant() == INTERFACE_STMT)
        {
            SgStatement* ifaceEnd = func->lastNodeOfStmt();
            bool found = false;
            for (auto st = func->lexNext(); st != ifaceEnd; st = st->lexNext())
            {
                if (st->variant() == variant && st->symbol()->identifier() == ifaceName)
                {
                    auto copy = st->copyBlockPtr();
                    st->insertStmtBefore(*copy, *st->controlParent());
                    SgSymbol* copyS = &copy->symbol()->copy();
                    copyS->changeName(ifaceCopy.c_str());
                    copy->setSymbol(*copyS);
                    found = true;
                    break;
                }
            }
            if (found)
                break;
        }

        if (isSgExecutableStatement(func) && !isDVM_stat(func) && !isSPF_stat(func))
            break;
        if (func->variant() == CONTAINS_STMT)
            break;
    }
}

static void copyGroup(const map<string, FuncInfo*> &mapOfFunc, const vector<FuncInfo*> &toCopyGroups, map<FuncInfo*, callInfo> &funcInfoOfCall)
{
    if (toCopyGroups.size() == 0)
        return;

    // function -> functions with interface to thisF
    map<FuncInfo*, set<FuncInfo*>> interfaceInfo;

    for (auto& func: mapOfFunc)
        for (auto& interf : func.second->interfaceBlocks)
            interfaceInfo[interf.second].insert(func.second);

    bool changed = true;
    map<FuncInfo*, int> numCopyF;

    map<string, map<SgStatement*, SgSymbol*>> toChangeSt;
    map<string, map<SgExpression*, SgSymbol*>> toChangeEx;
    map<string, set<string>> addedFuncDecl;

    int iterCount = 0;
    while (changed)
    {
        changed = false;
        SgStatement::cleanParentStatsForExprs();

        for (auto& currFunc : toCopyGroups)
        {
            auto info = funcInfoOfCall.find(currFunc);
            if (info == funcInfoOfCall.end())
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            if (numCopyF.find(currFunc) == numCopyF.end())
                numCopyF[currFunc] = 1;            
            for (auto& varCall : info->second.variantCall)
            {
                int numCopy = numCopyF[currFunc];
                if (varCall.copiedName.size() != 0)
                    continue;

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

                SgStatement* pointToF = currFunc->funcPointer->GetOriginal();

                SgSymbol* orig = pointToF->symbol();
                string origName = orig->identifier();
                origName = getOrigName(pointToF->fileName(), origName);
                const string newName = checkSymbNameAndCorrect(origName + string("_spf_") + to_string(numCopy));

                SgStatement* toMove = duplicateProcedure(pointToF, &newName, false, false, false);

                varCall.copiedName = newName;

                map<SgStatement*, SgStatement*> origCopySt;
                map<SgExpression*, SgExpression*> origCopyEx;
                // set line numbers and pointer to attributes
                for (auto origStat = pointToF, copyStat = toMove;
                    origStat != pointToF->lastNodeOfStmt()->lexNext();
                    origStat = origStat->lexNext(), copyStat = copyStat->lexNext())
                {
                    if (copyStat->variant() != origStat->variant())
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                    
                    copyStat->setlineNumber(origStat->lineNumber());
                    copyStat->setLocalLineNumber(origStat->localLineNumber());

                    BIF_FILE_NAME(copyStat->thebif) = BIF_FILE_NAME(origStat->thebif);
                    if (origStat->numberOfAttributes() > 0)
                        copyStat->addAttributeTree(origStat->getAttribute(0));
                    origCopySt[origStat] = copyStat;
                    if (origStat->comments())
                        copyStat->setComments(origStat->comments());

                    for (int z = 0; z < 3; ++z)
                        fillOrigCopyEx(origStat->expr(z), copyStat->expr(z), origCopyEx);
                }

                int varOfCall = PROC_HEDR;
                //replace calls
                for (auto& places : varCall.callPlaces)
                {
                    if (get<1>(places) == PROC_STAT)
                    {
                        SgStatement* proc = (SgStatement*)get<0>(places);
                        if (SgFile::switchToFile(proc->fileName()) == -1)
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        SgSymbol* newS = &proc->symbol()->copy();
                        newS->changeName(newName.c_str());
                        toChangeSt[proc->fileName()][proc] = newS;
                    }
                    else if (get<1>(places) == FUNC_CALL)
                    {
                        varOfCall = FUNC_HEDR;
                        SgExpression* proc = (SgExpression*)get<0>(places);
                        SgStatement* parent = (SgStatement*)get<2>(places);

                        if (parent == NULL)
                            parent = findReplacedExpression(proc);
                        checkNull(parent, convertFileName(__FILE__).c_str(), __LINE__);

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

                        SgSymbol* newS = &proc->symbol()->copy();
                        set<SgExpression*> allPlaces;
                        for (int z = 0; z < 3; ++z)
                            findAllFunctionCalls(parent->expr(z), varCall.callVariant, parent->fileName(), allPlaces);
                        if (allPlaces.size() == 0)
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                        newS->changeName(newName.c_str());
                        for (auto &elem : allPlaces)
                            toChangeEx[parent->fileName()][elem] = newS;

                        //duplicate names if parent func has implicit (none)
                        auto funcStat = getFuncStat(parent);
                        const string fName = funcStat->symbol()->identifier();
                        if (addedFuncDecl.find(fName) == addedFuncDecl.end())
                            addedFuncDecl[fName] = set<string>();

                        if (addedFuncDecl[fName].find(newName) == addedFuncDecl[fName].end())
                        {
                            findDeclAndDuplicate(funcStat, origName, newName, varOfCall);
                            addedFuncDecl[fName].insert(newName);
                        }
                    }
                    else
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }

                //duplicate interfaces
                auto itInterf = interfaceInfo.find(currFunc);
                if (itInterf != interfaceInfo.end())
                    for (auto& func : itInterf->second)
                        findInterfaceBlockAndDuplicate(func->funcPointer->GetOriginal(), origName, newName, varOfCall);

                // fill additional places to next replaces
                for (int z = 0; z < currFunc->callsFromDetailed.size(); ++z)
                {
                    pair<void*, int> place = currFunc->callsFromDetailed[z].pointerDetailCallsFrom;
                    if (place.second == PROC_STAT)
                    {
                        SgStatement* proc = (SgStatement*)place.first;
                        if (SgFile::switchToFile(proc->fileName()) == -1)
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        SgStatement* copyPoint = origCopySt[proc];
                        const string toFind = proc->symbol()->identifier();

                        auto itF = mapOfFunc.find(toFind.c_str());
                        if (itF == mapOfFunc.end())
                            continue;

                        FuncInfo* toAdd = itF->second;
                        auto infoAdd = funcInfoOfCall.find(toAdd);
                        if (infoAdd == funcInfoOfCall.end())
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                        vector<void*> tmp;
                        infoAdd->second.variantCall.push_back(callVars(make_pair(copyPoint, PROC_STAT), tmp, currFunc->callsFromDetailed[z].parentForPointer));
                    }
                    else if (place.second == FUNC_CALL)
                    {
                        SgExpression* proc = (SgExpression*)place.first;
                        SgStatement* parent = (SgStatement*)currFunc->callsFromDetailed[z].parentForPointer;
                        checkNull(parent, convertFileName(__FILE__).c_str(), __LINE__);
                        if (SgFile::switchToFile(parent->fileName()) == -1)
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                        SgExpression* copyPoint = origCopyEx[proc];
                        const string toFind = proc->symbol()->identifier();

                        auto itF = mapOfFunc.find(toFind.c_str());
                        if (itF == mapOfFunc.end())
                            continue;

                        FuncInfo* toAdd = itF->second;
                        auto infoAdd = funcInfoOfCall.find(toAdd);
                        if (infoAdd == funcInfoOfCall.end())
                            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                        vector<void*> tmp;
                        infoAdd->second.variantCall.push_back(callVars(make_pair(copyPoint, FUNC_CALL), tmp, currFunc->callsFromDetailed[z].parentForPointer));
                    }
                    else
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                }

                numCopyF[currFunc]++;
            }
        }        
        ++iterCount;
    }

    for (auto& elem : toChangeSt)
    {
        if (SgFile::switchToFile(elem.first) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto& toSet : elem.second)
            toSet.first->setSymbol(*toSet.second);
    }

    for (auto& elem : toChangeEx)
    {
        if (SgFile::switchToFile(elem.first) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto& toSet : elem.second)
            toSet.first->setSymbol(*toSet.second);
    }
}

static bool checkForData(FuncInfo* info, map<string, vector<Messages>>& messages)
{
    bool retVal = true;
    if (info->isMain || info->isInterface)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    SgStatement* funcP = info->funcPointer->GetOriginal();
    if (!funcP->switchToFile())
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    for (auto st = funcP; st != funcP->lastNodeOfStmt(); st = st->lexNext())
    {
        if (st->variant() == CONTAINS_STMT)
            break;
        if (isSgExecutableStatement(st))
            break;
        if (st->variant() == VAR_DECL || st->variant() == VAR_DECL_90)
        {
            SgExpression* list = st->expr(0);
            while (list)
            {
                auto value = list->lhs();
                if (value->variant() == ASSGN_OP)
                {
                    wstring bufE, bufR;
                    __spf_printToLongBuf(bufE, L"SAVE or DATA operators prevent function duplication: variable '%s'",
                                         to_wstring(value->lhs()->symbol()->identifier()).c_str());
                    __spf_printToLongBuf(bufR, R174, to_wstring(value->lhs()->symbol()->identifier()).c_str());

                    messages[st->fileName()].push_back(Messages(ERROR, st->lineNumber(), bufR, bufE, 2013));
                    retVal = false;
                }
                else if (value->symbol() && (value->symbol()->attributes() & DATA_BIT))
                {
                    wstring bufE, bufR;
                    __spf_printToLongBuf(bufE, L"SAVE or DATA operators prevent function duplication: variable '%s'", to_wstring(value->symbol()->identifier()).c_str());
                    __spf_printToLongBuf(bufR, R174, to_wstring(value->symbol()->identifier()).c_str());

                    messages[st->fileName()].push_back(Messages(ERROR, st->lineNumber(), bufR, bufE, 2013));
                    retVal = false;
                }
                list = list->rhs();
            }
        }
        else if (st->variant() == SAVE_DECL || st->variant() == DATA_DECL)
        {
            wstring bufE, bufR;
            __spf_printToLongBuf(bufE, L"SAVE or DATA operators prevent function duplication");
            __spf_printToLongBuf(bufR, R173);

            messages[st->fileName()].push_back(Messages(ERROR, st->lineNumber(), bufR, bufE, 2012));
            retVal = false;
        }
    }

    return retVal;
}

bool duplicateFunctions(const map<string, vector<FuncInfo*>> &allFuncs, const map<DIST::Array*, set<DIST::Array*>>& arrayLinksByFuncCall,
                        map<string, vector<Messages>>& messages)
{    
    map<string, FuncInfo*> mapOfFunc;
    createMapOfFunc(allFuncs, mapOfFunc);

    map<FuncInfo*, callInfo> funcInfoOfCall;
    vector<FuncInfo*> toCopy = detect(allFuncs, mapOfFunc, funcInfoOfCall, arrayLinksByFuncCall);
    map<FuncInfo*, set<FuncInfo*>> toCopyGroups = groupByMainCall(toCopy, mapOfFunc);

    vector<FuncInfo*> toCopyVec;
    for (auto& elem : toCopyGroups)
        toCopyVec.push_back(elem.first);

    for (auto& elem : toCopyGroups)
        for (auto &inG : elem.second)
            toCopyVec.push_back(inG);

    bool checkOk = true;
    //check for save vars
    for (auto& func : toCopyVec)
    {
        bool res = checkForData(func, messages);
        checkOk = checkOk && res;
    }
    if (checkOk)
        copyGroup(mapOfFunc, toCopyVec, funcInfoOfCall);
    return checkOk;
}

static void changeFuncNameInDvmDirs(SgExpression* ex, const string& to, const string& from, map<SgSymbol*, string>& changed)
{
    if (ex)
    {
        if (ex->variant() == VAR_REF)
        {
            auto s = ex->symbol();
            if (s->identifier() == from || from.find("::") != string::npos && from.find(s->identifier()) != string::npos)
            {
                changed[s] = s->identifier();
                s->changeName(to.c_str());
            }
        }

        changeFuncNameInDvmDirs(ex->lhs(), to, from, changed);
        changeFuncNameInDvmDirs(ex->rhs(), to, from, changed);
    }
}

static map<SgSymbol*, string> changeFuncNameInDvmDirs(const string& to, const string &from, SgStatement* func)
{
    map<SgSymbol*, string> changed;
    SgStatement* st = func;
    SgStatement* last = st->lastNodeOfStmt();
    while (st != last)
    {
        if (isDVM_stat(st))
            for (int z = 0; z < 3; ++z)
                changeFuncNameInDvmDirs(st->expr(z), to, from, changed);
        st = st->lexNext();
    }
    return changed;
}

static map<FuncInfo*, set<FuncInfo*>> getUniqCopies(const vector<FuncInfo*> &toCmp)
{
    map<string, set<FuncInfo*>> dict;
    string cmpName = "testCmpName";
    for (auto& elem : toCmp)
    {
        SgSymbol *s = elem->funcPointer->GetOriginal()->symbol();
        string saveName = s->identifier();
        s->changeName(cmpName.c_str());
        
        map<SgSymbol*, string> changed;
        if (elem->funcPointer->GetOriginal()->variant() == FUNC_HEDR)
            changed = changeFuncNameInDvmDirs(cmpName, saveName, elem->funcPointer->GetOriginal());

        const char* buf = elem->funcPointer->GetOriginal()->unparse();
        dict[buf].insert(elem);

        s->changeName(saveName.c_str());
        for (auto& elem : changed)
            elem.first->changeName(elem.second.c_str());
    }

    map<FuncInfo*, set<FuncInfo*>> uniq;
    for (auto& elem : dict)
    {
        auto it = elem.second.begin();
        FuncInfo* base = *it;
        uniq[base] = set<FuncInfo*>();
        for (it++; it != elem.second.end(); it++)
            uniq[base].insert(*it);
    }

    return uniq;
}

static map<string, set<FuncInfo*>> removed;
static map<string, set<SgStatement*>> copied;
static map<string, string> newNamesOfUniqCopies;
map<SgSymbol*, pair<string, string>> replaced;
static map<string, set<SgStatement*>> hiddenInterfaceBlocks;
static map<string, set<SgStatement*>> unitedDecls;

static bool removeThisFunctions(const string &file, const map<FuncInfo*, set<FuncInfo*>> &uniqCopies, const vector<FuncInfo*>& toRem)
{
    bool wasRemoved = false;
    for (auto& rem : toRem)
    {
        if (uniqCopies.find(rem) == uniqCopies.end() && removed[file].find(rem) == removed[file].end())
        {
            SgStatement* orig = rem->funcPointer->GetOriginal();
            SgStatement* last = orig->lastNodeOfStmt();
            while (orig != last)
            {
                orig->setVariant(orig->variant() * -1);
                orig = orig->lexNext();
            }
            wasRemoved = true;
            removed[file].insert(rem);
        }
    }

    return wasRemoved;
}

static bool removeThisFunctions(const string& file, FuncInfo* toRem)
{
    vector<FuncInfo*> tmp = { toRem };
    map<FuncInfo*, set<FuncInfo*>> info;
    return removeThisFunctions(file, info, tmp);
}

static void restoreFunctions(const string &file)
{
    for (auto& elem : removed[file])
    {
        SgStatement* orig = elem->funcPointer->GetOriginal();
        SgStatement* last = orig->lastNodeOfStmt();
        while (orig != last)
        {
            orig->setVariant(orig->variant() * -1);
            orig = orig->lexNext();
        }
    }
    removed[file].clear();
}

static void createCopies(const string &file, const string &baseName, const  map<FuncInfo*, set<FuncInfo*>> &uniqCopies)
{
    int newNum = 0;
    for (auto& toCopy : uniqCopies)
    {
        const string newName = newNum > 0 ? baseName + "_spfr_" + to_string(newNum) : baseName;
        ++newNum;

        const string clearNameS = getClearName(newName);
        SgStatement* duplicated = duplicateProcedure(toCopy.first->funcPointer->GetOriginal(), &clearNameS, true, true);
        if (duplicated->variant() == FUNC_HEDR)
            changeFuncNameInDvmDirs(clearNameS, toCopy.first->funcPointer->GetOriginal()->symbol()->identifier(), duplicated);

        copied[toCopy.first->funcPointer->GetOriginal()->fileName()].insert(duplicated);
        newNamesOfUniqCopies[toCopy.first->funcName] = newName;
        for (auto &theSame : toCopy.second)
            newNamesOfUniqCopies[theSame->funcName] = newName;

        removeThisFunctions(file, toCopy.first);
    }
}

static void doReplacements(SgSymbol* s, map<SgSymbol*, pair<string, string>> &replaced)
{
    auto itR = replaced.find(s);
    if (itR == replaced.end())
    {
        auto it = newNamesOfUniqCopies.find(s->identifier());
        if (it != newNamesOfUniqCopies.end())
        {
            s->changeName(it->second.c_str());
            replaced.insert(itR, make_pair(s, *it));
        }
        else // for modules and contains?
        {
            vector<string> toReplace;
            string ident = s->identifier();
            for (auto& elem : newNamesOfUniqCopies)
                if (getClearName(elem.first) == ident)
                    toReplace.push_back(elem.second);

            if (toReplace.size() == 1)
            {
                s->changeName(getClearName(toReplace[0]).c_str());
                replaced.insert(itR, make_pair(s, make_pair(ident, toReplace[0])));
            }
        }
    }
}

static void doReplacements(SgExpression* ex, map<SgSymbol*, pair<string, string>> *replaced)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
        {
            SgSymbol* s = ex->symbol();
            if (replaced)
                doReplacements(s, *replaced);
            else
                doReplacements(s, ::replaced);
        }

        doReplacements(ex->lhs(), replaced);
        doReplacements(ex->rhs(), replaced);
    }
}

static void doReplacements(SgStatement* st, SgStatement* last, map<SgSymbol*, pair<string, string>> *replaced = NULL)
{
    while (st != last)
    {
        if (st->variant() == PROC_STAT)
        {
            SgSymbol *s = st->symbol();

            if (replaced)
                doReplacements(s, *replaced);
            else
                doReplacements(s, ::replaced);
        }

        for (int z = 0; z < 3; ++z)
            doReplacements(st->expr(z), replaced);

        st = st->lexNext();
    }
}

static void removeUnnecessaryReplacedFunctionDecls(SgStatement* st, SgStatement* last)
{
    map<int, pair<string, string>> replacedIdx;
    for (auto& elem : replaced)
    {
        if (replacedIdx.find(elem.first->id()) != replacedIdx.end())
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
        replacedIdx[elem.first->id()] = elem.second;
    }

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

    SgStatement* orig = st;
    while (st != last)
    {
        if (st->variant() == CONTAINS_STMT)
            break;

        if (st->variant() == VAR_DECL || st->variant() == VAR_DECL_90)
        {
            SgExpression* ex = st->expr(0);
            
            /*while (ex)
            {
                auto arg = ex->lhs();
                if (arg && arg->symbol())
                {
                    auto s = arg->symbol();
                    auto it = replacedIdx.find(s->id());

                    //restore original name and copy symbol
                    if (it != replacedIdx.end())
                    {
                        auto copy = &s->copy();
                        copy->changeName(it->second.first.c_str());
                        arg->setSymbol(copy);
                    }
                }
                ex = ex->rhs();
            }*/

            bool hasReplaces = false;
            while (ex)
            {
                auto arg = ex->lhs();
                if (arg && arg->symbol())
                {
                    auto s = arg->symbol();
                    auto it = replacedIdx.find(s->id());

                    //restore original name and copy symbol
                    if (it != replacedIdx.end())
                    {
                        hasReplaces = true;
                        break;
                    }
                }
                ex = ex->rhs();
            }

            if (hasReplaces)
            {
                auto copy = st->copy();
                //united decls
                ex = copy.expr(0);
                SgExpression* prev = NULL;

                set<string> has;
                while (ex)
                {
                    auto arg = ex->lhs();
                    if (arg && arg->symbol())
                    {
                        auto s = arg->symbol();
                        if (has.find(s->identifier()) != has.end())
                        {
                            if (prev)
                                prev->setRhs(ex->rhs());
                            else
                                copy.setExpression(0, ex->rhs());

                            ex = ex->rhs();
                            continue;
                        }
                        else
                            has.insert(s->identifier());
                    }
                    prev = ex;
                    ex = ex->rhs();
                }

                st->insertStmtBefore(copy, *st->controlParent());
                st->setVariant(st->variant() * -1);
                unitedDecls[current_file->filename()].insert(st);
            }
        }

        st = st->lexNext();
    }
}

static void hiddenInterfaces(SgStatement* func)
{
    SgStatement* last = func;
    const string fileName = func->fileName();

    for (auto st = func->lexNext(); st != last; st = st->lexNext())
    {
        if (st->variant() == CONTAINS_STMT)
            break;
        if (isSgExecutableStatement(st) && !isDVM_stat(st) && !isSPF_stat(st))
            break;

        if (st->variant() == INTERFACE_STMT)
        {
            int total = 0;
            map<string, set<SgStatement*>> objects;
            SgStatement* iEnd = st->lastNodeOfStmt();
            for (auto iSt = st->lexNext(); iSt != iEnd; iSt = iSt->lexNext())
            {
                if (iSt->variant() == PROC_HEDR || iSt->variant() == FUNC_HEDR)
                    objects[iSt->symbol()->identifier()].insert(iSt);
                total++;
                iSt = iSt->lastNodeOfStmt();
            }

            if (objects.size() != total)
            {
                for (auto& uniq : objects)
                {
                    int first = 0;
                    for (auto& obj : uniq.second)
                    {
                        if (first)
                        {
                            obj->setVariant(-obj->variant());
                            hiddenInterfaceBlocks[fileName].insert(obj);
                            --total;
                        }
                        ++first;
                    }
                }
            }

            if (total == 0)
            {
                st->setVariant(-st->variant());
                hiddenInterfaceBlocks[fileName].insert(st);
            }
            st = iEnd;
        }
    }
}

static void replaceNewNames(const map<string, vector<FuncInfo*>> &allFuncs)
{
    for (auto& byfile : allFuncs)
    {
        if (SgFile::switchToFile(byfile.first) == -1)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        for (auto& func : byfile.second)
        {
            if (func->funcPointer->GetOriginal()->variant() < 0)
                continue;

            SgStatement* p = func->funcPointer->GetOriginal();
            doReplacements(p, p->lastNodeOfStmt());
            hiddenInterfaces(p);
            removeUnnecessaryReplacedFunctionDecls(p, p->lastNodeOfStmt());
        }

        map<SgSymbol*, pair<string, string>> replaced_tmp;
        if (copied.find(byfile.first) != copied.end())
        {
            for (auto& func : copied[byfile.first])
                doReplacements(func, func->lastNodeOfStmt(), &replaced_tmp);
        }
    }    
}

void removeCopies(const map<string, vector<FuncInfo*>>& allFuncs)
{
    bool changed = true;
    while (changed)
    {
        changed = false;
        for (auto& byFile : allFuncs)
        {
            string fileS = byFile.first;
            if (SgFile::switchToFile(fileS) == -1)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            for (auto& func : byFile.second)
            {
                if (func->fullCopiesOfThisFunction.size() && removed[fileS].find(func) == removed[fileS].end())
                {
                    map<FuncInfo*, set<FuncInfo*>> uniqCopies = getUniqCopies(func->fullCopiesOfThisFunction);
                    if (uniqCopies.size() == func->fullCopiesOfThisFunction.size())
                        continue;
                    if (uniqCopies.size() == 0)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    bool ret1 = removeThisFunctions(fileS, uniqCopies, func->fullCopiesOfThisFunction);
                    bool ret2 = removeThisFunctions(fileS, func);
                    changed = ret1 || ret2;
                    if (changed)
                        createCopies(fileS, func->funcName, uniqCopies);
                }
            }
        }

        if (changed)
            replaceNewNames(allFuncs);
    }
}

void restoreCopies(SgFile* file)
{
    const string fileName = file->filename();

    auto itU = unitedDecls.find(fileName);
    if (itU != unitedDecls.end())
    {
        for (auto& st : itU->second)
        {
            st->setVariant(st->variant() * -1);
            auto prev = st->lexPrev();
            prev->deleteStmt();
        }

        unitedDecls.erase(itU);
    }

    restoreFunctions(fileName);
    for (auto& elem : copied[file->filename()])
        elem->deleteStmt();
    copied[file->filename()].clear();

    for (auto& elem : replaced)
        elem.first->changeName(elem.second.first.c_str());
    replaced.clear();
    newNamesOfUniqCopies.clear();

    auto it = hiddenInterfaceBlocks.find(fileName);
    if (it != hiddenInterfaceBlocks.end())
    {
        for (auto& obj : it->second)
            if (obj->variant() < 0)
                obj->setVariant(-obj->variant());

        hiddenInterfaceBlocks.erase(it);
    }
}
