/*
 * Copyright 2015, 2016 Hans Leidekker for CodeWeavers
 *
 * 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
 */

#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "webservices.h"

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

WINE_DEFAULT_DEBUG_CHANNEL(webservices);

static const struct prop_desc error_props[] =
{
    { sizeof(ULONG), TRUE },    /* WS_ERROR_PROPERTY_STRING_COUNT */
    { sizeof(ULONG), FALSE },   /* WS_ERROR_PROPERTY_ORIGINAL_ERROR_CODE */
    { sizeof(LANGID), FALSE }   /* WS_ERROR_PROPERTY_LANGID */
};

struct error
{
    ULONG            magic;
    CRITICAL_SECTION cs;
    ULONG            prop_count;
    struct prop      prop[sizeof(error_props)/sizeof(error_props[0])];
};

#define ERROR_MAGIC (('E' << 24) | ('R' << 16) | ('R' << 8) | 'O')

static struct error *alloc_error(void)
{
    static const ULONG count = sizeof(error_props)/sizeof(error_props[0]);
    struct error *ret;
    ULONG size = sizeof(*ret) + prop_size( error_props, count );

    if (!(ret = heap_alloc_zero( size ))) return NULL;

    ret->magic      = ERROR_MAGIC;
    InitializeCriticalSection( &ret->cs );
    ret->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": error.cs");

    prop_init( error_props, count, ret->prop, &ret[1] );
    ret->prop_count = count;
    return ret;
}

static void free_error( struct error *error )
{
    error->cs.DebugInfo->Spare[0] = 0;
    DeleteCriticalSection( &error->cs );
    heap_free( error );
}

/**************************************************************************
 *          WsCreateError		[webservices.@]
 */
HRESULT WINAPI WsCreateError( const WS_ERROR_PROPERTY *properties, ULONG count, WS_ERROR **handle )
{
    struct error *error;
    LANGID langid = GetUserDefaultUILanguage();
    HRESULT hr;
    ULONG i;

    TRACE( "%p %u %p\n", properties, count, handle );

    if (!handle) return E_INVALIDARG;
    if (!(error = alloc_error())) return E_OUTOFMEMORY;

    prop_set( error->prop, error->prop_count, WS_ERROR_PROPERTY_LANGID, &langid, sizeof(langid) );
    for (i = 0; i < count; i++)
    {
        if (properties[i].id == WS_ERROR_PROPERTY_ORIGINAL_ERROR_CODE)
        {
            free_error( error );
            return E_INVALIDARG;
        }
        hr = prop_set( error->prop, error->prop_count, properties[i].id, properties[i].value,
                       properties[i].valueSize );
        if (hr != S_OK)
        {
            free_error( error );
            return hr;
        }
    }

    TRACE( "created %p\n", error );
    *handle = (WS_ERROR *)error;
    return S_OK;
}

static void reset_error( struct error *error )
{
    ULONG code = 0;
    /* FIXME: release strings added with WsAddErrorString when it's implemented, reset string count */
    prop_set( error->prop, error->prop_count, WS_ERROR_PROPERTY_ORIGINAL_ERROR_CODE, &code, sizeof(code) );
}

/**************************************************************************
 *          WsFreeError		[webservices.@]
 */
void WINAPI WsFreeError( WS_ERROR *handle )
{
    struct error *error = (struct error *)handle;

    TRACE( "%p\n", handle );

    if (!error) return;

    EnterCriticalSection( &error->cs );

    if (error->magic != ERROR_MAGIC)
    {
        LeaveCriticalSection( &error->cs );
        return;
    }

    reset_error( error );
    error->magic = 0;

    LeaveCriticalSection( &error->cs );
    free_error( error );
}

/**************************************************************************
 *          WsResetError	[webservices.@]
 */
HRESULT WINAPI WsResetError( WS_ERROR *handle )
{
    struct error *error = (struct error *)handle;

    TRACE( "%p\n", handle );

    if (!error) return E_INVALIDARG;

    EnterCriticalSection( &error->cs );

    if (error->magic != ERROR_MAGIC)
    {
        LeaveCriticalSection( &error->cs );
        return E_INVALIDARG;
    }

    reset_error( error );

    LeaveCriticalSection( &error->cs );
    return S_OK;
}

/**************************************************************************
 *          WsGetErrorProperty		[webservices.@]
 */
HRESULT WINAPI WsGetErrorProperty( WS_ERROR *handle, WS_ERROR_PROPERTY_ID id, void *buf,
                                   ULONG size )
{
    struct error *error = (struct error *)handle;
    HRESULT hr;

    TRACE( "%p %u %p %u\n", handle, id, buf, size );

    if (!error) return E_INVALIDARG;

    EnterCriticalSection( &error->cs );

    if (error->magic != ERROR_MAGIC)
    {
        LeaveCriticalSection( &error->cs );
        return E_INVALIDARG;
    }

    hr = prop_get( error->prop, error->prop_count, id, buf, size );

    LeaveCriticalSection( &error->cs );
    return hr;
}

/**************************************************************************
 *          WsGetErrorString		[webservices.@]
 */
HRESULT WINAPI WsGetErrorString( WS_ERROR *handle, ULONG index, WS_STRING *str )
{
    FIXME( "%p %u %p: stub\n", handle, index, str );
    return E_NOTIMPL;
}

/**************************************************************************
 *          WsSetErrorProperty		[webservices.@]
 */
HRESULT WINAPI WsSetErrorProperty( WS_ERROR *handle, WS_ERROR_PROPERTY_ID id, const void *value,
                                   ULONG size )
{
    struct error *error = (struct error *)handle;
    HRESULT hr;

    TRACE( "%p %u %p %u\n", handle, id, value, size );

    if (!error) return E_INVALIDARG;

    EnterCriticalSection( &error->cs );

    if (error->magic != ERROR_MAGIC)
    {
        LeaveCriticalSection( &error->cs );
        return E_INVALIDARG;
    }

    if (id == WS_ERROR_PROPERTY_LANGID)
    {
        LeaveCriticalSection( &error->cs );
        return WS_E_INVALID_OPERATION;
    }

    hr = prop_set( error->prop, error->prop_count, id, value, size );

    LeaveCriticalSection( &error->cs );
    return hr;
}
