/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecmascript/compiler/compiler_log.h"

namespace panda::ecmascript::kungfu {
CompilerLog::CompilerLog(const std::string &logOpt)
{
    outputCIR_ = (logOpt.find("cir") != std::string::npos) || (logOpt.find("0") != std::string::npos);
    outputLLIR_ = (logOpt.find("llir") != std::string::npos) || (logOpt.find("1") != std::string::npos);
    outputASM_ = (logOpt.find("asm") != std::string::npos) || (logOpt.find("2") != std::string::npos);
    outputType_ = (logOpt.find("type") != std::string::npos) || (logOpt.find("3") != std::string::npos);
    allMethod_ = logOpt.find("all") != std::string::npos;
    cerMethod_ = (logOpt.find("all") == std::string::npos) && (logOpt.find("cer") != std::string::npos);
    noneMethod_ = (logOpt.find("all") == std::string::npos) && (logOpt.find("cer") == std::string::npos);
}

void CompilerLog::SetMethodLog(const std::string &fileName,
                               const std::string &methodName, AotMethodLogList *logList)
{
    bool enableMethodLog = !NoneMethod();
    if (CertainMethod()) {
        enableMethodLog = logList->IncludesMethod(fileName, methodName);
    }
    SetEnableMethodLog(enableMethodLog);
}

void CompilerLog::SetStubLog(const std::string &stubName, MethodLogList *logList)
{
    bool enableMethodLog = !NoneMethod();
    if (CertainMethod()) {
        enableMethodLog = logList->IncludesMethod(stubName);
    }
    SetEnableMethodLog(enableMethodLog);
}

bool MethodLogList::IncludesMethod(const std::string &methodName) const
{
    bool empty = methodName.empty();
    bool found = methods_.find(methodName) != std::string::npos;
    return !empty && found;
}

bool AotMethodLogList::IncludesMethod(const std::string &fileName, const std::string &methodName) const
{
    if (fileMethods_.find(fileName) == fileMethods_.end()) {
        return false;
    }
    std::vector methodVector = fileMethods_.at(fileName);
    auto it = find(methodVector.begin(), methodVector.end(), methodName);
    return (it != methodVector.end());
}

std::vector<std::string> AotMethodLogList::spiltString(const std::string &str, const char ch)
{
    std::vector<std::string> vec {};
    std::istringstream sstr(str.c_str());
    std::string spilt;
    while (getline(sstr, spilt, ch)) {
        vec.emplace_back(spilt);
    }
    return vec;
}

void AotMethodLogList::ParseFileMethodsName(const std::string &logMethods)
{
    std::vector<std::string> fileVector = spiltString(logMethods, fileSplitSign);
    std::vector<std::string> itemVector;
    for (size_t index = 0; index < fileVector.size(); ++index) {
        itemVector = spiltString(fileVector[index], methodSplitSign);
        std::vector<std::string> methodVector(itemVector.begin() + 1, itemVector.end());
        fileMethods_[itemVector[0]] = methodVector;
    }
}

TimeScope::TimeScope(
    std::string name, std::string methodName, uint32_t methodOffset, CompilerLog* log, Circuit* circuit)
    : ClockScope(),
      name_(std::move(name)),
      methodName_(std::move(methodName)),
      methodOffset_(methodOffset),
      circuit_(circuit),
      log_(log)
{
    if (log_ != nullptr && log_->GetEnableCompilerLogTime()) {
        if (log_->nameIndex_.find(name_) == log_->nameIndex_.end()) {
            log_->nameIndex_[name_] = log_->GetIndex();
        }
        if (circuit_ != nullptr) {
            initialNodeCount_ = circuit_->GetGateCount();
        }
    }
}

TimeScope::TimeScope(std::string name, CompilerLog* log)
    : ClockScope(), name_(std::move(name)), log_(log)
{
    if (log_ != nullptr && log_->GetEnableCompilerLogTime()) {
        if (log_->nameIndex_.find(name_) == log_->nameIndex_.end()) {
            log_->nameIndex_[name_] = log_->GetIndex();
        }
    }
}

TimeScope::~TimeScope()
{
    if (log_ != nullptr && log_->GetEnableCompilerLogTime()) {
        bool shouldPrint
            = log_->GetEnableCompilerLogAllMethodsTime() || (log_->CertainMethod() && log_->GetEnableMethodLog());
        auto time = TotalSpentTime();
        if (shouldPrint) {
            int nodeIn = INVALID_NODE_COUNT;
            int nodeOut = INVALID_NODE_COUNT;
            if (circuit_ != nullptr) {
                nodeIn = initialNodeCount_;
                nodeOut = GetCurrentNodeCount();
            }
            LOG_COMPILER(INFO) << LogFormatter()
                                      .Left("", name_, PASS_LENS)
                                      .Left("", GetShortName(methodName_), METHOD_LENS)
                                      .Right("OFFSET:", methodOffset_, OFFSET_LENS)
                                      .Right("TIME(ms):", time, TIME_LENS)
                                      .Right("NODE_IN:", nodeIn, NODE_COUNT_LENS)
                                      .Right("NODE_OUT:", nodeOut, NODE_COUNT_LENS)
                                      .str();
        }
        std::string shortName = GetShortName(methodName_);
        log_->AddPassTime(name_, time);
        log_->AddMethodTime(shortName, methodOffset_, time);
    }
}

const std::string TimeScope::GetShortName(const std::string& methodName)
{
    std::string KeyStr = "@";
    if (methodName.find(KeyStr) != std::string::npos) {
        std::string::size_type index = methodName.find(KeyStr);
        std::string extracted = methodName.substr(0, index);
        return extracted;
    } else {
        return methodName;
    }
}

size_t TimeScope::GetCurrentNodeCount() const
{
    if (circuit_ != nullptr) {
        return circuit_->GetGateCount();
    }
    return 0;
}

void CompilerLog::Print() const
{
    if (compilerLogTime_) {
        PrintTime();
    }
}

void CompilerLog::PrintPassTime() const
{
    double allPassTimeforAllMethods = 0;
    auto myMap = timePassMap_;
    auto myIndexMap = nameIndex_;
    std::multimap<int, std::string> PassTimeMap;
    for (auto it = myMap.begin(); it != myMap.end(); it++) {
        PassTimeMap.insert(make_pair(myIndexMap[it->first], it->first));
        allPassTimeforAllMethods += it->second;
    }
    for (auto [key, val] : PassTimeMap) {
        LOG_COMPILER(INFO) << std::setw(PASS_LENS) << val << " Total cost time is "<< std::setw(TIME_LENS)
                           << myMap[val] / MILLION_TIME << "ms " << "percentage:"
                           << std::fixed << std::setprecision(PERCENT_LENS)
                           << myMap[val] / allPassTimeforAllMethods * HUNDRED_TIME << "% ";
    }
}

void CompilerLog::PrintMethodTime() const
{
    double methodTotalTime = 0;
    auto myMap = timeMethodMap_;
    std::multimap<double, std::pair<uint32_t, std::string>> MethodTimeMap;
    std::map<std::pair<uint32_t, std::string>, double>::iterator it;
    for (it = myMap.begin(); it != myMap.end(); it++) {
        MethodTimeMap.insert(make_pair(it->second, it->first));
    }
    for (auto [key, val] : MethodTimeMap) {
        methodTotalTime += key;
    }
    for (auto [key, val] : MethodTimeMap) {
        LOG_COMPILER(INFO) << "method:" << std::setw(METHOD_LENS) << val.second
                           << " offset:" << std::setw(OFFSET_LENS) << val.first << " all pass cost time is "
                           << std::setw(TIME_LENS) << key / MILLION_TIME << "ms " << "percentage:" << std::fixed
                           << std::setprecision(PERCENT_LENS) << key / methodTotalTime * HUNDRED_TIME << "% ";
    }
    LOG_COMPILER(INFO) << "total compile time is " << std::setw(TIME_LENS) << methodTotalTime / MILLION_TIME << "ms ";
}

void CompilerLog::PrintTime() const
{
    LOG_COMPILER(INFO) << " ";
    PrintPassTime();
    LOG_COMPILER(INFO) << " ";
    PrintMethodTime();
}

void CompilerLog::AddMethodTime(const std::string& name, uint32_t id, double time)
{
    auto methodInfo = std::make_pair(id, name);
    timeMethodMap_[methodInfo] += time;
}

void CompilerLog::AddPassTime(const std::string& name, double time)
{
    timePassMap_[name] += time;
}

double CompilerLog::GetPassTime(const std::string& name) const
{
    auto it = timePassMap_.find(name);
    if (it != timePassMap_.end()) {
        return it->second;
    }
    return 0.0;
}

double CompilerLog::GetMethodTime(const std::string& name, uint32_t id) const
{
    auto methodInfo = std::make_pair(id, name);
    auto it = timeMethodMap_.find(methodInfo);
    if (it != timeMethodMap_.end()) {
        return it->second;
    }
    return 0.0;
}

int CompilerLog::GetIndex()
{
    return (idx_++);
}

void PGOTypeLogList::CollectGateTypeLogInfo(GateRef gate, bool isBinOp)
{
    log_ += "[PGOTypePrinter] ";
    log_ += "gate id: "+ std::to_string(acc_.GetId(gate)) + ", ";
    EcmaOpcode ecmaOpcode = acc_.GetByteCodeOpcode(gate);
    log_ += "bytecode: " + GetEcmaOpcodeStr(ecmaOpcode);

    uint32_t pcOffset = acc_.TryGetPcOffset(gate);
    if (isBinOp) {
        const PGOSampleType *sampleType = acc_.TryGetPGOType(gate).GetPGOSampleType();
        if (sampleType->IsString()) {
            log_ += " [left type: string, right type: string]";
        } else if (sampleType->HasNumber()) {
            if (sampleType->IsInt()) {
                log_ += " [left type: int, right type: int]";
            } else {
                if (sampleType->IsIntOverFlow()) {
                    log_ += " [left type: int, right type: int]";
                } else if (sampleType->IsDouble()) {
                    log_ += " [left type: double, right type: double]";
                } else {
                    log_ += " [left type: number, right type: number]";
                }
            }
        } else {
            GateRef left = acc_.GetValueIn(gate, 0);
            GateRef right = acc_.GetValueIn(gate, 1);
            GateType leftType = acc_.GetGateType(left);
            GateType rightType = acc_.GetGateType(right);
            if (leftType.IsIntType()) {
                log_ += " [left type: int, ";
            } else if (leftType.IsDoubleType()) {
                log_ += " [left type: double, ";
            } else {
                log_ += " [left type: number, ";
            }
            if (rightType.IsIntType()) {
                log_ += "right type: int]";
            } else if (rightType.IsDoubleType()) {
                log_ += "right type: double]";
            } else {
                log_ += "right type: number]";
            }
        }
    } else {
        GateRef value = acc_.GetValueIn(gate, 0);
        GateType valueType = acc_.GetGateType(value);
        if (valueType.IsIntType()) {
            log_ += " [type: int]";
        } else if (valueType.IsDoubleType()) {
            log_ += " [type: double]";
        } else {
            log_ += " [type: number]";
        }
    }
    log_ += ", pcOffset: " + std::to_string(pcOffset);
    log_ += "\n[compiler] ";
}

void PGOTypeLogList::PrintPGOTypeLog()
{
    LOG_COMPILER(INFO) << log_;
}

} // namespace panda::ecmascript::kungfu