/*
 * ErrorInfo API
 *
 * Copyright 2000 Patrik Stridvall, Juergen Schmied
 *
 * 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 "oleauto.h"

#include "combase_private.h"

#include "wine/debug.h"
#include "wine/heap.h"

WINE_DEFAULT_DEBUG_CHANNEL(ole);

static WCHAR *heap_strdupW(const WCHAR *str)
{
    WCHAR *ret = NULL;

    if (str)
    {
        size_t size;

        size = (lstrlenW(str)+1)*sizeof(WCHAR);
        ret = heap_alloc(size);
        if (ret)
            memcpy(ret, str, size);
    }

    return ret;
}

struct error_info
{
    IErrorInfo IErrorInfo_iface;
    ICreateErrorInfo ICreateErrorInfo_iface;
    ISupportErrorInfo ISupportErrorInfo_iface;
    LONG refcount;

    GUID guid;
    WCHAR *source;
    WCHAR *description;
    WCHAR *help_file;
    DWORD help_context;
};

static struct error_info *impl_from_IErrorInfo(IErrorInfo *iface)
{
    return CONTAINING_RECORD(iface, struct error_info, IErrorInfo_iface);
}

static struct error_info *impl_from_ICreateErrorInfo(ICreateErrorInfo *iface)
{
    return CONTAINING_RECORD(iface, struct error_info, ICreateErrorInfo_iface);
}

static struct error_info *impl_from_ISupportErrorInfo(ISupportErrorInfo *iface)
{
    return CONTAINING_RECORD(iface, struct error_info, ISupportErrorInfo_iface);
}

static HRESULT WINAPI errorinfo_QueryInterface(IErrorInfo *iface, REFIID riid, void **obj)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);

    TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj);

    *obj = NULL;

    if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IErrorInfo))
    {
        *obj = &error_info->IErrorInfo_iface;
    }
    else if (IsEqualIID(riid, &IID_ICreateErrorInfo))
    {
        *obj = &error_info->ICreateErrorInfo_iface;
    }
    else if (IsEqualIID(riid, &IID_ISupportErrorInfo))
    {
        *obj = &error_info->ISupportErrorInfo_iface;
    }

    if (*obj)
    {
        IUnknown_AddRef((IUnknown *)*obj);
        return S_OK;
    }

    WARN("Unsupported interface %s.\n", debugstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI errorinfo_AddRef(IErrorInfo *iface)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);
    ULONG refcount = InterlockedIncrement(&error_info->refcount);

    TRACE("%p, refcount %u.\n", iface, refcount);

    return refcount;
}

static ULONG WINAPI errorinfo_Release(IErrorInfo *iface)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);
    ULONG refcount = InterlockedDecrement(&error_info->refcount);

    TRACE("%p, refcount %u.\n", iface, refcount);

    if (!refcount)
    {
        heap_free(error_info->source);
        heap_free(error_info->description);
        heap_free(error_info->help_file);
        heap_free(error_info);
    }

    return refcount;
}

static HRESULT WINAPI errorinfo_GetGUID(IErrorInfo *iface, GUID *guid)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);

    TRACE("%p, %p.\n", iface, guid);

    if (!guid) return E_INVALIDARG;
    *guid = error_info->guid;
    return S_OK;
}

static HRESULT WINAPI errorinfo_GetSource(IErrorInfo* iface, BSTR *source)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);

    TRACE("%p, %p.\n", iface, source);

    if (!source)
        return E_INVALIDARG;
    *source = SysAllocString(error_info->source);
    return S_OK;
}

static HRESULT WINAPI errorinfo_GetDescription(IErrorInfo *iface, BSTR *description)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);

    TRACE("%p, %p.\n", iface, description);

    if (!description)
        return E_INVALIDARG;
    *description = SysAllocString(error_info->description);
    return S_OK;
}

static HRESULT WINAPI errorinfo_GetHelpFile(IErrorInfo *iface, BSTR *helpfile)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);

    TRACE("%p, %p.\n", iface, helpfile);

    if (!helpfile)
        return E_INVALIDARG;
    *helpfile = SysAllocString(error_info->help_file);
    return S_OK;
}

static HRESULT WINAPI errorinfo_GetHelpContext(IErrorInfo *iface, DWORD *help_context)
{
    struct error_info *error_info = impl_from_IErrorInfo(iface);

    TRACE("%p, %p.\n", iface, help_context);

    if (!help_context)
        return E_INVALIDARG;
    *help_context = error_info->help_context;

    return S_OK;
}

static const IErrorInfoVtbl errorinfo_vtbl =
{
    errorinfo_QueryInterface,
    errorinfo_AddRef,
    errorinfo_Release,
    errorinfo_GetGUID,
    errorinfo_GetSource,
    errorinfo_GetDescription,
    errorinfo_GetHelpFile,
    errorinfo_GetHelpContext
};

static HRESULT WINAPI create_errorinfo_QueryInterface(ICreateErrorInfo *iface, REFIID riid, void **obj)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);
    return IErrorInfo_QueryInterface(&error_info->IErrorInfo_iface, riid, obj);
}

static ULONG WINAPI create_errorinfo_AddRef(ICreateErrorInfo *iface)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);
    return IErrorInfo_AddRef(&error_info->IErrorInfo_iface);
}

static ULONG WINAPI create_errorinfo_Release(ICreateErrorInfo *iface)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);
    return IErrorInfo_Release(&error_info->IErrorInfo_iface);
}

static HRESULT WINAPI create_errorinfo_SetGUID(ICreateErrorInfo *iface, REFGUID guid)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);

    TRACE("%p, %s.\n", iface, debugstr_guid(guid));

    error_info->guid = *guid;

    return S_OK;
}

static HRESULT WINAPI create_errorinfo_SetSource(ICreateErrorInfo *iface, LPOLESTR source)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);

    TRACE("%p, %s.\n", iface, debugstr_w(source));

    heap_free(error_info->source);
    error_info->source = heap_strdupW(source);

    return S_OK;
}

static HRESULT WINAPI create_errorinfo_SetDescription(ICreateErrorInfo *iface, LPOLESTR description)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);

    TRACE("%p, %s.\n", iface, debugstr_w(description));

    heap_free(error_info->description);
    error_info->description = heap_strdupW(description);

    return S_OK;
}

static HRESULT WINAPI create_errorinfo_SetHelpFile(ICreateErrorInfo *iface, LPOLESTR helpfile)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);

    TRACE("%p, %s.\n", iface, debugstr_w(helpfile));

    heap_free(error_info->help_file);
    error_info->help_file = heap_strdupW(helpfile);

    return S_OK;
}

static HRESULT WINAPI create_errorinfo_SetHelpContext(ICreateErrorInfo *iface, DWORD help_context)
{
    struct error_info *error_info = impl_from_ICreateErrorInfo(iface);

    TRACE("%p, %#x.\n", iface, help_context);

    error_info->help_context = help_context;

    return S_OK;
}

static const ICreateErrorInfoVtbl create_errorinfo_vtbl =
{
    create_errorinfo_QueryInterface,
    create_errorinfo_AddRef,
    create_errorinfo_Release,
    create_errorinfo_SetGUID,
    create_errorinfo_SetSource,
    create_errorinfo_SetDescription,
    create_errorinfo_SetHelpFile,
    create_errorinfo_SetHelpContext
};

static HRESULT WINAPI support_errorinfo_QueryInterface(ISupportErrorInfo *iface, REFIID riid, void **obj)
{
    struct error_info *error_info = impl_from_ISupportErrorInfo(iface);
    return IErrorInfo_QueryInterface(&error_info->IErrorInfo_iface, riid, obj);
}

static ULONG WINAPI support_errorinfo_AddRef(ISupportErrorInfo *iface)
{
    struct error_info *error_info = impl_from_ISupportErrorInfo(iface);
    return IErrorInfo_AddRef(&error_info->IErrorInfo_iface);
}

static ULONG WINAPI support_errorinfo_Release(ISupportErrorInfo *iface)
{
    struct error_info *error_info = impl_from_ISupportErrorInfo(iface);
    return IErrorInfo_Release(&error_info->IErrorInfo_iface);
}

static HRESULT WINAPI support_errorinfo_InterfaceSupportsErrorInfo(ISupportErrorInfo *iface, REFIID riid)
{
    struct error_info *error_info = impl_from_ISupportErrorInfo(iface);

    TRACE("%p, %s.\n", iface, debugstr_guid(riid));

    return IsEqualIID(riid, &error_info->guid) ? S_OK : S_FALSE;
}

static const ISupportErrorInfoVtbl support_errorinfo_vtbl =
{
    support_errorinfo_QueryInterface,
    support_errorinfo_AddRef,
    support_errorinfo_Release,
    support_errorinfo_InterfaceSupportsErrorInfo
};

/***********************************************************************
 *                CreateErrorInfo (combase.@)
 */
HRESULT WINAPI CreateErrorInfo(ICreateErrorInfo **ret)
{
    struct error_info *error_info;

    TRACE("%p.\n", ret);

    if (!ret) return E_INVALIDARG;

    if (!(error_info = heap_alloc(sizeof(*error_info))))
        return E_OUTOFMEMORY;

    error_info->IErrorInfo_iface.lpVtbl = &errorinfo_vtbl;
    error_info->ICreateErrorInfo_iface.lpVtbl = &create_errorinfo_vtbl;
    error_info->ISupportErrorInfo_iface.lpVtbl = &support_errorinfo_vtbl;
    error_info->refcount = 1;
    error_info->source = NULL;
    error_info->description = NULL;
    error_info->help_file = NULL;
    error_info->help_context = 0;

    *ret = &error_info->ICreateErrorInfo_iface;

    return S_OK;
}

/***********************************************************************
 *                GetErrorInfo    (combase.@)
 */
HRESULT WINAPI GetErrorInfo(ULONG reserved, IErrorInfo **error_info)
{
    struct tlsdata *tlsdata;
    HRESULT hr;

    TRACE("%u, %p\n", reserved, error_info);

    if (reserved || !error_info)
        return E_INVALIDARG;

    if (FAILED(hr = com_get_tlsdata(&tlsdata)))
        return hr;

    if (!tlsdata->errorinfo)
    {
        *error_info = NULL;
        return S_FALSE;
    }

    *error_info = tlsdata->errorinfo;
    tlsdata->errorinfo = NULL;

    return S_OK;
}

/***********************************************************************
 *               SetErrorInfo    (combase.@)
 */
HRESULT WINAPI SetErrorInfo(ULONG reserved, IErrorInfo *error_info)
{
    struct tlsdata *tlsdata;
    HRESULT hr;

    TRACE("%u, %p\n", reserved, error_info);

    if (reserved)
        return E_INVALIDARG;

    if (FAILED(hr = com_get_tlsdata(&tlsdata)))
        return hr;

    if (tlsdata->errorinfo)
        IErrorInfo_Release(tlsdata->errorinfo);

    tlsdata->errorinfo = error_info;
    if (error_info)
        IErrorInfo_AddRef(error_info);

    return S_OK;
}
