#include "leak_detector.h"

#include <cstdio>
#include <map>
#include <string>
#include <algorithm>

#include "dvm.h"
#include "verifications.h"

#include "../ParallelizationRegions/ParRegions.h"
#include "utils.h"
#include "SgUtils.h"
#include "errors.h"

using std::vector;
using std::map;
using std::pair;
using std::string;
using std::wstring;
using std::make_pair;
using std::set;

bool EndDoLoopChecker(SgFile *file, vector<Messages> &currMessages)
{
    int funcNum = file->numberOfFunctions();
    bool checkOK = true;

    for (int i = 0; i < funcNum; ++i)
    {
        SgStatement *st = file->functions(i);
        SgStatement *lastNode = st->lastNodeOfStmt();

        while (st != lastNode)
        {            
            if (st == NULL)
            {
                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (st->variant() == CONTAINS_STMT)
                break;

            if (st->variant() == FOR_NODE)
            {
                SgForStmt *currSt = (SgForStmt*)st;
                if (currSt->isEnddoLoop() == 0)
                {
                    __spf_print(1, "  ERROR: Loop on line %d does not have END DO\n", st->lineNumber());
                    currMessages.push_back(Messages(ERROR, st->lineNumber(), R51, L"This loop does not have END DO format", 1018));
                    checkOK = false;
                }
            }
            
            if (st->variant() == FORALL_NODE || st->variant() == FORALL_STAT ||
                st->variant() == WHERE_BLOCK_STMT || st->variant() == WHERE_NODE)
            {
                __spf_print(1, "  ERROR: Loop on line %d does not have END DO\n", st->lineNumber());
                currMessages.push_back(Messages(ERROR, st->lineNumber(), R50, L"This loop does not have END DO format", 1018));
                checkOK = false;
            }
            
            if (st->variant() == WHILE_NODE)
            {
                auto last = st->lastNodeOfStmt();
                auto str = string(last->unparse());
                convertToLower(str);
                if (last->variant() != CONTROL_END || str.find("enddo") == string::npos)
                {
                    __spf_print(1, "  ERROR: Loop on line %d does not have END DO\n", st->lineNumber());
                    currMessages.push_back(Messages(ERROR, st->lineNumber(), R50, L"This loop does not have END DO format", 1018));
                    checkOK = false;
                }
            }

            st = st->lexNext();
        }
    }
    return checkOK;
}
 
bool DvmDirectiveChecker(SgFile *file, map<string, vector<int>> &errors, const int keepDvmDirectives, const int ignoreDvmChecker)
{
    if (keepDvmDirectives != 0)
        return true;

    int funcNum = file->numberOfFunctions();
    bool checkOK = true;
    __spf_print(1, "  ignoreDvmChecker = %d\n", ignoreDvmChecker);

    for (int i = 0; i < funcNum; ++i)
    {
        SgStatement *st = file->functions(i);
        SgStatement *lastNode = st->lastNodeOfStmt();

        for ( ; st != lastNode; st = st->lexNext())
        {            
            if (st->variant() == CONTAINS_STMT)
                break;

            if (isDVM_stat(st) && (st->variant() != DVM_INTERVAL_DIR && st->variant() != DVM_ENDINTERVAL_DIR))
            {
                errors[st->fileName()].push_back(st->lineNumber());
                checkOK = false;
            }
        }
    }

    return checkOK;
}

bool EquivalenceChecker(SgFile *file, const string &fileName, const vector<ParallelRegion*> &regions, map<string, vector<Messages>> &currMessages)
{
    int funcNum = file->numberOfFunctions();
    bool checkOK = true;

    for (int i = 0; i < funcNum; ++i)
    {
        SgStatement *st = file->functions(i);
        SgStatement *lastNode = st->lastNodeOfStmt();
        int lastLine = 1;

        while (st != lastNode)
        {            
            lastLine = st->lineNumber();
            if (st == NULL)
            {
                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (st->variant() == EQUI_STAT)
            {
                auto eqList = isSgNestedVarListDeclStmt(st);
                if (eqList == NULL)
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                bool needToReport = false;
                bool checkTmp = true;
                //TODO: need to check variables in region
                if (getRegionByLine(regions, st->fileName(), lastLine))
                {
                    checkTmp = false;
                    needToReport = true;
                }
                else
                    needToReport = true;

                bool onlyVars = true;
                for (int z = 0; z < eqList->numberOfLists(); ++z)
                {
                    auto list = eqList->list(z);
                    while (list)
                    {
                        SgSymbol* symb = list->lhs()->symbol();
                        checkNull(symb, convertFileName(__FILE__).c_str(), __LINE__);
                        
                        if (isArrayRef(list->lhs()) ||
                            symb->type() && symb->type()->variant() == T_ARRAY)
                        {
                            SgStatement* decl = declaratedInStmt(symb);
                            DIST::Array* array = getArrayFromDeclarated(decl, symb->identifier());

                            if (array && !array->IsNotDistribute())
                                onlyVars = false;
                        }
                        list = list->rhs();
                    }
                }

                if (onlyVars)
                {
                    needToReport = false;
                    checkTmp = true;                    
                }

                checkOK &= checkTmp;
                if (needToReport)
                {
                    __spf_print(1, "The equivalence operator is not supported yet at line %d of file %s\n", st->lineNumber(), st->fileName());
                    currMessages[st->fileName()].push_back(Messages(checkOK ? WARR : ERROR, st->lineNumber(), R70, L"An equivalence operator is not supported yet", 1038));
                }
            }

            if (st->variant() == PAUSE_NODE)
            {
                checkOK &= false;
                __spf_print(1, "The PAUSE operator is not supported yet at line %d of file %s\n", st->lineNumber(), st->fileName());
                currMessages[st->fileName()].push_back(Messages(ERROR, st->lineNumber(), R69, L"An PAUSE operator is deprecated to parallel", 1038));
            }
            st = st->lexNext();
        }
    }
    return checkOK;
}

bool CommonBlockChecker(SgFile *file, const string &fileName, const map<string, CommonBlock*> &commonBlocks, map<string, vector<Messages>> &messages)
{
    bool checkOK = true;

    for (auto& block : commonBlocks)
    {
        auto vars = block.second->getVariables();

        for (int i = 0; i < vars.size(); i++)
        {
            int pos = vars[i]->getPosition();
            varType type = vars[i]->getType();

            //only this file
            bool needToSkip = true;
            const CommonVariableUse *currUse = NULL;
            for (auto &elem : vars[i]->getAllUse())
            {
                if (elem.getFile() == file)
                {
                    needToSkip = false;
                    currUse = &elem;
                }
            }

            if (needToSkip)
                continue;

            for (int j = i + 1; j < vars.size(); j++)
            {
                bool needToReport = false;
                auto typeMessage = NOTE;

                if ((vars[j]->getPosition() == pos) &&
                    ((vars[j]->getType() == ARRAY && type != ARRAY) || (vars[j]->getType() != ARRAY && type == ARRAY)))
                {
                    DIST::Array *array = NULL;
                    if (vars[j]->getType() == ARRAY)
                        array = getArrayFromDeclarated(vars[j]->getDeclarated(), vars[j]->getName());
                    else
                        array = getArrayFromDeclarated(vars[i]->getDeclarated(), vars[i]->getName());

                    if (array == NULL)
                        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

                    if (array->IsNotDistribute())
                        typeMessage = WARR;
                    else
                    {
                        checkOK = false;
                        typeMessage = ERROR;
                    }
                    needToReport = true;
                }
                else if (vars[j]->getPosition() == pos && vars[j]->getType() != type)
                {
                    typeMessage = WARR;
                    needToReport = true;
                }

                if (needToReport)
                {
                    wstring messageE, messageR;
                    __spf_printToLongBuf(messageE, L"Variables '%s' and '%s' in one storage association (common block '%s') have different types (files - %s:%d and %s:%d)",
                        to_wstring(vars[i]->getName()).c_str(), to_wstring(vars[j]->getName()).c_str(), to_wstring(block.first).c_str(),
                        to_wstring(vars[i]->getDeclarated()->fileName()).c_str(), vars[i]->getDeclarated()->lineNumber(),
                        to_wstring(vars[j]->getDeclarated()->fileName()).c_str(), vars[j]->getDeclarated()->lineNumber());

                    __spf_printToLongBuf(messageR, R71,
                        to_wstring(vars[i]->getName()).c_str(), to_wstring(vars[j]->getName()).c_str(), to_wstring(block.first).c_str(),
                        to_wstring(vars[i]->getDeclarated()->fileName()).c_str(), vars[i]->getDeclarated()->lineNumber(),
                        to_wstring(vars[j]->getDeclarated()->fileName()).c_str(), vars[j]->getDeclarated()->lineNumber());

                    messages[vars[i]->getDeclarated()->fileName()].push_back(Messages(typeMessage, vars[i]->getDeclarated()->lineNumber(), messageR, messageE, 1039));
                    messages[vars[j]->getDeclarated()->fileName()].push_back(Messages(typeMessage, vars[j]->getDeclarated()->lineNumber(), messageR, messageE, 1039));
                }
            }
        }
    }
    
    return checkOK;
}

bool FunctionsChecker(SgFile *file, map<string, pair<string, int>> &funcNames, map<string, vector<Messages>> &currMessages)
{
    int funcNum = file->numberOfFunctions();
    bool checkOK = true;

    for (int i = 0; i < funcNum; ++i)
    {
        SgStatement *st = file->functions(i);
        SgStatement *lastNode = st->lastNodeOfStmt();
        
        if (st->controlParent()->variant() == GLOBAL)
        {
            string funcName = st->symbol()->identifier();
            auto it = funcNames.find(funcName);
            if (it == funcNames.end())
                funcNames[funcName] = make_pair(file->filename(), st->lineNumber());
            else
            {
                __spf_print(1, "the same function name in different places was found: func %s, places %s:%d and %s:%d\n", 
                            it->first.c_str(), it->second.first.c_str(), it->second.second, file->filename(), st->lineNumber());

                wstring messageE, messageR;
                __spf_printToLongBuf(messageE, L"Function '%s' was declared in more than one place: '%s':%d and '%s':%d",
                                                to_wstring(funcName).c_str(), to_wstring(it->second.first).c_str(),
                                                it->second.second, to_wstring(file->filename()).c_str(), st->lineNumber());

                __spf_printToLongBuf(messageR, R92,
                                                to_wstring(funcName).c_str(), to_wstring(it->second.first).c_str(), 
                                                it->second.second, to_wstring(file->filename()).c_str(), st->lineNumber());

                currMessages[st->fileName()].push_back(Messages(ERROR, st->lineNumber(), messageR, messageE, 1048));
                currMessages[it->second.first].push_back(Messages(ERROR, it->second.second, messageR, messageE, 1048));

                checkOK = false;
            }

            if (isIntrinsicFunctionName(funcName.c_str()))
            {
                __spf_print(1, "the same function name in different places was found: func %s, places %s:%d and %s:%d\n",
                            funcName.c_str(), file->filename(), st->lineNumber(), "intrinsic", 0);

                wstring messageE, messageR;
                __spf_printToLongBuf(messageE, L"Function '%s' was declared in more than one place: '%s':%d and '%s':%d",
                    to_wstring(funcName).c_str(), to_wstring(file->filename()).c_str(), st->lineNumber(), to_wstring("intrinsic").c_str(), 0);

                __spf_printToLongBuf(messageR, R92,
                    to_wstring(funcName).c_str(), to_wstring(file->filename()).c_str(), st->lineNumber(), to_wstring("intrinsic").c_str(), 0);

                currMessages[st->fileName()].push_back(Messages(ERROR, st->lineNumber(), messageR, messageE, 1048));

                checkOK = false;
            }
        }
    }
    return checkOK;
}

void fillFunctionInfo(SgFile* file, map<string, vector<Function>>& funcInfo)
{
    const int funcNum = file->numberOfFunctions();
    const string fileName = file->filename();
    for (int i = 0; i < funcNum; ++i)
    {
        SgStatement* st = file->functions(i);
        SgStatement* lastNode = st->lastNodeOfStmt();

        if (st->fileName() != fileName)
            continue;

        Function newFunc;
        newFunc.name = st->symbol()->identifier();
        newFunc.line = st->lineNumber();
        newFunc.file = file->filename();
        newFunc.point = st;
        if (isSgProgHedrStmt(st) == NULL)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        newFunc.parametersCount = ((SgProgHedrStmt*)st)->numberOfParameters();

        auto cp = st->controlParent();
        if (cp->variant() == GLOBAL)
            newFunc.type = 1;
        else if (cp->variant() == MODULE_STMT)
        {
            if (string(cp->fileName()) != file->filename())
                continue; // add module functions only once
            newFunc.type = 0;
            newFunc.parentName = cp->symbol()->identifier();
        }
        else
        {
            auto cpcp = cp->controlParent();
            if (cpcp->variant() == GLOBAL)
            {
                newFunc.type = 2;
                newFunc.parentName = cp->symbol()->identifier();
            }
            else
            {
                newFunc.type = 3;
                newFunc.parentName = cp->symbol()->identifier();
                newFunc.globalName = cpcp->symbol()->identifier();
            }
        }
        funcInfo[newFunc.name].push_back(newFunc);
    }
}

//TODO: for the same parameters count
//      add support of optilnal parameters
static SgSymbol* resolveName(SgFile* file, SgStatement* context, SgExpression* parameters, const vector<Function>& functions, const string& name)
{
    SgSymbol* corrected = NULL;

    int parCount = 0;
    while (parameters)
    {
        parCount++;
        parameters = parameters->rhs();
    }

    auto cp = getFuncStat(context, { MODULE_STMT } );
    auto cpcp = cp->controlParent();
    auto globalP = cpcp->controlParent();

    const string cpName = cp->symbol()->identifier();
    const string cpcpName = (cpcp->variant() == GLOBAL) ? "" : cpcp->symbol()->identifier();
    const string global = globalP ? ((globalP->variant() != GLOBAL) ? globalP->symbol()->identifier() : "") : "";

    //try to resolve by parameters count
    vector<Function> firstStep, candidates;

    for (auto& func : functions)
        if (func.parametersCount == parCount)
            firstStep.push_back(func);

    if (firstStep.size() != 1)
    {
        for (auto& func : firstStep)
        {
            if (func.type == 3)
            {
                if (func.parentName == cpName && func.globalName == cpcpName ||
                    func.parentName == cpcpName && func.globalName == global)
                {
                    candidates.push_back(func);
                }
            }
            else if (func.type == 0)
            {
                if (func.parentName == cpcpName)
                    candidates.push_back(func);
            }
            else if (func.type == 2)
            {
                if (func.parentName == cpName || func.parentName == cpcpName) // contains 
                    candidates.push_back(func);
            }
            else
            {
                //TODO:
                candidates.push_back(func);
            }
        }
    }
    else
        candidates = firstStep;

    if (candidates.size() == 0)
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

    //TODO: added candidate filtration by context

    if (candidates.size() == 1)
    {
        Function target = candidates[0];
        auto itF = target.resolved.find(file);
        
        if (itF != target.resolved.end())
            corrected = itF->second;
        else
        {
            if (target.type == 1 || target.type == 2) // global or internal
            {
                if (target.file == file->filename())
                {
                    corrected = target.point->symbol();
                    target.resolved[file] = corrected;
                }
                /*else //TODO: create needed symbol?
                    printInternalError(convertFileName(__FILE__).c_str(), __LINE__);*/
            }
            else if (target.type == 0) // module
            {
                if (target.file == file->filename())
                {
                    corrected = target.point->symbol();
                    target.resolved[file] = corrected;
                }
                else
                    ;//TODO: find module symbol clone in current file
            }
        }
    }
    else // TODO
    {
        __spf_print(1, "can not resolve of CALL '%s' function, candidates count %d, file %s and line %d\n", name.c_str(), functions.size(), current_file->filename(), context->lineNumber());
        for (auto& func : functions)
            __spf_print(1, "  --> from file file %s and line %d, parameter count - %d\n", func.file.c_str(), func.line, func.parametersCount);
        printInternalError(convertFileName(__FILE__).c_str(), __LINE__);
    }

    return corrected;
}

static void resolveFunctionCalls(SgFile* file, SgStatement* context, SgExpression* ex, const set<string>& toResolve, const map<string, vector<Function>>& funcInfo)
{
    if (ex)
    {
        if (ex->variant() == FUNC_CALL)
        {
            string name = ex->symbol()->identifier();
            if (toResolve.find(name) != toResolve.end())
            {
                auto newS = resolveName(file, context, ex->lhs(), funcInfo.at(name), name);
                if (newS && ex->symbol()->id() != newS->id())
                    ex->setSymbol(*newS);
            }
        }

        resolveFunctionCalls(file, context, ex->lhs(), toResolve, funcInfo);
        resolveFunctionCalls(file, context, ex->rhs(), toResolve, funcInfo);
    }
}

void resolveFunctionCalls(SgFile* file, const set<string>& toResolve, const map<string, vector<Function>>& funcInfo)
{
    if (toResolve.size() == 0)
        return;

    int funcNum = file->numberOfFunctions();
    for (int z = 0; z < funcNum; ++z)
    {
        SgStatement* st = file->functions(z);
        SgStatement* lastNode = st->lastNodeOfStmt();

        while (st != lastNode)
        {
            if (st->variant() == CONTAINS_STMT)
                break;

            if (st->variant() == PROC_STAT)
            {
                string name = st->symbol()->identifier();
                if (toResolve.find(name) != toResolve.end())
                {
                    auto newS = resolveName(file, st, st->expr(0), funcInfo.at(name), name);
                    if (newS && st->symbol()->id() != newS->id())
                        st->setSymbol(*newS);
                }
            }

            for (int z = 0; z < 3; ++z)
                resolveFunctionCalls(file, st, st->expr(z), toResolve, funcInfo);
            st = st->lexNext();
        }
    }
}

bool OperatorChecker(SgFile* file, map<string, vector<Messages>>& currMessages)
{
    bool checkOK = true;
    set<int> usedLines;

    SgStatement* st = file->firstStatement();
    string currF = file->filename();

    const set<int> cpOnSameLine = { ARITHIF_NODE, LOGIF_NODE, GOTO_NODE , IF_NODE, FORALL_STAT };
    while (st)
    {
        int line = st->lineNumber();
        if (line > 0 && st->variant() == PROG_HEDR && st->symbol()->identifier() == string("_MAIN"))
            ; // skip
        else if (line > 0 && st->fileName() == currF)
        {
            int var = st->controlParent()->variant();
            bool cpWasAdded = cpOnSameLine.find(var) != cpOnSameLine.end() && (usedLines.find(line) != usedLines.end());

            if (usedLines.find(line) != usedLines.end() && !cpWasAdded)
            {

                wstring messageE, messageR;
                __spf_printToLongBuf(messageE, L"More than one operator found on a line, try to run Code correction pass");
                __spf_printToLongBuf(messageR, R179);

                currMessages[st->fileName()].push_back(Messages(ERROR, line, messageR, messageE, 1027));
                checkOK = false;
            }
            usedLines.insert(line);
        }
        st = st->lexNext();
    }
    return checkOK;
}

bool checkAndMoveFormatOperators(SgFile* file, vector<Messages>& currMessages, bool withError)
{
    bool checkOK = true;

    const int funcNum = file->numberOfFunctions();
    for (int i = 0; i < funcNum; ++i)
    {
        SgStatement* st = file->functions(i);
        SgStatement* lastNode = st->lastNodeOfStmt();

        vector<SgStatement*> toMove;
        while (st != lastNode)
        {
            if (st == NULL)
            {
                __spf_print(1, "internal error in analysis, parallel directives will not be generated for this file!\n");
                break;
            }

            if (st->variant() == CONTAINS_STMT)
                break;

            if (isSgExecutableStatement(st) && !isDVM_stat(st) && !isSPF_stat(st))                
                break;

            if (st->variant() == FORMAT_STAT)
            {
                if (withError)
                {
                    toMove.push_back(st);
                    st = st->lexNext();
                }
                else
                {
                    SgStatement* needed = st;
                    st = st->lexNext();
                    toMove.push_back(needed->extractStmt());
                }                
            }
            else
                st = st->lexNext();
        }

        if (toMove.size() > 0)
        {
            if (!withError)
            {
                for (auto& format : toMove)
                    lastNode->insertStmtBefore(*format, *lastNode->controlParent());
            }
            else
            {
                for (auto& format : toMove)
                {
                    wstring messageE, messageR;
                    __spf_printToLongBuf(messageE, L"FORMAT operators cannot be placed in the declaration scope in the SAPFOR, try to run Code correction pass");
                    __spf_printToLongBuf(messageR, R183);

                    currMessages.push_back(Messages(ERROR, format->lineNumber(), messageR, messageE, 1060));
                }
                checkOK = false;
            }
        }
    }
    return checkOK;    
}
