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

#include "Utils/SgUtils.h"
#include "rename_symbols.h"

using std::vector;
using std::map;
using std::multimap;
using std::set;
using std::make_pair;
using std::string;
using std::to_string;

static set<string> allNames;
static map<SgSymbol*, set<SgSymbol*>> usesByModSym;

static multimap<string, SgSymbol*> saves; //for saves
static multimap<string, SgSymbol*> params; //for params

inline static string generateUnique(const set<string> &banned, const string &old) {
    static int counter = 0;
    string new_name = old + "_" + to_string(++counter);
    while (banned.count(new_name))
        new_name = old + "_" + to_string(++counter);
    return new_name;
}

inline static void renameUnique(SgSymbol* s, const set<string>& banned = allNames) {
    string new_name = generateUnique(banned, s->identifier());
    allNames.insert(new_name);

    SgSymbol* orig = OriginalSymbol(s);
    orig->changeName(new_name.c_str());
    if (usesByModSym.count(orig))
        for (auto& e : usesByModSym[orig])
            e->changeName(new_name.c_str());
}

static void addToScope(SgSymbol* s, SgProject* project) {
    if (isSgVariableSymb(s) || isSgConstantSymb(s)) {
        SgSymbol* orig_mod = OriginalSymbol(s);

        if (orig_mod != s) {
            usesByModSym[orig_mod].insert(s);
            return;
        }

        if (s->scope() && s->scope()->variant() == MODULE_STMT)
            usesByModSym[s];

        if (s->attributes() & SAVE_BIT) 
            saves.insert(make_pair(s->identifier(), s));

        if (s->attributes() & PARAMETER_BIT) 
            params.insert(make_pair(s->identifier(), s));
    }
}

void runRenameSymbolsByFiles(SgFile* file, SgProject* project) {
    for (SgSymbol* s = file->firstSymbol(); s; s = s->next()) {
        allNames.insert(s->identifier());
        if (s->variant() == VARIABLE_NAME || isSgConstantSymb(s))
            addToScope(s, project);        
    }
}

void runRenameSymbols(SgProject* project, const map<string, CommonBlock*> &commonBlocks) {
    int n = project->numberOfFiles();
    for (int i = 0; i < n; i++) {
        SgFile* f = &(project->file(i));
        for (SgSymbol* s = f->firstSymbol(); s; s = s->next()) {
            if (s->variant() == VARIABLE_NAME || isSgConstantSymb(s)) {
                string name = s->identifier();
                SgSymbol* orig = OriginalSymbol(s);
                bool is_mod_sym = (usesByModSym.find(orig) != usesByModSym.end());

                auto is_mod_iter = std::find_if(usesByModSym.begin(),
                        usesByModSym.end(), 
                        [&name, orig](const std::pair<SgSymbol*, set<SgSymbol*>> &p) {
                            return (name == p.first->identifier()) && (p.first != orig);
                        });

                bool intersect_with_mod = (is_mod_iter != usesByModSym.end());

                //fix intersects with MODULE symbols
                if (intersect_with_mod) {
                    renameUnique(s);
                    continue;
                }

                if (is_mod_sym)
                    continue;

                //fix intersects inside SAVE variables
                if ((s->attributes() & SAVE_BIT) ? saves.count(name) > 1 : saves.count(name)) {
                    renameUnique(s);
                    name = s->identifier();
                    
                    if (s->attributes() & SAVE_BIT) {
                        saves.erase(find_if(saves.begin(),
                            saves.end(),
                            [&s](const std::pair<string, SgSymbol*>& p) {
                                return p.second == s;
                            }));
                    
                        saves.insert(make_pair(name, s));
                    }
                    continue;
                }

                //fix intersects inside PARAMETER variables
                if ((s->attributes() & PARAMETER_BIT) ? params.count(name) > 1 : params.count(name)) {
                    SgConstantSymb* cSym;
                    bool need_to_rename = false;
                    if (cSym = isSgConstantSymb(s)) {
                        string val = cSym->constantValue()->unparse();
                        string const_name = cSym->identifier();
                        auto found = find_if(params.begin(),
                            params.end(),
                            [&const_name, &val, project, i](const std::pair<string, SgSymbol*>& p) {
                                SgConstantSymb* cmpSym = isSgConstantSymb(p.second);
                                project->file(cmpSym->getFileId());

                                string cmp_val = cmpSym->constantValue()->unparse();
                                project->file(i);

                                return (const_name == cmpSym->identifier()) && (cmp_val != val);
                            });

                        need_to_rename = (found != params.end());
                    } else
                        need_to_rename = true;

                    if (need_to_rename) {
                        renameUnique(s);
                        name = s->identifier();
                        if (s->attributes() & PARAMETER_BIT) {
                            params.erase(find_if(params.begin(),
                                params.end(),
                                [&s](const std::pair<string, SgSymbol*>& p) {
                                    return p.second == s;
                                }));

                            params.insert(make_pair(name, s));
                        }
                    }
                }
            }
        }
    }

    for (auto& e : commonBlocks) {
        const CommonBlock& block = *e.second;

        for (auto& grouped : block.getGroupedVars()) {
            const vector<Variable*>& vars = grouped.second;
            string new_name = generateUnique(allNames, vars[0]->getName());

            for (auto& var : vars) 
                for (auto& varUse : var->getAllUse()) 
                    varUse.getUseS()->changeName(new_name.c_str());
        }
    }
}

