/*
 * Copyright (c) 2024 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/jit/compile_decision.h"
#include "ecmascript/jspandafile/js_pandafile.h"
#include "ecmascript/ic/profile_type_info.h"
#include "ecmascript/platform/aot_crash_info.h"

namespace panda::ecmascript {
std::string GetFileName(const std::string& fileName, bool isApp)
{
    if (isApp) {
        std::string pathOnMobile;
        std::string sanboxPath = panda::os::file::File::GetExtendedFilePath(AotCrashInfo::GetSandBoxPath());
        if (ecmascript::RealPath(sanboxPath, pathOnMobile, false)) {
            return pathOnMobile + "/method_compiled_by_jit.cfg";
        } else {
            LOG_JIT(ERROR) << "get method_compiled_by_jit.cfg path fail: " << sanboxPath;
            return "";
        }
    }
    return fileName;
}

void MethodNameCollector::Init(EcmaVM *vm)
{
    if (vm == nullptr || isInit_) {
        return;
    }
    if (!(vm->GetJSOptions().IsEnableJitMethodCollect())) {
        return;
    }
    enable_ = true;
    std::string fileName = vm->GetJSOptions().GetJitMethodPath();
    fileName = GetFileName(fileName, vm->GetJSOptions().IsEnableAPPJIT());
    if (fileName.empty()) {
        return;
    }
    file_.open(fileName.c_str(), std::ofstream::out | std::ofstream::trunc);
    if (!file_.is_open()) {
        LOG_JIT(ERROR) << "open method_compiled_by_jit.cfg fail: " << fileName;
        return;
    }
    LOG_JIT(INFO) << "open method_compiled_by_jit.cfg succ: " << fileName;
    isInit_ = true;
}

void MethodNameCollector::Collect(const std::string& methodFullName) const
{
    if (enable_ && isInit_) {
        file_ << methodFullName << std::endl;
    }
}

MethodNameCollector::~MethodNameCollector()
{
    if (enable_ && isInit_) {
        ASSERT(file_.is_open());
        file_.close();
    }
    isInit_ = false;
    enable_ = false;
}

void MethodNameFilter::Init(EcmaVM *vm)
{
    if (vm == nullptr || isInit_) {
        return;
    }
    if (!(vm->GetJSOptions().IsEnableJitMethodFilter())) {
        return;
    }
    enable_ = true;
    std::string fileName = vm->GetJSOptions().GetJitMethodPath();
    fileName = GetFileName(fileName, vm->GetJSOptions().IsEnableAPPJIT());
    if (fileName.empty()) {
        return;
    }
    std::ifstream file(fileName.c_str());
    if (!file.is_open()) {
        LOG_JIT(INFO) << "open method_compiled_by_jit.cfg fail: " << fileName;
        return;
    }
    LOG_JIT(INFO) << "open method_compiled_by_jit.cfg succ: " << fileName;
    std::string methodFullName;
    while (getline(file, methodFullName)) {
        if (methodFullName.empty()) {
            continue;
        }
        methodFullNames.insert(methodFullName);
    }
    if (methodFullNames.empty()) {
        LOG_JIT(INFO) << "the number of method names is 0.";
        return;
    }
    isInit_ = true;
}

bool MethodNameFilter::NeedCompiledByJit(const std::string& methodFullName) const
{
    if (enable_ && isInit_) {
        return methodFullNames.find(methodFullName) != methodFullNames.end();
    }
    // When filtering is not enabled, all JS functions need to be compiled.
    return true;
}

MethodNameFilter::~MethodNameFilter()
{
    methodFullNames.clear();
    enable_ = false;
    isInit_ = false;
}

MethodNameCollector CompileDecision::methodNameCollector;
MethodNameFilter CompileDecision::methodNameFilter;

CompileDecision::CompileDecision(EcmaVM *vm, JSHandle<JSFunction> &jsFunction, CompilerTier tier,
    int32_t osrOffset, JitCompileMode mode) : vm_(vm), jsFunction_(jsFunction),
    tier_(tier), osrOffset_(osrOffset), compileMode_(mode) { }

CString CompileDecision::GetMethodInfo() const
{
    uint32_t codeSize = GetCodeSize();
    return GetMethodName() + ", bytecode size:" + ToCString(codeSize);
}

CString CompileDecision::GetMethodName() const
{
    JSThread *thread = vm_->GetJSThread();
    Method *method = Method::Cast(jsFunction_->GetMethod(thread).GetTaggedObject());
    ASSERT(method != nullptr);
    auto jSPandaFile = method->GetJSPandaFile(thread);
    CString fileDesc;
    if (jSPandaFile != nullptr) {
        fileDesc = jSPandaFile->GetJSPandaFileDesc();
    }
    return fileDesc + ":" + method->GetRecordNameStr(thread) + "." + CString(method->GetMethodName(thread));
}

uint32_t CompileDecision::GetCodeSize() const
{
    JSThread *thread = vm_->GetJSThread();
    Method *method = Method::Cast(jsFunction_->GetMethod(thread).GetTaggedObject());
    return method->GetCodeSize(thread);
}

bool CompileDecision::Decision()
{
    return IsGoodCompilationRequest();
}

bool CompileDecision::IsGoodCompilationRequest() const
{
    if (!CheckJsFunctionStatus()) {
        return false;
    }

    if (!IsJsFunctionSupportCompile()) {
        DisableJitCompile();
        return false;
    }

    if (!CheckVmState()) {
        return false;
    }
    return true;
}

bool CompileDecision::IsJsFunctionSupportCompile() const
{
    JSThread *thread = vm_->GetJSThread();
    JSTaggedValue funcEnv = jsFunction_->GetLexicalEnv(thread);
    JSTaggedValue globalEnv = BaseEnv::Cast(funcEnv.GetTaggedObject())->GetGlobalEnv(thread);
    if (globalEnv.IsHole()) {
        return false;
    }

    if (!IsSupportFunctionKind()) {
        return false;
    }

    uint32_t maxSize = 9000;
    if (vm_->GetJSOptions().IsEnableJitFastCompile()) {
        maxSize = 15; // 15 is method codesize threshold during fast compiling
    }
    if (GetCodeSize() > maxSize && !(vm_->GetJSOptions().IsEnableForceJitCompileMain() && compileMode_.IsSync())) {
        LOG_JIT(DEBUG) << tier_ << "skip jit task, as too large:" << GetMethodInfo();
        return false;
    }
    Method *method = Method::Cast(jsFunction_->GetMethod(vm_->GetJSThread()).GetTaggedObject());
    if (vm_->IsEnableOsr() && osrOffset_ != MachineCode::INVALID_OSR_OFFSET &&
        method->HasCatchBlock(vm_->GetJSThread())) {
        LOG_JIT(DEBUG) << "skip jit task, as osr does not support catch blocks: " << GetMethodInfo();
        return false;
    }
#if ECMASCRIPT_ENABLE_ARK_STEED
    if (method->HasCatchBlock(vm_->GetJSThread())) {
        LOG_JIT(DEBUG) << "skip jit task, as ArkSteed does not support catch blocks: " << GetMethodInfo();
        return false;
    }
#endif
    methodNameCollector.Collect(std::string(GetMethodName()));
    if (!methodNameFilter.NeedCompiledByJit(std::string(GetMethodName()))) {
        LOG_JIT(DEBUG) << "skip jit task, as not the compilation target:" << GetMethodInfo();
        return false;
    }
    return true;
}

bool CompileDecision::IsSupportFunctionKind() const
{
    Method *method = Method::Cast(jsFunction_->GetMethod(vm_->GetJSThread()).GetTaggedObject());
    if (jsFunction_.GetTaggedValue().IsJSSharedFunction()) {
        LOG_JIT(DEBUG) << tier_ << "method does not support compile shared function:" << GetMethodInfo();
        return false;
    }

    FunctionKind kind = method->GetFunctionKind();
    switch (kind) {
        case FunctionKind::NORMAL_FUNCTION:
        case FunctionKind::GETTER_FUNCTION:
        case FunctionKind::SETTER_FUNCTION:
        case FunctionKind::ARROW_FUNCTION:
        case FunctionKind::BASE_CONSTRUCTOR:
        case FunctionKind::CLASS_CONSTRUCTOR:
        case FunctionKind::DERIVED_CONSTRUCTOR:
        case FunctionKind::NONE_FUNCTION:
            return true;
        default:
            break;
    }
    LOG_JIT(DEBUG) << tier_ << "method does not support jit:" << GetMethodInfo() << ", kind:" << static_cast<int>(kind);
    return false;
}

bool CompileDecision::CheckJsFunctionStatus() const
{
    if (tier_.IsFastJit() && jsFunction_->IsJitCompiling()) {
        return false;
    }

    if (tier_.IsBaseLine() && jsFunction_->IsBaselinejitCompiling()) {
        return false;
    }

    if (tier_.IsFastJit() && jsFunction_->IsCompiledCode()) {
        JSTaggedValue machineCode = jsFunction_->GetMachineCode(vm_->GetJSThread());
        if (machineCode.IsMachineCodeObject() &&
            MachineCode::Cast(machineCode.GetTaggedObject())->GetOSROffset() == MachineCode::INVALID_OSR_OFFSET) {
            return false;
        }
        return true;
    }

    if (tier_.IsBaseLine() && !jsFunction_->GetBaselineCode(vm_->GetJSThread()).IsUndefined()) {
        return false;
    }
    return true;
}

void CompileDecision::DisableJitCompile() const
{
    jsFunction_->SetJitHotnessCnt(vm_->GetJSThread(), ProfileTypeInfo::JIT_DISABLE_FLAG);
}

bool CompileDecision::CheckVmState() const
{
    if (vm_->GetJSThread()->IsMachineCodeLowMemory()) {
        LOG_JIT(DEBUG) << tier_ << "skip jit task, as low code memory:" << GetMethodInfo();
        return false;
    }
    return true;
}
}  // namespace panda::ecmascript