#pragma once

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

#include "IR.h"
#include "IR_domTree.h"

namespace SAPFOR
{
    enum CFG_VAL : int { KILL_ALL = -1, UNINIT = -2 };

    class IR_Block;
    class Argument;
    class Instruction;

    class BasicBlock
    {
        static int lastNumBlock;
    private:
        int num;
        std::vector<IR_Block*> instructions;

        std::vector<BasicBlock*> next;
        std::vector<BasicBlock*> prev;
        BasicBlock* directDominator = NULL;
        //reaching definition
        std::map<SAPFOR::Argument*, std::set<int>> RD_in, RD_out;

        //live variables [arg -> blocks with usages]
        std::map<SAPFOR::Argument*, std::vector<SAPFOR::BasicBlock*>> live_in, live_out, live_inout;

        bool addLive(const std::map<SAPFOR::Argument*, std::vector<SAPFOR::BasicBlock*>>& to_add, bool in);
        std::map<SAPFOR::Argument*, std::vector<SAPFOR::BasicBlock*>> getLive(bool in) const;
        bool removeLive(SAPFOR::Argument* to_remove, bool in);

    public:
        BasicBlock() { num = lastNumBlock++; }
        BasicBlock(IR_Block* item);
        BasicBlock(const BasicBlock& copyFrom);

        void addInstruction(IR_Block* item);
        void addPrev(BasicBlock* prev_) { prev.push_back(prev_); }
        void addNext(BasicBlock* next_) { next.push_back(next_); }
        void setDom(BasicBlock* dom) { directDominator = dom; }

        int removePrev(BasicBlock* removed);
        int removeNext(BasicBlock* removed);
        
        void replacePrevNext(const std::map<BasicBlock*, BasicBlock*>& oldToNew)
        {
            for (int z = 0; z < next.size(); ++z)
            {
                auto it = oldToNew.find(next[z]);
                if (it == oldToNew.end())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                next[z] = it->second;
            }

            for (int z = 0; z < prev.size(); ++z)
            {
                auto it = oldToNew.find(prev[z]);
                if (it == oldToNew.end())
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
                prev[z] = it->second;
            }
        }

        int getNumber() const { return num; }
        const std::vector<IR_Block*>& getInstructions() const { return instructions; }
        const std::vector<BasicBlock*>& getNext() const { return next; }
        const std::vector<BasicBlock*>& getPrev() const { return prev; }
        BasicBlock* getDom() const 
        {
            return directDominator; 
        }

        /*
        *  FOR LIVE ANALYSIS
        */                
        bool addLiveIn(const std::map<SAPFOR::Argument*, std::vector<SAPFOR::BasicBlock*>>& to_add) { return addLive(to_add, true); };
        bool addLiveOut(const std::map<SAPFOR::Argument*, std::vector<SAPFOR::BasicBlock*>>& to_add) { return addLive(to_add, false); };

        bool removeLiveIn(SAPFOR::Argument* to_remove) { return removeLive(to_remove, true); };
        bool removeLiveOut(SAPFOR::Argument* to_remove) { return removeLive(to_remove, false); };
        
        std::map<SAPFOR::Argument*, std::vector<SAPFOR::BasicBlock*>> getLiveIn() const { return getLive(true); };
        std::map<SAPFOR::Argument*, std::vector<SAPFOR::BasicBlock*>> getLiveOut() const { return getLive(false); };
        void compressLives();

        /*
        *  FOR REACHING DEFINITIONS
        */
        void setRD_In(const std::map<Argument*, std::set<int>>& inSet) { RD_in = inSet; }
        void setRD_Out(const std::map<Argument*, std::set<int>>& outSet) { RD_out = outSet; }
        const std::map<Argument*, std::set<int>>& getRD_In() const { return RD_in; }
        const std::map<Argument*, std::set<int>>& getRD_Out() const { return RD_out; }
        std::map<Argument*, std::set<int>>& getModRD_In() { return RD_in; }
        std::map<Argument*, std::set<int>>& getModRD_Out() { return RD_out; }

        ~BasicBlock();
    };

    struct CFG_Settings
    {
        enum setting { CFG_atLeastOneIterInLoop = 1, 
                       CFG_withRD = 2, 
                       CFG_withRegisters = 3,  
                       CFG_withSPF = 4, 
                       CFG_withDVM = 5, 
                       CFG_withCallsInBlocks = 6, 
                       CFG_withCallFrom = 7, 
                       CFG_withDominators = 8, 
                       CFG_withUnreachable = 9 };

        bool atLeastOneIterInLoop = false;
        bool withRD =               false;
        bool withRegisters =        false;
        bool withSPF =              false;
        bool withDVM =              false;
        bool withCallsInBlocks =    false; // separate each F_CALL to own BasicBlock
        bool withCallFrom =         false;
        bool withDominators =       false;
        bool withUnreachable =      false;

        explicit CFG_Settings(const std::set<setting> &settings = { CFG_withRD, CFG_withCallFrom, CFG_withDominators })
        {
            for (auto& set : settings)
            {
                if (set == CFG_atLeastOneIterInLoop)
                    atLeastOneIterInLoop = true;
                else if (set == CFG_withRD)
                    withRD = true;
                else if (set == CFG_withRegisters)
                    withRegisters = true;
                else if (set == CFG_withSPF)
                    withSPF = true;
                else if (set == CFG_withDVM)
                    withDVM = true;
                else if (set == CFG_withCallsInBlocks)
                    withCallsInBlocks = true;
                else if (set == CFG_withCallFrom)
                    withCallFrom = true;
                else if (set == CFG_withDominators)
                    withDominators = true;
                else if (set == CFG_withUnreachable)
                    withUnreachable = true;
                else
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
        }
    };
}

std::map<FuncInfo*, std::vector<SAPFOR::BasicBlock*>> buildCFG(const std::map<std::string, CommonBlock*>& commonBlocks, const std::map<std::string, std::vector<FuncInfo*>>& allFuncInfo, const SAPFOR::CFG_Settings settings);
std::map<FuncInfo*, std::vector<SAPFOR::BasicBlock*>> buildCFGforCurrentFunc(SgStatement* stmt, SAPFOR::CFG_Settings settings, const std::map<std::string, CommonBlock*>& commonBlocks, const std::map<std::string, std::vector<FuncInfo*>>& allFuncInfo);

std::vector<std::pair<const Variable*, CommonBlock*>> getCommonsByFunction(SgFile* file, SgStatement* function, const std::map<std::string, CommonBlock*>& commonBlocks);
std::vector<SAPFOR::IR_Block*> getAllIR(const std::vector<SAPFOR::BasicBlock*>& blocks);
void dumpCFG(const std::map<FuncInfo*, std::vector<SAPFOR::BasicBlock*>>& blocks, bool withRD);
std::vector<std::set<FuncInfo*>> groupByCallDependencies(const std::map<FuncInfo*, std::set<FuncInfo*>>& callDeps, std::vector<std::set<FuncInfo*>>& scc);

template<typename T>
bool intersectAndAdd(std::set<T>& s1, const std::set<T>& s2);

void buildGenKillForCFG(const std::vector<SAPFOR::BasicBlock*>& CFG, 
                        const std::map<std::string, FuncInfo*>& funcByName, 
                        const std::map<FuncInfo*, std::map<SAPFOR::Argument*, std::set<int>>>& outForFunc,
                        std::map<SAPFOR::BasicBlock*, std::map<SAPFOR::Argument*, std::set<int>>>& gen, 
                        std::map<SAPFOR::BasicBlock*, std::map<SAPFOR::Argument*, std::set<int>>>& kill, 
                        std::map<SAPFOR::Instruction*, std::map<SAPFOR::Argument*, std::set<int>>>* genForIR, 
                        std::map<SAPFOR::Instruction*, std::map<SAPFOR::Argument*, std::set<int>>>* killForIR,
                        std::map<SAPFOR::BasicBlock*, std::set<SAPFOR::Argument*>>& notInitedGlobals, 
                        const SAPFOR::CFG_Settings settings);

static inline void deleteCFG(std::map<FuncInfo*, std::vector<SAPFOR::BasicBlock*>>& cfg)
{
    for (auto& byFunc : cfg)
    {
        for (auto& block : byFunc.second)
            delete block;
        byFunc.second.clear();
    }
    cfg.clear();
}

void removedUnreachableBlocks(std::vector<SAPFOR::BasicBlock*>& blocks);
