/**
 * Copyright (c) 2021-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.
 */

#ifndef LIBPANDAFILE_DEBUG_HELPERS_H
#define LIBPANDAFILE_DEBUG_HELPERS_H

#include "debug_data_accessor-inl.h"
#include "file.h"
#include "file_items.h"
#include "method_data_accessor-inl.h"
#include "line_number_program.h"
#include "libpandabase/utils/span.h"

namespace panda::panda_file::debug_helpers {

class BytecodeOffsetResolver {
public:
    BytecodeOffsetResolver(panda_file::LineProgramState *state, uint32_t bc_offset)
        : state_(state), bc_offset_(bc_offset), prev_line_(state->GetLine()), line_(0)
    {
    }

    ~BytecodeOffsetResolver() = default;

    DEFAULT_MOVE_SEMANTIC(BytecodeOffsetResolver);
    DEFAULT_COPY_SEMANTIC(BytecodeOffsetResolver);

    panda_file::LineProgramState *GetState() const
    {
        return state_;
    }

    uint32_t GetLine() const
    {
        return line_;
    }

    void ProcessBegin() const {}

    void ProcessEnd()
    {
        if (line_ == 0) {
            line_ = state_->GetLine();
        }
    }

    bool HandleAdvanceLine(int32_t line_diff) const
    {
        state_->AdvanceLine(line_diff);
        return true;
    }

    bool HandleAdvancePc(uint32_t pc_diff) const
    {
        state_->AdvancePc(pc_diff);
        return true;
    }

    bool HandleSetFile([[maybe_unused]] uint32_t source_file_id) const
    {
        return true;
    }

    bool HandleSetSourceCode([[maybe_unused]] uint32_t source_code_id) const
    {
        return true;
    }

    bool HandleSetPrologueEnd() const
    {
        return true;
    }

    bool HandleSetEpilogueBegin() const
    {
        return true;
    }

    bool HandleStartLocal([[maybe_unused]] int32_t reg_number, [[maybe_unused]] uint32_t name_id,
                          [[maybe_unused]] uint32_t type_id) const
    {
        return true;
    }

    bool HandleStartLocalExtended([[maybe_unused]] int32_t reg_number, [[maybe_unused]] uint32_t name_id,
                                  [[maybe_unused]] uint32_t type_id, [[maybe_unused]] uint32_t type_signature_id) const
    {
        return true;
    }

    bool HandleEndLocal([[maybe_unused]] int32_t reg_number) const
    {
        return true;
    }

    bool HandleSetColumn([[maybe_unused]] int32_t column_number) const
    {
        return true;
    }

    bool HandleSpecialOpcode(uint32_t pc_offset, int32_t line_offset)
    {
        state_->AdvancePc(pc_offset);
        state_->AdvanceLine(line_offset);

        if (state_->GetAddress() == bc_offset_) {
            line_ = state_->GetLine();
            return false;
        }

        if (state_->GetAddress() > bc_offset_) {
            line_ = prev_line_;
            return false;
        }

        prev_line_ = state_->GetLine();

        return true;
    }

private:
    panda_file::LineProgramState *state_;
    uint32_t bc_offset_;
    uint32_t prev_line_;
    uint32_t line_;
};

inline size_t GetLineNumber(panda::panda_file::MethodDataAccessor mda, uint32_t bc_offset,
                            const panda::panda_file::File *panda_debug_file)
{
    auto debug_info_id = mda.GetDebugInfoId();
    if (!debug_info_id) {
        return -1;
    }

    panda::panda_file::DebugInfoDataAccessor dda(*panda_debug_file, debug_info_id.value());
    const uint8_t *program = dda.GetLineNumberProgram();

    panda::panda_file::LineProgramState state(*panda_debug_file, panda::panda_file::File::EntityId(0),
                                              dda.GetLineStart(), dda.GetConstantPool());

    BytecodeOffsetResolver resolver(&state, bc_offset);
    panda::panda_file::LineNumberProgramProcessor<BytecodeOffsetResolver> program_processor(program, &resolver);
    program_processor.Process();

    return resolver.GetLine();
}

}  // namespace panda::panda_file::debug_helpers

#endif  // LIBPANDAFILE_DEBUG_HELPERS_H