#pragma once
#include <string>
#include <map>
#include <set>
#include <vector>
#include <algorithm>

#include "CFGraph.h"
#include "CommonBlock.h"

namespace SAPFOR
{
    struct CFG_Settings;

    enum class CFG_OP { NONE, ASSIGN, POINTER_ASS, LOAD, STORE, REC_REF_LOAD, REC_REF_STORE, REF, PARAM, IO_PARAM, RANGE, ENTRY, REC_REF,
                        ADD, MULT, DIV, SUBT, UN_ADD, UN_MINUS, POW, CONCAT, CAST,  
                        JUMP, JUMP_IF,
                        GE, LE, GT, LT, EQ, NEQV, EQV, EMPTY, OR, AND, NOT,
                        F_CALL, EXIT,
                        DVM_DIR, SPF_DIR };

    enum class CFG_ARG_TYPE { NONE, REG, VAR, ARRAY, CONST, FUNC, LAB, INSTR, CONST_STR, RECORD, CONSTR_REF };
    enum class CFG_MEM_TYPE { NONE_, COMMON_, SAVE_, LOCAL_, MODULE_, FUNC_RES_, FUNC_PARAM_, FILED_ };

    static std::vector<std::string> CFG_OP_S = { "--", " = ", " => ", "LOAD ", "STORE ", "LOAD_REF ", "STORE_REF ", "REF ", "PARAM ", "IO_PARAM ", "RANGE ", "ENTRY ", "->",
                                                 " + ", " * ", " / ", " - ", " + ", "-", " ** ", " // ", "CAST ",
                                                 "GOTO ", "IF_FALSE ", 
                                                 " >= ", " <= ", " > " , " < ", " == ", " != ", " eqv ", "CONTINUE", " || ", " && ", " ! ",
                                                 "F_CALL ", "EXIT ", 
                                                 " ", " " };

    class Argument
    {
        static int lastNumArg;
    private:
        int number;

        CFG_ARG_TYPE type;
        CFG_MEM_TYPE mType;
        std::string value;
    public:
        Argument() : number(lastNumArg++), type(CFG_ARG_TYPE::NONE), value(""), mType(CFG_MEM_TYPE::NONE_) { }
        Argument(CFG_ARG_TYPE type, CFG_MEM_TYPE mType) : number(lastNumArg++), type(type), mType(mType), value("") { }
        Argument(CFG_ARG_TYPE type, CFG_MEM_TYPE mType, const std::string& value) : number(lastNumArg++), type(type), mType(mType), value(value) { }
        Argument(CFG_ARG_TYPE type, const std::string& value) : number(lastNumArg++), type(type), mType(CFG_MEM_TYPE::NONE_), value(value) 
        {
            if (type != CFG_ARG_TYPE::INSTR && type == CFG_ARG_TYPE::LAB &&
                type != CFG_ARG_TYPE::NONE && type != CFG_ARG_TYPE::FUNC)
            {
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
            }
        }

        void setType(CFG_ARG_TYPE newType) { type = newType; }
        CFG_ARG_TYPE getType() const { return type; }

        void setMemType(CFG_MEM_TYPE newType) { mType = newType; }
        CFG_MEM_TYPE getMemType() const { return mType; }

        bool isMemGlobal() const
        {
            if (mType == CFG_MEM_TYPE::COMMON_ ||
                mType == CFG_MEM_TYPE::MODULE_ ||
                mType == CFG_MEM_TYPE::SAVE_)
                return true;

            return false;
        }

        bool isParameter() const
        {
            if (mType == CFG_MEM_TYPE::FUNC_RES_ ||
                mType == CFG_MEM_TYPE::FUNC_PARAM_)
                return true;

            return false;
        }

        void setValue(const std::string& newValue) { value = newValue; }
        const std::string getValue() const 
        {
            if (type == CFG_ARG_TYPE::CONST_STR || type == CFG_ARG_TYPE::CONSTR_REF)
                return "'" + value + "'";
            else
                return value; 
        }

        int getNumber() const { return number; }

        std::string getMemTypeStr() const
        {
            if (mType == CFG_MEM_TYPE::NONE_)
                return "none";
            else if (mType == CFG_MEM_TYPE::COMMON_)
                return "common";
            else if (mType == CFG_MEM_TYPE::LOCAL_)
                return "local";
            else if (mType == CFG_MEM_TYPE::MODULE_)
                return "module";
            else if (mType == CFG_MEM_TYPE::FUNC_RES_)
                return "func_res";
            else if (mType == CFG_MEM_TYPE::FUNC_PARAM_)
                return "func_par";
            else if (mType == CFG_MEM_TYPE::SAVE_)
                return "save";
            else if (mType == CFG_MEM_TYPE::FILED_)
                return "filed";
            else
                return "error";
        }

        ~Argument() { }
    };
    
    class Instruction
    {
        static int lastNumInstr;
    private:
        int number;

        CFG_OP operation;
        Argument* arg1;
        Argument* arg2;
        Argument* result;

        SgStatement* st = NULL;
        SgExpression* ex = NULL;

        std::string getArgValue(Argument* arg) const 
        {
            if (arg == NULL)
                return "";
            return arg->getValue();
        }
    public:

        Instruction() : number(lastNumInstr++), operation(CFG_OP::NONE), arg1(NULL), arg2(NULL), result(NULL) { }
        Instruction(CFG_OP op, Argument* arg1 = NULL, Argument* arg2 = NULL, Argument* res = NULL, SgStatement* st = NULL, SgExpression* ex = NULL) :
                    number(lastNumInstr++), operation(op), arg1(arg1), arg2(arg2), result(res), st(st), ex(ex) { }

        Instruction(CFG_OP op, SgStatement* st) : number(lastNumInstr++), operation(op), arg1(NULL), arg2(NULL), result(NULL), st(st) { }

        Instruction(CFG_OP op, int num, Argument* arg1 = NULL, Argument* arg2 = NULL, Argument* res = NULL, SgStatement* st = NULL, SgExpression* ex = NULL) :
                    number(num), operation(op), arg1(arg1), arg2(arg2), result(res), st(st), ex(ex) { }

        void setOperation(CFG_OP op) { operation = op; }
        CFG_OP getOperation() const { return operation; }

        Argument* getArg1() const { return arg1; }
        Argument* getArg2() const { return arg2; }
        Argument* getResult() const { return result; }

        void setArg1(Argument* arg) { arg1 = arg; }
        void setArg2(Argument* arg) { arg2 = arg; }
        void setResult(Argument* arg) { result = arg; }

        int getNumber() const { return number; }
        void shiftNumber(int add) { number += add; }

        SgStatement* getOperator() const { return st; }
        void setOperator(SgStatement* st_) { st = st_; }

        SgExpression* getExpression() const { return ex; }

        int getLine() const { return st ? st->lineNumber() : -1; }

        bool isAccess() const 
        {
            std::set<CFG_OP> accessOps = { CFG_OP::LOAD , CFG_OP::STORE, CFG_OP::REC_REF_LOAD, CFG_OP::REC_REF_STORE };
            return accessOps.find(operation) != accessOps.end();
        }

        bool isArith() const
        {
            std::set<CFG_OP> arithOps = { CFG_OP::ADD, CFG_OP::MULT, CFG_OP::DIV, CFG_OP::SUBT, CFG_OP::UN_ADD, CFG_OP::UN_MINUS, CFG_OP::POW, CFG_OP::CONCAT };
                                         //CFG_OP::CAST, CFG_OP::GE, CFG_OP::LE, CFG_OP::GT, CFG_OP::LT, CFG_OP::EQ, CFG_OP::NEQV, CFG_OP::EQV, CFG_OP::OR, CFG_OP::AND, CFG_OP::NOT };

            return (arithOps.find(operation) != arithOps.end()) || isIntirinsicCall();
        }

        bool isIntirinsicCall() const
        {
            if (operation == CFG_OP::F_CALL)
            {
                if (ex)
                {
                    if (isIntrinsicFunctionName(ex->symbol()->identifier()))
                        return true;
                }
            }
            return false;
        }

        static int getNextInstrNum() { return lastNumInstr; }
        static void shiftNextInstrNum(int byNum) { lastNumInstr += byNum; }

        std::string dump()
        {
            std::string res = "";

            std::string resultVal = getArgValue(result);
            std::string arg1Val = getArgValue(arg1);
            std::string arg2Val = getArgValue(arg2);

            switch (operation)
            {
            case CFG_OP::ADD:
            case CFG_OP::MULT:
            case CFG_OP::DIV:
            case CFG_OP::SUBT:
            case CFG_OP::GE:
            case CFG_OP::LE:
            case CFG_OP::GT:
            case CFG_OP::LT:
            case CFG_OP::EQ:
            case CFG_OP::NEQV:
            case CFG_OP::EQV:
            case CFG_OP::OR:
            case CFG_OP::AND:
            case CFG_OP::POW:
            case CFG_OP::CONCAT:
                res = resultVal + " = " + arg1Val + CFG_OP_S[(int)operation] + arg2Val;
                break;
            case CFG_OP::NOT:
            case CFG_OP::UN_MINUS:
            case CFG_OP::UN_ADD:
                res = resultVal + " = " + CFG_OP_S[(int)operation] + arg1Val;
                break;
            case CFG_OP::POINTER_ASS:
                res = arg1Val + CFG_OP_S[(int)operation] + resultVal;
                break;
            case CFG_OP::ASSIGN:
                if (arg2)
                    res = resultVal + CFG_OP_S[(int)operation] + arg1Val + "  [" + arg2Val + "]";
                else
                    res = resultVal + CFG_OP_S[(int)operation] + arg1Val;
                break;
            case CFG_OP::JUMP:
            case CFG_OP::ENTRY:
                res = CFG_OP_S[(int)operation] + arg1Val;
                break;
            case CFG_OP::JUMP_IF:
                res = CFG_OP_S[(int)operation] + arg1Val + " then goto " + arg2Val;
                break;
            case CFG_OP::EMPTY:
                res = CFG_OP_S[(int)operation];
                break;
            case CFG_OP::LOAD:
            case CFG_OP::REC_REF_LOAD:
                res = CFG_OP_S[(int)operation] + resultVal + " " + arg1Val + (arg2 ? (" " + arg2Val) : "");
                break;
            case CFG_OP::REC_REF_STORE:
            case CFG_OP::STORE:
                res = CFG_OP_S[(int)operation] + arg1Val + (arg2 ? (" " + arg2Val) : "") + " " + resultVal;
                break;
            case CFG_OP::REC_REF:
                res = "LOAD " + resultVal + " " + arg1Val + CFG_OP_S[(int)operation] + arg2Val;
                break;
            case CFG_OP::REF:
            case CFG_OP::PARAM:
            case CFG_OP::IO_PARAM:
                res = CFG_OP_S[(int)operation] + arg1Val;
                break;
            case CFG_OP::CAST:
                res = resultVal + " = " + CFG_OP_S[(int)operation] + arg1Val;
                break;
            case CFG_OP::F_CALL:
                if (result)
                    res = resultVal + " = " + CFG_OP_S[(int)operation] + arg1Val + " " + arg2Val;
                else
                    res = CFG_OP_S[(int)operation] + arg1Val + " " + arg2Val;
                break;
            case CFG_OP::RANGE:
                res = CFG_OP_S[(int)operation] + "[ ";
                if (arg1)
                    res += arg1Val;
                res += " : ";
                if (arg2)
                    res += arg2Val;
                res += " : ";
                if (result)
                    res += resultVal;
                res += " ]";
                break;
            case CFG_OP::DVM_DIR:
            case CFG_OP::SPF_DIR:
                res = (st != NULL) ? std::string(st->unparse()) : "NULL";
                break;
            default:
                res = CFG_OP_S[(int)operation];
                break;
            }

            return res;
        }
    };

    class IR_Block
    {
    private:
        Instruction* current;
        Instruction* jumpTo;

        BasicBlock* bblock;
        bool header;
    public:
        IR_Block(Instruction* instr) : current(instr), jumpTo(NULL), bblock(NULL), header(false) { }
        IR_Block(const IR_Block& copyFrom)
        {
            current = new Instruction(*copyFrom.getInstruction());
            bblock = NULL;
            //need to replace with actual IR
            jumpTo = copyFrom.getJump();
            header = false;
        }

        Instruction* getInstruction() const { return current; }
        Instruction* getJump() const { return jumpTo; }
        BasicBlock* getBasicBlock() const { return bblock; }
        int getNumber() const { return current->getNumber(); }

        void setJump(Instruction* instr) { jumpTo = instr; }
        void setBasicBlock(BasicBlock* newBlock) { bblock = newBlock; }

        void setHeader() { header = true; }
        bool isHeader() const { return header; }

        int getLine() const { return current->getLine(); }

        ~IR_Block() { delete current; }
    };

    class BasicBlock;
}

std::string createName(const std::vector<std::pair<const Variable*, CommonBlock*>>& commonVars, const FuncInfo* func,
                       SgSymbol* s, SgStatement* scope, SAPFOR::CFG_MEM_TYPE& mType);
std::string getNameByArg(SAPFOR::Argument* arg);
SgSymbol* getSybolByArg(SAPFOR::Argument* arg);
SAPFOR::Argument* createArg(const std::string& fullName, const std::string& name, SAPFOR::CFG_MEM_TYPE mType);

std::vector<SAPFOR::IR_Block*> buildIR(SgStatement* function, const FuncInfo* func, const std::vector<std::pair<const Variable*, CommonBlock*>>& commonVars, const SAPFOR::CFG_Settings settings);
SAPFOR::Instruction* getInstructionByNumber(const std::vector<SAPFOR::IR_Block*>& blocks, int num);
std::pair<SAPFOR::Instruction*, SAPFOR::BasicBlock*> getInstructionAndBlockByNumber(const std::map<FuncInfo*, std::vector<SAPFOR::BasicBlock*>>& CFGraph, int num);
std::pair<SAPFOR::Instruction*, SAPFOR::BasicBlock*> getInstructionAndBlockByStatement(const std::map<FuncInfo*, std::vector<SAPFOR::BasicBlock*>>& CFGraph, SgStatement* stmt);
int getParamIndex(SAPFOR::Argument* func_param, int max_index);