#include "leak_detector.h"

#include <cstdio>
#include <cstdlib>

#include <string>
#include <vector>

#include "errors.h"
#include "SgUtils.h"
#include "utils.h"

#include "../VerificationCode/verifications.h"

#include "ConvertFiles.h"
#include "calls.h"

using namespace std;

extern int v_print;
extern int warn_all;
extern int unparse_functions;
extern int opt_base;
extern int opt_loop_range;

extern graph_node* cur_node;
extern graph_node* node_list;

extern "C" int out_free_form;
extern "C" int out_upper_case;
extern "C" int out_line_unlimit;
extern "C" int out_line_length;
extern "C" PTR_SYMB last_file_symbol;

static int convertFile(int argc, char* argv[], const set<string>& filesInProj, const set<string>& moduleDeclsInFiles)
{
    FILE* fout = NULL;
    FILE* fout_cuf = NULL, * fout_C_cu = NULL, * fout_info = NULL; /*ACC*/
    const char* fout_name = NULL;
    char* fout_name_cuf;     /*ACC*/
    char* fout_name_C_cu;    /*ACC*/
    char* fout_name_info_C;  /*ACC*/

    char* source_name;
    int level, hpf, openmp, isz, dvm_type_size;
    int a_mode = 0;

    initialize();

    openmp = hpf = 0; dvm_type_size = 0;

    argv++;
    while ((argc > 1) && (*argv)[0] == '-')
    {
        if ((*argv)[1] == 'o' && ((*argv)[2] == '\0')) {
            if (filesInProj.size() == 1)
                fout_name = argv[1];
            argv++;
            argc--;
        }
        else if (!strcmp(argv[0], "-dc"))
            check_regim = 1;
        else if (!strcmp(argv[0], "-dbif1"))
            dbg_if_regim = 1;
        else if (!strcmp(argv[0], "-dbif2"))
            dbg_if_regim = 2;
        else if (!strcmp(argv[0], "-speedL0")) /* for dedugging ACROSS-scheme */
            options.setOn(SPEED_TEST_L0);          /*ACC*/
        else if (!strcmp(argv[0], "-speedL1")) /* for dedugging ACROSS-scheme */
            options.setOn(SPEED_TEST_L1);          /*ACC*/
        else if (!strcmp(argv[0], "-dmpi"))
            deb_mpi = 1;
        else if (!strcmp(argv[0], "-dnoind"))
            d_no_index = 1;
        else if (!strcmp(argv[0], "-dperf")) {
            debug_regim = 1;
            omp_debug = DPERF;
        }
        else if (!strcmp(argv[0], "-dvmLoopAnalysisEC"))   /*ACC*/
        {
            options.setOn(LOOP_ANALYSIS);
            options.setOn(OPT_EXP_COMP);
        }
        else if (!strcmp(argv[0], "-dvmIrregAnalysis"))   /*ACC*/
        {
            options.setOn(LOOP_ANALYSIS);
            options.setOn(OPT_EXP_COMP);
            options.setOn(GPU_IRR_ACC);
        }
        else if (!strcmp(argv[0], "-dvmLoopAnalysis"))   /*ACC*/
            options.setOn(LOOP_ANALYSIS);
        else if (!strcmp(argv[0], "-dvmPrivateAnalysis"))   /*ACC*/
            options.setOn(PRIVATE_ANALYSIS);
        else if ((*argv)[1] == 'd') {
            switch ((*argv)[2]) {
            case '0':  level = 0; break;
            case '1':  level = 1; omp_debug = D1; /*OMP*/ break;
            case '2':  level = 2; omp_debug = D2; /*OMP*/ break;
            case '3':  level = 3; omp_debug = D3; /*OMP*/ break;
            case '4':  level = 4; omp_debug = D4; /*OMP*/ break;
            case '5':  level = 5; omp_debug = D5; /*OMP*/ break;
                /* case '5':  level = -1; many_files=1; break;*/
            default:  level = -1;
            }
            if (level > 0)
                debug_regim = 1;
            if ((*argv)[3] == '\0')
                AddToFragmentList(0, 0, level, -1);
            else if ((*argv)[3] == ':')
                FragmentList(*argv + 4, level, -1);
        }
        else if ((*argv)[1] == 'e') {
            switch ((*argv)[2]) {
            case '0':  level = 0; break;
            case '1':  level = 1; break;
            case '2':  level = 2; break;
            case '3':  level = 3; break;
            case '4':  level = 4; break;
            case 'm':  omp_perf = 1; break;
            default:  level = -1;
            }
            if ((*argv)[3] == '\0')
                AddToFragmentList(0, 0, -1, level);
            else if ((*argv)[3] == ':')
                FragmentList(*argv + 4, -1, level);
        }
        else if (!strcmp(argv[0], "-spf"))
        {
            fprintf(stderr, "Illegal option -spf \n");
            return 1;
        }
        else if (!strcmp(argv[0], "-p")) {
            only_debug = 0; hpf = 0;
        }
        else if (!strcmp(argv[0], "-s")) {
            only_debug = 1; hpf = 0;
        }
        else if (!strcmp(argv[0], "-v"))
            v_print = 1;
        else if (!strcmp(argv[0], "-w"))
            warn_all = 1;
        else if (!strcmp(argv[0], "-bind0"))
            bind_ = 0;
        else if (!strcmp(argv[0], "-bind1"))
            bind_ = 1;
        else if (!strcmp(argv[0], "-t8"))
            dvm_type_size = 8;
        else if (!strcmp(argv[0], "-t4"))
            dvm_type_size = 4;
        else if (!strcmp(argv[0], "-r8"))
            default_real_size = 8;
        else if (!strcmp(argv[0], "-i8"))
            default_integer_size = 8;
        else if (!strcmp(argv[0], "-hpf") || !strcmp(argv[0], "-hpf1") || !strcmp(argv[0], "-hpf2"))
            hpf = 1;
        else if (!strcmp(argv[0], "-mp")) {
            OMP_program = 1; /*OMP*/
            openmp = 1;
        }
        //else if (!strcmp(argv[0],"-ta")) 
        //  ACC_program = 1;      
        else if (!strcmp(argv[0], "-noH"))
            ACC_program = 0;
        else if (!strcmp(argv[0], "-noCudaType"))  /*ACC*/
            undefined_Tcuda = 1;
        else if (!strcmp(argv[0], "-noCuda"))
            options.setOn(NO_CUDA);                /*ACC*/
        else if (!strcmp(argv[0], "-noPureFunc"))
            options.setOn(NO_PURE_FUNC);           /*ACC*/
        else if (!strcmp(argv[0], "-C_Cuda"))      /*ACC*/
            options.setOn(C_CUDA);
        else if (!strcmp(argv[0], "-FTN_Cuda") || !strcmp(argv[0], "-F_Cuda"))    /*ACC*/
            options.setOff(C_CUDA);
        else if (!strcmp(argv[0], "-no_blocks_info") || !strcmp(argv[0], "-noBI"))
            options.setOn(NO_BL_INFO);             /*ACC*/
        else if (!strcmp(argv[0], "-cacheIdx"))
            options.setOff(NO_BL_INFO);             /*ACC*/
        else if (!strcmp(argv[0], "-Ohost"))       /*ACC*/
            options.setOn(O_HOST);
        else if (!strcmp(argv[0], "-noOhost"))     /*ACC*/
            options.setOff(O_HOST);
        else if (!strcmp(argv[0], "-Opl2"))         /*ACC*/
        {
            parloop_by_handler = 2;
            options.setOn(O_HOST);
            options.setOn(O_PL2);
            // options.setOn(NO_CUDA);
        }
        else if (!strcmp(argv[0], "-Opl"))         /*ACC*/
        {
            parloop_by_handler = 1;
            options.setOn(O_PL);
        }
        else if (!strcmp(argv[0], "-oneThread"))   /*ACC*/
            options.setOn(ONE_THREAD);
        else if (!strcmp(argv[0], "-noTfm"))       /*ACC*/
            options.setOff(AUTO_TFM);
        else if (!strcmp(argv[0], "-autoTfm"))     /*ACC*/
            options.setOn(AUTO_TFM);
        else if (!strcmp(argv[0], "-gpuO0"))       /*ACC*/
            options.setOn(GPU_O0);
        else if (!strcmp(argv[0], "-gpuO1"))       /*ACC*/
            options.setOn(GPU_O1);
        else if (!strcmp(argv[0], "-rtc"))         /*ACC*/
            options.setOn(RTC);  //for NVRTC compilation and execution
        else if (!strcmp(argv[0], "-ffo"))
            out_free_form = 1;
        else if (!strcmp(argv[0], "-upcase"))
            out_upper_case = 1;
        else if (!strcmp(argv[0], "-noLimitLine"))
            out_line_unlimit = 1;
        else if (!strcmp(argv[0], "-uniForm"))
        {
            out_free_form = 1;
            out_line_length = 72;
        }
        else if (!strcmp(argv[0], "-noRemote"))
            options.setOn(NO_REMOTE);
        else if (!strcmp(argv[0], "-lgstd"))
        {
            (void)fprintf(stderr, "Illegal option -lgstd \n");
            return 1;
        }
        else if (!strcmp(argv[0], "-byFunUnparse"))
            unparse_functions = 1;
        else if (!strncmp(argv[0], "-bufio", 6)) {
            if ((*argv)[6] != '\0' && (isz = is_integer_value(*argv + 6)))
                IOBufSize = isz;
        }
        else if (!strncmp(argv[0], "-bufUnparser", 12)) {
            if ((*argv)[12] != '\0' && (isz = is_integer_value(*argv + 12)))
                UnparserBufSize = isz * 1024 * 1024;
        }
        else if (!strcmp(argv[0], "-ioRTS"))
            options.setOn(IO_RTS);
        else if (!strcmp(argv[0], "-read_all"))
            options.setOn(READ_ALL);
        else if (!strcmp(argv[0], "-Obase"))
            opt_base = 1;
        else if (!strcmp(argv[0], "-Oloop_range"))
            opt_loop_range = 1;
        else if ((*argv)[1] == 'H') {
            if ((*argv)[2] == 's' && (*argv)[3] == 'h' && (*argv)[4] == 'w') {
                if ((*argv)[5] != '\0' && (all_sh_width = is_integer_value(*argv + 5)))
                    ;
            }
            else if (!strcmp(*argv + 2, "nora"))
                no_rma = 1;
            else if (!strcmp(*argv + 2, "oneq"))
                one_inquiry = 1;
            else if (!strcmp(*argv + 2, "onlyl"))
                only_local = 1;
        }
        else if (!strncmp(argv[0], "-collapse", 9))
            if ((*argv)[9] != '\0' && (collapse_loop_count = is_integer_value(*argv + 9)));
        argc--;
        argv++;
    }

    // Check options combinations
    options.checkCombinations();

    // definition of DvmType size: len_DvmType 
    // len_DvmType==0, if DvmType-size == default_integer_size == 4 
    if (bind_ == 1)
        len_DvmType = 8;  //sizeof(long) == 8
    if (dvm_type_size)
        len_DvmType = dvm_type_size;
    if (len_DvmType == 0 && default_integer_size == 8)
        len_DvmType = 4;

    if (ACC_program && debug_regim && !only_debug)
    {
        fprintf(stderr, "Warning: -noH option is set to debug mode\n");
        ACC_program = 0;
    }
    if (parloop_by_handler > 0 && debug_regim)
    {
        fprintf(stderr, "Warning: -Opl/Opl2 option is ignored in debug mode\n");
        parloop_by_handler = 0;
        options.setOff(O_PL);
        options.setOff(O_PL2);
    }

    if (openmp && ACC_program)
    {
        fprintf(stderr, "Warning: -noH option is set to -mp mode\n");
        ACC_program = 0;
    }
    if (parloop_by_handler == 2 && !options.isOn(O_HOST))
    {
        fprintf(stderr, "Warning: -Ohost option is set to -Opl2 mode\n");
        options.setOn(O_HOST);
    }
    if (out_free_form == 1 && out_line_length == 72 && out_line_unlimit == 1)
    {
        fprintf(stderr, "Warning: -noLimitLine and -uniForm options are incompatible; -noLimitLine option is ignored\n");
        out_line_unlimit = 0;
    }

    if (v_print)
        (void)fprintf(stderr, "<<<<<  Translating  >>>>>\n");

    //------------------------------------------------------------------------------

    string proj_name = "fdvm__";
    static int id = 0;

    vector<char*> filesList;
    for (auto& file : filesInProj)
        filesList.push_back((char*)file.c_str());

    SgProject project((proj_name + to_string(id++)).c_str(), filesList.data(), filesList.size());

    shiftLines(&project, false);
    for (int z = 0; z < project.numberOfFiles(); ++z) {
        vector<SgStatement*> tmp;
        removeExecutableFromModuleDeclaration(&(project.file(z)), filesInProj, tmp);
    }

    SgFile* file = NULL;
    addNumberOfFileToAttribute(&project);

    //----------------------------
    ProjectStructure(project);
    Private_Vars_Project_Analyzer();
    //----------------------------

    initVariantNames();           //for project
    initIntrinsicFunctionNames(); //for project
    initSupportedVars(); // for project, acc_f2c.cpp
    initF2C_FunctionCalls(); // for project, acc_f2c.cpp
    for (int id = project.numberOfFiles() - 1; id >= 0; id--)
    {
        file = &(project.file(id));
        fin_name = new char[strlen(project.fileName(id)) + 2];
        sprintf(fin_name, "%s%s", project.fileName(id), " ");

        if (fout_name == NULL)
            fout_name = doOutFileName(file->filename());
        
        if (fout_name && source_name && !strcmp(source_name, fout_name))
        {
            fprintf(stderr, "Output file has the same name as source file\n");
            return 1;
        }

        printf("convert %d to %s\n", id, fout_name);

        fout_name_cuf = ChangeFtoCuf(fout_name);         /*ACC*/
        fout_name_C_cu = ChangeFto_C_Cu(fout_name);      /*ACC*/
        fout_name_info_C = ChangeFto_info_C(fout_name);  /*ACC*/

        //set the last symbol of file
        initLibNames();   //for every file
        InitDVM(file);    //for every file
        current_file = file;   // global variable (used in SgTypeComplex)
        max_lab = getLastLabelId();

        if (dbg_if_regim)
            GetLabel();

        TranslateFileDVM(file);

        if (err_cnt) {
            fprintf(stderr, "%d error(s)\n", err_cnt);
            return 1;
        }

        if (!fout_name) {
            file->unparsestdout();
            return 0;
        }

        //writing result of converting into file
        if ((fout = fopen(fout_name, "w")) == NULL) {
            fprintf(stderr, "Can't open file %s for write\n", fout_name);
            return 1;
        }

        if (GeneratedForCuda())  /*ACC*/
        {
            if ((fout_C_cu = fopen(fout_name_C_cu, "w")) == NULL) {
                fprintf(stderr, "Can't open file %s for write\n", fout_name_C_cu);
                return 1;
            }

            if (!options.isOn(C_CUDA)) {
                if ((fout_cuf = fopen(fout_name_cuf, "w")) == NULL) {
                    fprintf(stderr, "Can't open file %s for write\n", fout_name_cuf);
                    return 1;
                }
            }

            if ((fout_info = fopen(fout_name_info_C, "w")) == NULL) {
                fprintf(stderr, "Can't open file %s for write\n", fout_name_info_C);
                return 1;
            }
        }


        if (v_print)
            fprintf(stderr, "<<<<<  Unparsing   %s  >>>>>\n", fout_name);

        if (mod_gpu) /*ACC*/
            UnparseTo_CufAndCu_Files(file, fout_cuf, fout_C_cu, fout_info, fout_name);

        const string fileN = file->filename();
        set<SgStatement*> toRemove;

        for (SgStatement* st = file->firstStatement(); st; st = st->lexNext())
        {
            if (st->fileName() != fileN)
            {
                if (st->variant() == MODULE_STMT && moduleDeclsInFiles.find(st->fileName()) != moduleDeclsInFiles.end())
                {
                    toRemove.insert(st);
                    st = st->lastNodeOfStmt();
                }
            }
        }

        for (auto& toRem : toRemove)
            toRem->extractStmt();

        if (unparse_functions)
            UnparseFunctionsOfFile(file, fout);
        else if (UnparserBufSize)
            file->unparseS(fout, UnparserBufSize);
        else
            file->unparse(fout);

        if ((fclose(fout)) < 0) {
            fprintf(stderr, "Could not close %s\n", fout_name);
            return 1;
        }

        if (GeneratedForCuda()) {
            if ((fclose(fout_C_cu)) < 0) {
                fprintf(stderr, "Could not close %s\n", fout_name_C_cu);
                return 1;
            }

            if (!options.isOn(C_CUDA)) {
                if ((fclose(fout_cuf)) < 0) {
                    fprintf(stderr, "Could not close %s\n", fout_name_cuf);
                    return 1;
                }
            }

            if ((fclose(fout_info)) < 0) {
                fprintf(stderr, "Could not close %s\n", fout_name_info_C);
                return 1;
            }
        }
        fout_name = NULL;
    }

    if (v_print)
        fprintf(stderr, "\n*****  Done  *****\n");
    return 0;
}

static void preprocess(const vector<string>& files, map<string, set<string>>& moduleUsesByFile, map<string, string>& moduleDecls)
{
    vector<char*> filesList;
    for (int z = 0; z < files.size(); ++z)
        filesList.push_back((char*)files[z].c_str());

    SgProject tmpProj("preproc", filesList.data(), files.size());

    for (int z = 0; z < tmpProj.numberOfFiles(); ++z)
    {
        SgFile* file = &(tmpProj.file(z));
        fillModuleUse(file, moduleUsesByFile, moduleDecls);
    }

    InitializeTable();
}

void convertFiles(int argc, char* argv[], const char* proj_name)
{
    vector<char*> args_v;
    for (int z = 0; z < argc; ++z)
        args_v.push_back(argv[z]);
    
    const string fileText = readFileToStr(proj_name);
    vector<string> files;
    set<string> filesInSet;
    splitString(fileText, '\n', files);
    
    for (auto& file : files)
        filesInSet.insert(file);

    map<string, set<string>> moduleUsesByFile;
    map<string, string> moduleDecls;
    set<string> moduleDeclsInFiles;

    preprocess(files, moduleUsesByFile, moduleDecls);
    for (auto& elem : moduleDecls)
        moduleDeclsInFiles.insert(elem.second);

    int codes = 0;
    //for (auto& file : files)
    {
        codes += convertFile(args_v.size(), args_v.data(), filesInSet, moduleDeclsInFiles);

        cur_node = node_list = NULL;
        InitializeTable();
    }

    if (codes == 0)
        printf("Convertation was completed successfully\n");
    exit(0);
}