/*
 * Copyright 2011 Alistair Leslie-Hughes
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS
#include <stdio.h>

#include "windows.h"
#include "ole2.h"
#include "corerror.h"
#include "mscoree.h"
#include "corhdr.h"

#include "wine/test.h"

#include "initguid.h"
#include "cordebug.h"

static HMODULE hmscoree;

static HRESULT (WINAPI *pCreateDebuggingInterfaceFromVersion)(int, LPCWSTR, IUnknown **);

const WCHAR v2_0[] = {'v','2','.','0','.','5','0','7','2','7',0};

static HRESULT WINAPI ManagedCallback2_QueryInterface(ICorDebugManagedCallback2 *iface, REFIID riid, void **ppv)
{
    if(IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_ICorDebugManagedCallback2, riid))
    {
        *ppv = iface;
        return S_OK;
    }

    ok(0, "unexpected riid %s\n", wine_dbgstr_guid(riid));

    *ppv = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI ManagedCallback2_AddRef(ICorDebugManagedCallback2 *iface)
{
    return 2;
}

static ULONG WINAPI ManagedCallback2_Release(ICorDebugManagedCallback2 *iface)
{
    return 1;
}

static HRESULT WINAPI ManagedCallback2_FunctionRemapOpportunity(ICorDebugManagedCallback2 *iface,
                ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                ICorDebugFunction *pOldFunction, ICorDebugFunction *pNewFunction,
                ULONG32 oldILOffset)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback2_CreateConnection(ICorDebugManagedCallback2 *iface,
                ICorDebugProcess *pProcess, CONNID dwConnectionId, WCHAR *pConnName)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback2_ChangeConnection(ICorDebugManagedCallback2 *iface,
                ICorDebugProcess *pProcess, CONNID dwConnectionId)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback2_DestroyConnection(ICorDebugManagedCallback2 *iface,
                ICorDebugProcess *pProcess, CONNID dwConnectionId)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback2_Exception(ICorDebugManagedCallback2 *iface,
                ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                ICorDebugFrame *pFrame, ULONG32 nOffset,
                CorDebugExceptionCallbackType dwEventType, DWORD dwFlags)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback2_ExceptionUnwind(ICorDebugManagedCallback2 *iface,
                ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                CorDebugExceptionUnwindCallbackType dwEventType, DWORD dwFlags)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback2_FunctionRemapComplete(ICorDebugManagedCallback2 *iface,
                ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                ICorDebugFunction *pFunction)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback2_MDANotification(ICorDebugManagedCallback2 *iface,
                ICorDebugController *pController, ICorDebugThread *pThread,
                ICorDebugMDA *pMDA)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static struct ICorDebugManagedCallback2Vtbl managedCallback2Vtbl = {
    ManagedCallback2_QueryInterface,
    ManagedCallback2_AddRef,
    ManagedCallback2_Release,
    ManagedCallback2_FunctionRemapOpportunity,
    ManagedCallback2_CreateConnection,
    ManagedCallback2_ChangeConnection,
    ManagedCallback2_DestroyConnection,
    ManagedCallback2_Exception,
    ManagedCallback2_ExceptionUnwind,
    ManagedCallback2_FunctionRemapComplete,
    ManagedCallback2_MDANotification
};

static ICorDebugManagedCallback2 ManagedCallback2 = { &managedCallback2Vtbl };

static HRESULT WINAPI ManagedCallback_QueryInterface(ICorDebugManagedCallback *iface, REFIID riid, void **ppv)
{
    if(IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_ICorDebugManagedCallback, riid))
    {
        *ppv = iface;
        return S_OK;
    }
    else if(IsEqualGUID(&IID_ICorDebugManagedCallback2, riid))
    {
        *ppv = (void**)&ManagedCallback2;
        return S_OK;
    }

    ok(0, "unexpected riid %s\n", wine_dbgstr_guid(riid));
    *ppv = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI ManagedCallback_AddRef(ICorDebugManagedCallback *iface)
{
    return 2;
}

static ULONG WINAPI ManagedCallback_Release(ICorDebugManagedCallback *iface)
{
    return 1;
}

static HRESULT WINAPI ManagedCallback_Breakpoint(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_StepComplete(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, ICorDebugStepper *pStepper, CorDebugStepReason reason)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_Break(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *thread)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_Exception(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, BOOL unhandled)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_EvalComplete(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, ICorDebugEval *pEval)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_EvalException(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, ICorDebugEval *pEval)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_CreateProcess(ICorDebugManagedCallback *iface, ICorDebugProcess *pProcess)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_ExitProcess(ICorDebugManagedCallback *iface, ICorDebugProcess *pProcess)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_CreateThread(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *thread)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_ExitThread(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *thread)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_LoadModule(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugModule *pModule)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_UnloadModule(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugModule *pModule)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_LoadClass(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugClass *c)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_UnloadClass(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugClass *c)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_DebuggerError(ICorDebugManagedCallback *iface, ICorDebugProcess *pProcess,
                    HRESULT errorHR, DWORD errorCode)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_LogMessage(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, LONG lLevel, WCHAR *pLogSwitchName, WCHAR *pMessage)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_LogSwitch(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, LONG lLevel, ULONG ulReason,
                    WCHAR *pLogSwitchName, WCHAR *pParentName)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_CreateAppDomain(ICorDebugManagedCallback *iface, ICorDebugProcess *pProcess,
                    ICorDebugAppDomain *pAppDomain)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_ExitAppDomain(ICorDebugManagedCallback *iface, ICorDebugProcess *pProcess,
                    ICorDebugAppDomain *pAppDomain)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_LoadAssembly(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugAssembly *pAssembly)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_UnloadAssembly(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugAssembly *pAssembly)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_ControlCTrap(ICorDebugManagedCallback *iface, ICorDebugProcess *pProcess)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_NameChange(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_UpdateModuleSymbols(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugModule *pModule, IStream *pSymbolStream)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_EditAndContinueRemap(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, ICorDebugFunction *pFunction, BOOL fAccurate)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI ManagedCallback_BreakpointSetError(ICorDebugManagedCallback *iface, ICorDebugAppDomain *pAppDomain,
                    ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint, DWORD dwError)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static ICorDebugManagedCallbackVtbl managedCallbackVtbl = {
    ManagedCallback_QueryInterface,
    ManagedCallback_AddRef,
    ManagedCallback_Release,
    ManagedCallback_Breakpoint,
    ManagedCallback_StepComplete,
    ManagedCallback_Break,
    ManagedCallback_Exception,
    ManagedCallback_EvalComplete,
    ManagedCallback_EvalException,
    ManagedCallback_CreateProcess,
    ManagedCallback_ExitProcess,
    ManagedCallback_CreateThread,
    ManagedCallback_ExitThread,
    ManagedCallback_LoadModule,
    ManagedCallback_UnloadModule,
    ManagedCallback_LoadClass,
    ManagedCallback_UnloadClass,
    ManagedCallback_DebuggerError,
    ManagedCallback_LogMessage,
    ManagedCallback_LogSwitch,
    ManagedCallback_CreateAppDomain,
    ManagedCallback_ExitAppDomain,
    ManagedCallback_LoadAssembly,
    ManagedCallback_UnloadAssembly,
    ManagedCallback_ControlCTrap,
    ManagedCallback_NameChange,
    ManagedCallback_UpdateModuleSymbols,
    ManagedCallback_EditAndContinueRemap,
    ManagedCallback_BreakpointSetError
};

static ICorDebugManagedCallback ManagedCallback = { &managedCallbackVtbl };

static BOOL init_functionpointers(void)
{
    hmscoree = LoadLibraryA("mscoree.dll");

    if (!hmscoree)
    {
        win_skip("mscoree.dll not available\n");
        return FALSE;
    }

    pCreateDebuggingInterfaceFromVersion = (void *)GetProcAddress(hmscoree, "CreateDebuggingInterfaceFromVersion");

    if (!pCreateDebuggingInterfaceFromVersion)
    {
        win_skip("functions not available\n");
        FreeLibrary(hmscoree);
        return FALSE;
    }

    return TRUE;
}

#define check_process_enum(core, e) _check_process_enum(__LINE__, core, e)
static void _check_process_enum(unsigned line, ICorDebug *pCorDebug, ULONG nExpected)
{
    HRESULT hr;
    ICorDebugProcessEnum *pProcessEnum = NULL;

    hr = ICorDebug_EnumerateProcesses(pCorDebug, NULL);
    ok_(__FILE__,line) (hr == E_INVALIDARG, "expected E_INVALIDARG got %08x\n", hr);

    hr = ICorDebug_EnumerateProcesses(pCorDebug, &pProcessEnum);
    ok_(__FILE__,line) (hr == S_OK, "expected S_OK got %08x\n", hr);
    if(hr == S_OK)
    {
        ULONG cnt;

        hr = ICorDebugProcessEnum_GetCount(pProcessEnum, &cnt);
        ok_(__FILE__,line) (hr == S_OK, "expected S_OK got %08x\n", hr);
        ok_(__FILE__,line) (cnt == nExpected, "expected %d got %d\n", nExpected, cnt);

        ICorDebugProcessEnum_Release(pProcessEnum);
    }
}

static void test_createDebugger(void)
{
    HRESULT hr;
    IUnknown *pUnk;
    ICorDebug *pCorDebug;

    hr = pCreateDebuggingInterfaceFromVersion(0, v2_0, &pUnk);
    ok(hr == E_INVALIDARG, "CreateDebuggingInterfaceFromVersion returned %08x\n", hr);

    hr = pCreateDebuggingInterfaceFromVersion(1, v2_0, &pUnk);
    ok(hr == E_INVALIDARG, "CreateDebuggingInterfaceFromVersion returned %08x\n", hr);

    hr = pCreateDebuggingInterfaceFromVersion(2, v2_0, &pUnk);
    ok(hr == E_INVALIDARG, "CreateDebuggingInterfaceFromVersion returned %08x\n", hr);

    hr = pCreateDebuggingInterfaceFromVersion(4, v2_0, &pUnk);
    ok(hr == E_INVALIDARG, "CreateDebuggingInterfaceFromVersion returned %08x\n", hr);

    hr = pCreateDebuggingInterfaceFromVersion(3, v2_0, NULL);
    ok(hr == E_INVALIDARG, "CreateDebuggingInterfaceFromVersion returned %08x\n", hr);

    hr = pCreateDebuggingInterfaceFromVersion(3, v2_0, &pUnk);
    if(hr == S_OK)
    {
        hr = IUnknown_QueryInterface(pUnk, &IID_ICorDebug, (void**)&pCorDebug);
        ok(hr == S_OK, "expected S_OK got %08x\n", hr);
        if(hr == S_OK)
        {
            hr = ICorDebug_Initialize(pCorDebug);
            ok(hr == S_OK, "expected S_OK got %08x\n", hr);
            if(hr == S_OK)
            {
                hr = ICorDebug_SetManagedHandler(pCorDebug, NULL);
                ok(hr == E_INVALIDARG, "expected E_INVALIDARG got %08x\n", hr);

                hr = ICorDebug_SetManagedHandler(pCorDebug, &ManagedCallback);
                ok(hr == S_OK, "expected S_OK got %08x\n", hr);

                /* We should have no processes */
                check_process_enum(pCorDebug, 0);
            }

            ICorDebug_Release(pCorDebug);
        }
        IUnknown_Release(pUnk);
    }
    else
    {
        skip(".NET 2.0 or mono not installed.\n");
    }
}

START_TEST(debugging)
{
    CoInitialize(NULL);

    if (!init_functionpointers())
        return;

    test_createDebugger();

    FreeLibrary(hmscoree);
    CoUninitialize();
}