#pragma once

#include <map>
#include <set>
#include <vector>
#include "../Sapfor.h"

using std::vector;
using std::map;
using std::set;

#define list vector<Pass>

static map<passes, vector<passes>> *passDeps;
static map<passes, set<passes>> passRemovals;

class Pass
{
private:
    passes name;
public:
    const Pass& operator<=(const Pass &right) const
    {
        if ((*passDeps).find(right.name) == (*passDeps).end())
            (*passDeps)[right.name] = vector<passes>();
        (*passDeps)[right.name].push_back(this->name);
        return right;
    }

    Pass& operator<=(Pass &right) const
    {
        if ((*passDeps).find(right.name) == (*passDeps).end())
            (*passDeps)[right.name] = vector<passes>();
        (*passDeps)[right.name].push_back(this->name);
        return right;
    }

    const list& operator<=(const list &right_vec) const
    {
        for (int i = 0; i < right_vec.size(); ++i)
        {
            if ((*passDeps).find(right_vec[i].name) == (*passDeps).end())
                (*passDeps)[right_vec[i].name] = vector<passes>();
            (*passDeps)[right_vec[i].name].push_back(this->name);
        }
        return right_vec;
    }

    friend const Pass& operator<=(const list &left_vec, const Pass &right)
    {
        if ((*passDeps).find(right.name) == (*passDeps).end())
            (*passDeps)[right.name] = vector<passes>();

        for (int i = 0; i < left_vec.size(); ++i)
            (*passDeps)[right.name].push_back(left_vec[i].name);

        return right;
    }

    friend const list& operator<=(const list &left_vec, const list &right_vec)
    {
        for (int k = 0; k < right_vec.size(); ++k)
        {
            if ((*passDeps).find(right_vec[k].name) == (*passDeps).end())
                (*passDeps)[right_vec[k].name] = vector<passes>();

            for (int i = 0; i < left_vec.size(); ++i)
                (*passDeps)[right_vec[k].name].push_back(left_vec[i].name);
        }
        return right_vec;
    }

    friend void operator-=(const list& left_vec, const list& right_vec)
    {
        /* erase each pass in right_vec from dependency tree for each pass in left_vec */
        
        for(const auto& left : left_vec)
        {
            auto& eraseFrom = passRemovals[left.name];

            for(const auto& right : right_vec)
                eraseFrom.insert(right.name);
        }
    }

    friend void operator-=(const list& left_vec, const Pass& right)
    {
        left_vec -= list({right});
    }
    
    void operator-=(const list& right_vec) const
    {
        list({ *this }) -= right_vec;
    }

    void operator-=(const Pass& right) const
    {
        list({ *this }) -= list({ right });
    }

    void applyRemovals()
    {
        map<passes, set<passes>> to_process, processed;
        to_process[name] = {};

        while (!to_process.empty())
        {
            map<passes, set<passes>> to_process_next;

            for (const auto& pass : to_process)
            {
                auto processed_it = processed.find(pass.first);
                auto& done_removals = processed_it != processed.end() ? processed_it->second : processed[pass.first];

                set<passes> removals_to_do;
                bool process_pass = false;

                if (processed_it == processed.end())
                {
                    removals_to_do = done_removals = pass.second;
                    process_pass = true;
                }
                else 
                {
                    const auto& needed_removals = pass.second;

                    set_difference(needed_removals.begin(), needed_removals.end(), done_removals.begin(), done_removals.end(),
                                   inserter(removals_to_do, removals_to_do.begin()));
                    process_pass = needed_removals.size() != 0;

                    done_removals.insert(removals_to_do.begin(), removals_to_do.end());
                }

                if (process_pass)
                {
                    processed[pass.first] = pass.second;

                    auto removals_it = passRemovals.find(pass.first);
                    
                    if (removals_it != passRemovals.end())
                    {
                        auto& removals_saved = removals_it->second;

                        set<passes> add;
                        set_difference(removals_saved.begin(), removals_saved.end(), done_removals.begin(), done_removals.end(),
                            inserter(add, add.begin()));

                        done_removals.insert(add.begin(), add.end());
                        removals_to_do.insert(add.begin(), add.end());
                    }
                    
                    auto deps_it = passDeps->find(pass.first);

                    if (deps_it != passDeps->end())
                    {
                        auto& deps = deps_it->second;
                        
                        for (auto dep_it = deps.begin(); dep_it != deps.end();)
                        {
                            if (removals_to_do.find(*dep_it) == removals_to_do.end())
                                to_process_next[*(dep_it++)].insert(removals_to_do.begin(), removals_to_do.end());
                            else
                                dep_it = deps.erase(dep_it);
                        }
                    }
                }
            }

            to_process = to_process_next;
        }
    }

    Pass(passes name) : name(name) { }
};

static void depsToGraphViz(const map<passes, vector<passes>> &passDepsIn)
{
    FILE *file = fopen("pass_tree.txt", "w");
    if (!file)
        printf("ERROR in creating 'pass_tree.txt' file\n");
    else
    {
        fprintf(file, "digraph G {");
        for (auto it = passDepsIn.begin(); it != passDepsIn.end(); ++it)
        {
            for (int k = 0; k < it->second.size(); ++k)
                fprintf(file, "\"%s\" -> \"%s\";\n", passNames[it->first], passNames[it->second[k]]);
        }
        fprintf(file, "}\n");
        fclose(file);
        printf("write to 'pass_tree.txt' file complited\n");
    }
}

void InitPassesDependencies(map<passes, vector<passes>> &passDepsIn, set<passes> &passesIgnoreStateDone, bool printTree = false)
{
    passDepsIn.clear();

    passDeps = &passDepsIn;

    Pass(PREPROC_SPF) <= Pass(CREATE_INTER_TREE);

    list({ CREATE_INTER_TREE, CORRECT_VAR_DECL }) <= list({ GCOV_PARSER, PREDICT_SCHEME, INSERT_INTER_TREE });

    list({ FILE_LINE_INFO, BUILD_INCLUDE_DEPENDENCIES }) <= Pass(CORRECT_VAR_DECL);

    Pass(DEF_USE_STAGE1) <= Pass(DEF_USE_STAGE2);

    list({ VERIFY_DVM_DIRS, VERIFY_COMMON, FILL_COMMON_BLOCKS, PRIVATE_CALL_GRAPH_STAGE1, PRIVATE_CALL_GRAPH_STAGE2, MACRO_EXPANSION, CONVERT_ASSIGN_TO_LOOP, DEF_USE_STAGE1, DEF_USE_STAGE2, FILL_PARALLEL_REG_IR, CALL_GRAPH_IR }) <= list({ SUBST_EXPR, SUBST_EXPR_RD, BUILD_IR });

    Pass(VERIFY_COMMON) <= Pass(FILL_COMMON_BLOCKS);

    Pass(BUILD_IR) <= Pass(SUBST_EXPR) <= Pass(SUBST_EXPR_AND_UNPARSE);

    Pass(BUILD_IR) <= Pass(SUBST_EXPR_RD) <= Pass(SUBST_EXPR_RD_AND_UNPARSE);

    list({ LOOP_ANALYZER_DATA_DIST_S1, SUBST_EXPR_RD } ) <= Pass(PRIVATE_REMOVING_ANALYSIS);
    list({ PRIVATE_REMOVING_ANALYSIS, REVERT_SUBST_EXPR_RD }) <= Pass(PRIVATE_REMOVING);

    Pass(RESTORE_LOOP_FROM_ASSIGN) <= list({ SUBST_EXPR_AND_UNPARSE, SUBST_EXPR_RD_AND_UNPARSE });

    Pass(FILL_COMMON_BLOCKS) <= Pass(GET_ALL_ARRAY_DECL) <= list({ CALL_GRAPH_IR, INSERT_NO_DISTR_FLAGS_FROM_GUI });

    Pass(LOOP_GRAPH) <= Pass(PRIVATE_CALL_GRAPH_STAGE3) <= list(FIND_FUNC_TO_INCLUDE, PRIVATE_ANALYSIS_IR) <= list({ LOOP_ANALYZER_DATA_DIST_S0, LOOP_ANALYZER_DATA_DIST_S1, ONLY_ARRAY_GRAPH, LOOP_ANALYZER_ALIGNS });

    Pass(PRIVATE_ANALYSIS_IR) <= Pass(LOOP_ANALYZER_NODIST);

    Pass(CORRECT_VAR_DECL) <= list({ VERIFY_DVM_DIRS, VERIFY_OPERATORS, VERIFY_FORMAT, VERIFY_ENDDO, PREPROC_SPF, VERIFY_INCLUDES, PREPROC_ALLOCATES, CHECK_FUNC_TO_INCLUDE, FILL_PAR_REGIONS_LINES, CONVERT_ASSIGN_TO_LOOP, VERIFY_COMMON, VERIFY_EQUIVALENCE, PRINT_PAR_REGIONS_ERRORS }) <= Pass(CODE_CHECKER_PASSES);

    list({ VERIFY_OPERATORS, VERIFY_ENDDO, VERIFY_INCLUDES, PREPROC_SPF, PREPROC_ALLOCATES, GET_ALL_ARRAY_DECL, GCOV_PARSER }) <= list({ CALL_GRAPH, MACRO_EXPANSION, DEF_USE_STAGE1 });

    list({ VERIFY_OPERATORS, VERIFY_ENDDO, VERIFY_INCLUDES, PREPROC_ALLOCATES, FILL_PARALLEL_REG_IR }) <= list({ FILL_COMMON_BLOCKS, GET_ALL_ARRAY_DECL, PARSE_OMP_DIRS }) <= Pass(PREPROC_SPF);

    Pass(CHECK_PAR_REG_DIR) <= Pass(FILL_PARALLEL_REG_IR);

    list({ CODE_CHECKER_PASSES, GET_ALL_ARRAY_DECL, CALL_GRAPH2, SUBST_EXPR_RD, ARRAY_ACCESS_ANALYSIS_FOR_CORNER }) <= list({ LOOP_ANALYZER_NODIST, LOOP_ANALYZER_DATA_DIST_S0, LOOP_ANALYZER_DATA_DIST_S1, ONLY_ARRAY_GRAPH });

    list({ LOOP_ANALYZER_NODIST, REMOVE_OMP_DIRS })  <= Pass(INSERT_PARALLEL_DIRS_NODIST);

    Pass(CHECK_ARGS_DECL) <= Pass(CREATE_TEMPLATE_LINKS);

    Pass(LOOP_ANALYZER_DATA_DIST_S0) <= Pass(LOOP_ANALYZER_DATA_DIST_S1) <= Pass(LOOP_ANALYZER_DATA_DIST_S2) <= Pass(CREATE_TEMPLATE_LINKS) <= Pass(LOOP_ANALYZER_COMP_DIST);

    list({ VERIFY_OPERATORS, VERIFY_ENDDO, VERIFY_FORMAT, SUBST_EXPR_RD, CONVERT_ASSIGN_TO_LOOP }) <= Pass(LOOP_GRAPH) <= Pass(CALL_GRAPH) <= Pass(CALL_GRAPH2);

    Pass(MACRO_EXPANSION) <= Pass(CALL_GRAPH);

    list({ PREPROC_SPF, PROCESS_IO, CALL_GRAPH2, CONVERT_SAVE_TO_MODULE, REVERT_SUBST_EXPR_RD }) <= Pass(CREATE_CHECKPOINTS);
    
    Pass(FILL_PAR_REGIONS_LINES) <= Pass(VERIFY_EQUIVALENCE);

    Pass(LOOP_ANALYZER_DATA_DIST_S2) <= Pass(CREATE_NESTED_LOOPS);

    list({ CORRECT_VAR_DECL, PREPROC_SPF }) <= list({ LOOP_GRAPH, CALL_GRAPH, CALL_GRAPH2 });

    list({ PREPROC_SPF, CALL_GRAPH2 }) <= Pass(FILL_PAR_REGIONS_LINES) <= list({ EXPAND_EXTRACT_PAR_REGION, CHECK_FUNC_TO_INCLUDE, FIND_FUNC_TO_INCLUDE, CHECK_ARGS_DECL });

    list({ PREPROC_SPF, CALL_GRAPH2, FILL_PAR_REGIONS_LINES }) <= Pass(FILL_PAR_REGIONS) <= Pass(RESOLVE_PAR_REGIONS);

    list({ REVERT_SUBST_EXPR_RD, CONVERT_LOOP_TO_ASSIGN }) <= Pass(RESOLVE_PAR_REGIONS);

    list({ FILL_PAR_REGIONS, REVERT_SUBST_EXPR_RD, CONVERT_LOOP_TO_ASSIGN }) <= Pass(REMOVE_DIST_ARRAYS_FROM_IO);

    Pass(REVERT_SUBST_EXPR_RD) <= Pass(EXPAND_EXTRACT_PAR_REGION);
    
    Pass(FILL_PAR_REGIONS) <= Pass(PRINT_PAR_REGIONS_ERRORS);

    list({ PREPROC_SPF, CORRECT_VAR_DECL }) <= Pass(FILL_PAR_REGIONS_LINES);

    list({ LOOP_ANALYZER_COMP_DIST, REMOVE_OMP_DIRS }) <= list({ CREATE_DISTR_DIRS, CREATE_PARALLEL_DIRS, INSERT_PARALLEL_DIRS });

    Pass(CALL_GRAPH2) <= list({ ONLY_ARRAY_GRAPH, CREATE_NESTED_LOOPS, FIND_FUNC_TO_INCLUDE, CHECK_FUNC_TO_INCLUDE, FIND_PARAMETERS, GET_STATS_FOR_PREDICTOR });

    Pass(CALL_GRAPH2) <= list({ PURE_SAVE_TO_PARAMS, PURE_MODULE_TO_PARAMS, PURE_COMMON_TO_PARAMS, PURE_INTENT_INSERT });

    Pass(REVERT_SUBST_EXPR_RD) <= list({ PURE_SAVE_TO_PARAMS, PURE_MODULE_TO_PARAMS, PURE_COMMON_TO_PARAMS, PURE_INTENT_INSERT });

    list({ CORRECT_VAR_DECL, REVERT_SUBST_EXPR_RD, VERIFY_INCLUDES }) <= list({ CONVERT_TO_ENDDO, CORRECT_CODE_STYLE, REMOVE_DVM_DIRS, REMOVE_DVM_DIRS_TO_COMMENTS, REMOVE_SPF_DIRS, REMOVE_DVM_INTERVALS });

    list({ CALL_GRAPH2, CONVERT_LOOP_TO_ASSIGN, REVERT_SUBST_EXPR_RD, RESTORE_LOOP_FROM_ASSIGN }) <= Pass(INLINE_PROCEDURES);

    list({ CONVERT_LOOP_TO_ASSIGN, CORRECT_FORMAT_PLACE }) <= list({ CONVERT_TO_ENDDO, CORRECT_CODE_STYLE, INSERT_INCLUDES, REMOVE_DVM_DIRS, REMOVE_DVM_DIRS_TO_COMMENTS, REMOVE_SPF_DIRS, REMOVE_DVM_INTERVALS });

    list({ CORRECT_VAR_DECL, REVERT_SUBST_EXPR_RD }) <= list({ INSERT_INCLUDES, UNPARSE_FILE, SET_TO_ALL_DECL_INIT_ZERO });

    Pass(CALL_GRAPH2) <= Pass(PRIVATE_ARRAYS_SHRINKING_ANALYSIS) <= Pass(PRIVATE_ARRAYS_SHRINKING);

    list({ CALL_GRAPH2, LOOP_ANALYZER_ALIGNS, REVERT_SUBST_EXPR_RD }) <= list({ PRIVATE_ARRAYS_EXPANSION, PRIVATE_ARRAYS_SHRINKING });

    list({ GCOV_PARSER, CREATE_INTER_TREE, CALL_GRAPH, CALL_GRAPH2 }) <= Pass(CREATE_PARALLEL_REGIONS);
        
    list({ PRIVATE_CALL_GRAPH_STAGE1, PRIVATE_CALL_GRAPH_STAGE2, MACRO_EXPANSION, CONVERT_ASSIGN_TO_LOOP, DEF_USE_STAGE1, DEF_USE_STAGE2, LOOP_GRAPH, CALL_GRAPH, PRIVATE_ANALYSIS_IR, FIND_FUNC_TO_INCLUDE }) <= Pass(INSERT_REGIONS);

    list({ LOOP_ANALYZER_DATA_DIST_S1, REVERT_SUBST_EXPR_RD }) <= list({ LOOPS_SPLITTER, LOOPS_COMBINER, UNROLL_LOOPS, INSERT_REGIONS });

    list({ CALL_GRAPH2, REVERT_SUBST_EXPR_RD, CONVERT_LOOP_TO_ASSIGN, RESTORE_LOOP_FROM_ASSIGN }) <= list({ DUPLICATE_FUNCTIONS, REMOVE_UNUSED_FUNCTIONS });

    list({ CONVERT_LOOP_TO_ASSIGN, RESTORE_LOOP_FROM_ASSIGN }) <= list({ LOOPS_SPLITTER, LOOPS_COMBINER, PRIVATE_ARRAYS_EXPANSION, PRIVATE_ARRAYS_SHRINKING, CREATE_PARALLEL_REGIONS, PURE_SAVE_TO_PARAMS, PURE_MODULE_TO_PARAMS, PURE_COMMON_TO_PARAMS, PURE_INTENT_INSERT, PRIVATE_REMOVING });

    list({ GET_ALL_ARRAY_DECL, FILL_PARALLEL_REG_IR }) <= Pass(CONVERT_ASSIGN_TO_LOOP);

    list({ CALL_GRAPH2, REVERT_SUBST_EXPR_RD }) <= Pass(RENAME_SYMBOLS);

    list({ BUILD_IR, CALL_GRAPH }) <= Pass(LIVE_ANALYSIS_IR);
    
    list({ BUILD_IR, LOOP_GRAPH, LIVE_ANALYSIS_IR }) <= Pass(PRIVATE_ANALYSIS_IR);

    Pass(FILE_LINE_INFO) <= Pass(GET_MIN_MAX_BLOCK_DIST);

    Pass(CALL_GRAPH2) <= Pass(FIX_COMMON_BLOCKS);

    Pass(REMOVE_OMP_DIRS) <= Pass(REMOVE_OMP_DIRS_TRANSFORM);

    Pass(VERIFY_INCLUDES) <= Pass(RENAME_INLCUDES);

    list({ CALL_GRAPH2, REVERT_SUBST_EXPR_RD }) <= Pass(REMOVE_DEAD_CODE);
    list({ REMOVE_DEAD_CODE, CONVERT_LOOP_TO_ASSIGN, RESTORE_LOOP_FROM_ASSIGN }) <= Pass(REMOVE_DEAD_CODE_AND_UNPARSE);

    list({ VERIFY_INCLUDES, CORRECT_VAR_DECL }) <= Pass(SET_IMPLICIT_NONE);

    list({ ARRAY_PROPAGATION, CALL_GRAPH2, CALL_GRAPH, BUILD_IR, LOOP_GRAPH, LOOP_ANALYZER_DATA_DIST_S2 }) <= Pass(FIND_PRIVATE_ARRAYS_ANALYSIS);
    list({ FIND_PRIVATE_ARRAYS_ANALYSIS, CONVERT_LOOP_TO_ASSIGN, RESTORE_LOOP_FROM_ASSIGN, REVERT_SUBST_EXPR_RD, ARRAY_PROPAGATION_RESTORE }) <= Pass(FIND_PRIVATE_ARRAYS);

    list({ BUILD_IR, CALL_GRAPH2, RESTORE_LOOP_FROM_ASSIGN, REVERT_SUBST_EXPR_RD }) <= Pass(MOVE_OPERATORS);
    Pass(CREATE_TEMPLATE_LINKS) <= Pass(MERGE_REGIONS);

    passesIgnoreStateDone.insert({ CREATE_PARALLEL_DIRS, INSERT_PARALLEL_DIRS, INSERT_SHADOW_DIRS, EXTRACT_PARALLEL_DIRS, 
                                   EXTRACT_SHADOW_DIRS, CREATE_REMOTES, UNPARSE_FILE, REMOVE_AND_CALC_SHADOW,
                                   REVERSE_CREATED_NESTED_LOOPS, PREDICT_SCHEME, CALCULATE_STATS_SCHEME, REVERT_SPF_DIRS, CLEAR_SPF_DIRS, TRANSFORM_SHADOW_IF_FULL,
                                   RESTORE_LOOP_FROM_ASSIGN, RESTORE_LOOP_FROM_ASSIGN_BACK, INSERT_REGIONS, REMOVE_COPIES, RESTORE_COPIES, SHADOW_GROUPING,
                                   SWAP_LOOPS, RESTORE_SWAP_LOOPS, TEST_PASS, GROUP_ACTUAL_AND_REMOTE, GROUP_ACTUAL_AND_REMOTE_RESTORE, ADD_TEMPL_TO_USE_ONLY });
        
    //only for print
    if (printTree)
    {
        list({ CREATE_PARALLEL_DIRS, PRIVATE_ANALYSIS_IR, CREATE_REMOTES, REVERT_SUBST_EXPR_RD, UNPARSE_FILE, EXTRACT_PARALLEL_DIRS }) <= Pass(INSERT_PARALLEL_DIRS);
        depsToGraphViz(passDepsIn);
        exit(0);
    }
}

void removalsFromPassesDependencies(map<passes, vector<passes>>& passDepsIn, const int curr_regime)
{
    passDeps = &passDepsIn;
    
    list({ INSERT_PARALLEL_DIRS_NODIST, LOOP_ANALYZER_NODIST }) -= list({ FIND_FUNC_TO_INCLUDE, CHECK_FUNC_TO_INCLUDE });

    Pass(passes(curr_regime)).applyRemovals();
}
#undef list
