// Copyright (c) 2021 Samsung Electronics Co., LTD
// Distributed under the MIT License.
// See the LICENSE file in the project root for more information.

#include "debugger/breakpoint_break.h"
#include "debugger/threads.h"
#include "metadata/modules.h"
#include "utils/torelease.h"

namespace netcoredbg
{

HRESULT BreakBreakpoint::GetFullyQualifiedIlOffset(ICorDebugThread *pThread, FullyQualifiedIlOffset_t &fullyQualifiedIlOffset)
{
    HRESULT Status;
    ToRelease<ICorDebugFrame> pFrame;
    IfFailRet(pThread->GetActiveFrame(&pFrame));
    if (pFrame == nullptr)
        return E_FAIL;

    mdMethodDef methodToken;
    IfFailRet(pFrame->GetFunctionToken(&methodToken));

    ToRelease<ICorDebugFunction> pFunc;
    IfFailRet(pFrame->GetFunction(&pFunc));

    ToRelease<ICorDebugCode> pCode;
    IfFailRet(pFunc->GetILCode(&pCode));
    ULONG32 methodVersion;
    IfFailRet(pCode->GetVersionNumber(&methodVersion));

    ToRelease<ICorDebugModule> pModule;
    IfFailRet(pFunc->GetModule(&pModule));

    CORDB_ADDRESS modAddress;
    IfFailRet(pModule->GetBaseAddress(&modAddress));

    ToRelease<ICorDebugILFrame> pILFrame;
    IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame));

    ULONG32 ilOffset;
    CorDebugMappingResult mappingResult;
    IfFailRet(pILFrame->GetIP(&ilOffset, &mappingResult));
    if (mappingResult == MAPPING_UNMAPPED_ADDRESS ||
        mappingResult == MAPPING_NO_INFO)
        return E_FAIL;

    fullyQualifiedIlOffset.modAddress = modAddress;
    fullyQualifiedIlOffset.methodToken = methodToken;
    fullyQualifiedIlOffset.methodVersion = methodVersion;
    fullyQualifiedIlOffset.ilOffset = ilOffset;

    return S_OK;
}

void BreakBreakpoint::SetLastStoppedIlOffset(ICorDebugProcess *pProcess, const ThreadId &lastStoppedThreadId)
{
    std::lock_guard<std::mutex> lock(m_breakMutex);

    m_lastStoppedIlOffset.Reset();

    if (lastStoppedThreadId == ThreadId::AllThreads)
        return;

    ToRelease<ICorDebugThread> pThread;
    if (SUCCEEDED(pProcess->GetThread(int(lastStoppedThreadId), &pThread)))
        GetFullyQualifiedIlOffset(pThread, m_lastStoppedIlOffset);
}

HRESULT BreakBreakpoint::ManagedCallbackBreak(ICorDebugThread *pThread, const ThreadId &lastStoppedThreadId)
{
    HRESULT Status;
    ToRelease<ICorDebugFrame> iCorFrame;
    IfFailRet(pThread->GetActiveFrame(&iCorFrame));

    // Ignore break on Break() outside of code with loaded PDB (see JMC setup during module load).
    if (iCorFrame != nullptr)
    {
        ToRelease<ICorDebugFunction> iCorFunction;
        IfFailRet(iCorFrame->GetFunction(&iCorFunction));
        ToRelease<ICorDebugFunction2> iCorFunction2;
        IfFailRet(iCorFunction->QueryInterface(IID_ICorDebugFunction2, (LPVOID*) &iCorFunction2));
        BOOL JMCStatus;
        IfFailRet(iCorFunction2->GetJMCStatus(&JMCStatus));

        if (JMCStatus == FALSE)
            return S_OK;
    }

    ThreadId threadId(getThreadId(pThread));

    // Prevent stop event duplicate, if previous stop event was for same thread and same code point.
    // The idea is - store "fully qualified IL offset" (data for module + method + IL) on any stop event
    // and only at Break() callback check the real sequence point (required delegate call).
    // Note, that step/breakpoint/etc stop event at "Debugger.Break()" source line and stop event
    // generated by CoreCLR during Debugger.Break() execution in managed code have different IL offsets,
    // but same sequence point.

    std::lock_guard<std::mutex> lock(m_breakMutex);

    if (threadId != lastStoppedThreadId ||
        m_lastStoppedIlOffset.modAddress == 0 ||
        m_lastStoppedIlOffset.methodToken == 0)
        return S_FALSE;

    FullyQualifiedIlOffset_t fullyQualifiedIlOffset;
    IfFailRet(GetFullyQualifiedIlOffset(pThread, fullyQualifiedIlOffset));

    if (fullyQualifiedIlOffset.modAddress != m_lastStoppedIlOffset.modAddress ||
        fullyQualifiedIlOffset.methodToken != m_lastStoppedIlOffset.methodToken ||
        fullyQualifiedIlOffset.methodVersion != m_lastStoppedIlOffset.methodVersion)
        return S_FALSE;

    Modules::SequencePoint lastSP;
    IfFailRet(m_sharedModules->GetSequencePointByILOffset(m_lastStoppedIlOffset.modAddress, m_lastStoppedIlOffset.methodToken,
                                                          m_lastStoppedIlOffset.methodVersion, m_lastStoppedIlOffset.ilOffset, lastSP));

    Modules::SequencePoint curSP;
    IfFailRet(m_sharedModules->GetSequencePointByILOffset(fullyQualifiedIlOffset.modAddress, fullyQualifiedIlOffset.methodToken,
                                                          fullyQualifiedIlOffset.methodVersion, fullyQualifiedIlOffset.ilOffset, curSP));

    if (lastSP.startLine != curSP.startLine ||
        lastSP.startColumn != curSP.startColumn ||
        lastSP.endLine != curSP.endLine ||
        lastSP.endColumn != curSP.endColumn ||
        lastSP.offset != curSP.offset ||
        lastSP.document != curSP.document)
        return S_FALSE;

    return S_OK;
}

} // namespace netcoredbg
