/**
 * Copyright (c) 2025-2026 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 "emit-item.h"

namespace panda::pandasm {
using panda::panda_file::MethodItem;

std::mutex EmitFunctionsJob::mutex_;

template <class T>
typename T::mapped_type Find(const T &map, typename T::key_type key)
{
    auto res = map.find(key);
    ASSERT(res != map.end());
    return res->second;
}

void EmitFunctionsQueue::Schedule()
{
    ASSERT(jobsCount_ == 0);
    std::unique_lock<std::mutex> lock(m_);

    for (auto prog : progs_) {
        auto *fileJob = new EmitFunctionsJob(items_, *prog, emit_debug_info_, entities_);
        jobs_.push_back(fileJob);
        jobsCount_++;
    }

    lock.unlock();
    jobsAvailable_.notify_all();
}

bool EmitFunctionsJob::Run()
{
    return EmitFunctions();
}

bool EmitFunctionsJob::EmitFunctions()
{
    for (const auto &f : program_.function_table) {
        const auto &[name, func] = f;

        if (func.metadata->IsForeign()) {
            continue;
        }

        auto emitter = BytecodeEmitter {};
        auto *method = static_cast<MethodItem *>(Find(entities_.method_items, name));
        if (!func.Emit(emitter, method, entities_.method_items, entities_.field_items, *items_.GetClassMap(),
                       *items_.GetStringMap(), *items_.GetLiteralArrayItemMap())) {
            std::unique_lock<std::mutex> lock(mutex_);
            AsmEmitter::SetLastError("Internal error during emitting function: " + func.name);
            return false;
        }

        auto *code = method->GetCode();
        code->SetNumVregs(func.regs_num);
        code->SetNumArgs(func.GetParamsNum());

        auto num_ins = static_cast<size_t>(std::count_if(func.ins.begin(), func.ins.end(),
                                                         [](auto &it) { return it->GetOpcode() != Opcode::INVALID; }));
        code->SetNumInstructions(num_ins);

        auto *bytes = code->GetInstructions();
        auto status = emitter.Build(static_cast<std::vector<unsigned char> *>(bytes));
        if (status != BytecodeEmitter::ErrorCode::SUCCESS) {
            std::unique_lock<std::mutex> lock(mutex_);
            AsmEmitter::SetLastError("Internal error during emitting binary code, status=" +
                                     std::to_string(static_cast<int>(status)));
            return false;
        }
        auto try_blocks = func.BuildTryBlocks(method, *items_.GetClassMap(), *bytes);
        for (auto &try_block : try_blocks) {
            code->AddTryBlock(try_block);
        }

        EmitDebugInfo(bytes, method, func, name);
        const_cast<Function &>(func).local_variable_debug.clear();
        const_cast<Function &>(func).local_variable_debug.shrink_to_fit();
        const_cast<Function &>(func).ins.clear();
        const_cast<Function &>(func).ins.shrink_to_fit();
    }
    return true;
}

void EmitFunctionsJob::EmitDebugInfo(const std::vector<uint8_t> *bytes, const panda_file::MethodItem *method,
                                     const Function &func, const std::string &name)
{
    auto *debug_info = method->GetDebugInfo();
    if (debug_info == nullptr) {
        return;
    }

    auto *line_number_program = debug_info->GetLineNumberProgram();
    auto *constant_pool = debug_info->GetConstantPool();

    std::string record_name = GetOwnerName(name);
    std::string record_source_file;
    if (!record_name.empty()) {
        auto &rec = program_.record_table.find(record_name)->second;
        record_source_file = rec.source_file;
    }

    if (!func.source_file.empty() && func.source_file != record_source_file) {
        if (!func.source_code.empty()) {
            auto *source_code_item = items_.GetStringItem(func.source_code);
            ASSERT(source_code_item->GetOffset() != 0);
            line_number_program->EmitSetSourceCode(constant_pool, source_code_item);
        }
        auto *source_file_item = items_.GetStringItem(func.source_file);
        ASSERT(source_file_item->GetOffset() != 0);
        line_number_program->EmitSetFile(constant_pool, source_file_item);
    }
    func.BuildLineNumberProgram(debug_info, *bytes, &items_, constant_pool, emit_debug_info_);
}
}