#include "../Utils/leak_detector.h"

#include <string>
#include <map>

#include "Utils/SgUtils.h"
#include "set_implicit_none.h"

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

static const char commonIntLetters[6] = { 'i', 'j', 'k', 'm', 'n', 'l' };

static void InitTypes(map<char, SgType*>& types)
{
    for (char letter = 'a'; letter <= 'z'; letter++)
        types[letter] = 0;
}

static void FillCommonTypes(map<char, SgType*>& types)
{
    for (char letter : commonIntLetters)
        if (types[letter] == 0)
            types[letter] = new SgType(T_INT);

    for (auto letter : types)
        if (letter.second == NULL)
            types[letter.first] = new SgType(T_FLOAT);
}

static void FindAllVars(SgExpression* expr, set<SgSymbol*>& allVars)
{
    if (expr == NULL)
        return;

    if (expr->variant() == VAR_REF || expr->variant() == ARRAY_REF)
        allVars.insert(expr->symbol());

    FindAllVars(expr->lhs(), allVars);
    FindAllVars(expr->rhs(), allVars);
}

static char getValue(SgExpression* ex)
{
    char charVal = 0;
    if (ex && ex->variant() == CHAR_VAL)
        charVal = isSgValueExp(ex)->charValue();
    return charVal;
}

static void AddLettersToMap(SgExpression* expr, SgType* type, map<char, SgType*>& types)
{
    while (expr)
    {
        if (expr->variant() != EXPR_LIST)
            printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

        SgExpression* val = expr->lhs();
        if (val->variant() == DDOT)
        {
            char leftVal = getValue(val->lhs());
            char rightVal = getValue(val->rhs());

            if (leftVal == 0 || rightVal == 0)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            for (char letter = leftVal; letter <= rightVal; letter++)
                types[letter] = type;
        }
        else
        {
            char charVal = getValue(val);
            if (charVal == 0)
                printInternalError(convertFileName(__FILE__).c_str(), __LINE__);

            types[charVal] = type;
        }

        expr = expr->rhs();
    }
}

static map<char, SgType*> FunctionImplicitCheck(SgStatement* function, const map<SgStatement*, map<char, SgType*>>& typesByFunctions)
{
    set<SgSymbol*> allVars;
    map<char, SgType*> types;
    vector<SgSymbol*> varsWithoutDecl;
     
    InitTypes(types);
    FillCommonTypes(types);

    auto cp = function->controlParent();
    if (isSgProgHedrStmt(cp))
        if (typesByFunctions.find(cp) != typesByFunctions.end())
            for (auto& parentType : typesByFunctions.at(cp))
                types[parentType.first] = parentType.second;

    auto hasImplicitNone = false;
    auto endOfFunc = function->lastNodeOfStmt();
    for (auto st = function; st != endOfFunc; st = st->lexNext())
    {
        if (st->variant() == IMPL_DECL)
        {
            SgImplicitStmt* implicitStatement = isSgImplicitStmt(st);
            if (implicitStatement != NULL)
            {
                const int numberOfTypes = implicitStatement->numberOfImplicitTypes();

                if (numberOfTypes > 0)
                {
                    for (int j = 0; j < numberOfTypes; ++j)
                    {
                        SgType* type = implicitStatement->implicitType(j);
                        SgExpression* lettersExpression = implicitStatement->implicitRangeList(j);

                        AddLettersToMap(lettersExpression, type, types);
                    }
                }
                else
                    hasImplicitNone = true;
            }
        }
        else if (st->variant() == CONTAINS_STMT || isSgExecutableStatement(st) != NULL)
            break;
    }

    for (auto st = function; st != endOfFunc && st->variant() != CONTAINS_STMT; st = st->lexNext())
        for (int i = 0; i < 3; ++i)
            FindAllVars(st->expr(i), allVars);

    for (auto& var : allVars)
    {
        if (string(var->identifier()) == function->symbol()->identifier())
            continue;

        vector<SgStatement*> _;
        SgStatement* declaredInStatement = declaratedInStmt(var, &_, false);
        if (declaredInStatement == NULL)
        {
             const char c = var->identifier()[0];

            if (types.find(c) != types.end())
                var->setType(types[c]);

            varsWithoutDecl.push_back(var);
        }
    }

    makeDeclaration(varsWithoutDecl, function, NULL);

    if (!hasImplicitNone)
    {
        for (auto st = function->lexNext(); 
             st != endOfFunc && st->variant() != CONTAINS_STMT && isSgExecutableStatement(st) == NULL; 
             )
        {
            if (st->variant() == IMPL_DECL)
            {
                auto tmpStatement = st;
                st = st->lexNext();
                tmpStatement->deleteStmt();
            }
            else
                st = st->lexNext();
        }

        auto implNone = new SgStatement(IMPL_DECL);
        implNone->setlineNumber(function->lineNumber());
        implNone->setFileName(function->fileName());

        function->insertStmtAfter(*implNone, *function);
    }

    allVars.clear();
    varsWithoutDecl.clear();

    return types;
}

void implicitCheck(SgFile* file)
{
    map<SgStatement*, map<char, SgType*>> typesByFunctions;

    for (int func = 0; func < file->numberOfFunctions(); ++func)
    {
        SgStatement* function = file->functions(func);
        typesByFunctions[function] = FunctionImplicitCheck(function, typesByFunctions);
    }

    typesByFunctions.clear();
}
