/*
 * 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 "winuser.h"
#include "webservices.h"

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

WINE_DEFAULT_DEBUG_CHANNEL(webservices);

static const struct
{
    ULONG size;
    BOOL  readonly;
}
writer_props[] =
{
    { sizeof(ULONG), FALSE },       /* WS_XML_WRITER_PROPERTY_MAX_DEPTH */
    { sizeof(BOOL), FALSE },        /* WS_XML_WRITER_PROPERTY_ALLOW_FRAGMENT */
    { sizeof(ULONG), FALSE },       /* WS_XML_READER_PROPERTY_MAX_ATTRIBUTES */
    { sizeof(BOOL), FALSE },        /* WS_XML_WRITER_PROPERTY_WRITE_DECLARATION */
    { sizeof(ULONG), FALSE },       /* WS_XML_WRITER_PROPERTY_INDENT */
    { sizeof(ULONG), FALSE },       /* WS_XML_WRITER_PROPERTY_BUFFER_TRIM_SIZE */
    { sizeof(WS_CHARSET), FALSE },  /* WS_XML_WRITER_PROPERTY_CHARSET */
    { sizeof(WS_BUFFERS), FALSE },  /* WS_XML_WRITER_PROPERTY_BUFFERS */
    { sizeof(ULONG), FALSE },       /* WS_XML_WRITER_PROPERTY_BUFFER_MAX_SIZE */
    { sizeof(WS_BYTES), FALSE },    /* WS_XML_WRITER_PROPERTY_BYTES */
    { sizeof(BOOL), TRUE },         /* WS_XML_WRITER_PROPERTY_IN_ATTRIBUTE */
    { sizeof(ULONG), FALSE },       /* WS_XML_WRITER_PROPERTY_MAX_MIME_PARTS_BUFFER_SIZE */
    { sizeof(WS_BYTES), FALSE },    /* WS_XML_WRITER_PROPERTY_INITIAL_BUFFER */
    { sizeof(BOOL), FALSE },        /* WS_XML_WRITER_PROPERTY_ALLOW_INVALID_CHARACTER_REFERENCES */
    { sizeof(ULONG), FALSE },       /* WS_XML_WRITER_PROPERTY_MAX_NAMESPACES */
    { sizeof(ULONG), TRUE },        /* WS_XML_WRITER_PROPERTY_BYTES_WRITTEN */
    { sizeof(ULONG), TRUE },        /* WS_XML_WRITER_PROPERTY_BYTES_TO_CLOSE */
    { sizeof(BOOL), FALSE },        /* WS_XML_WRITER_PROPERTY_COMPRESS_EMPTY_ELEMENTS */
    { sizeof(BOOL), FALSE }         /* WS_XML_WRITER_PROPERTY_EMIT_UNCOMPRESSED_EMPTY_ELEMENTS */
};

enum writer_state
{
    WRITER_STATE_INITIAL,
    WRITER_STATE_STARTELEMENT,
    WRITER_STATE_STARTENDELEMENT,
    WRITER_STATE_STARTATTRIBUTE,
    WRITER_STATE_STARTCDATA,
    WRITER_STATE_ENDSTARTELEMENT,
    WRITER_STATE_TEXT,
    WRITER_STATE_ENDELEMENT,
    WRITER_STATE_ENDCDATA
};

struct writer
{
    ULONG                     write_pos;
    unsigned char            *write_bufptr;
    enum writer_state         state;
    struct node              *root;
    struct node              *current;
    WS_XML_STRING            *current_ns;
    WS_XML_WRITER_OUTPUT_TYPE output_type;
    struct xmlbuf            *output_buf;
    WS_HEAP                  *output_heap;
    ULONG                     prop_count;
    WS_XML_WRITER_PROPERTY    prop[sizeof(writer_props)/sizeof(writer_props[0])];
};

static struct writer *alloc_writer(void)
{
    static const ULONG count = sizeof(writer_props)/sizeof(writer_props[0]);
    struct writer *ret;
    ULONG i, size = sizeof(*ret) + count * sizeof(WS_XML_WRITER_PROPERTY);
    char *ptr;

    for (i = 0; i < count; i++) size += writer_props[i].size;
    if (!(ret = heap_alloc_zero( size ))) return NULL;

    ptr = (char *)&ret->prop[count];
    for (i = 0; i < count; i++)
    {
        ret->prop[i].value = ptr;
        ret->prop[i].valueSize = writer_props[i].size;
        ptr += ret->prop[i].valueSize;
    }
    ret->prop_count = count;
    return ret;
}

static HRESULT set_writer_prop( struct writer *writer, WS_XML_WRITER_PROPERTY_ID id, const void *value,
                                ULONG size )
{
    if (id >= writer->prop_count || size != writer_props[id].size || writer_props[id].readonly)
        return E_INVALIDARG;

    memcpy( writer->prop[id].value, value, size );
    return S_OK;
}

static HRESULT get_writer_prop( struct writer *writer, WS_XML_WRITER_PROPERTY_ID id, void *buf, ULONG size )
{
    if (id >= writer->prop_count || size != writer_props[id].size)
        return E_INVALIDARG;

    memcpy( buf, writer->prop[id].value, writer->prop[id].valueSize );
    return S_OK;
}

static void free_writer( struct writer *writer )
{
    destroy_nodes( writer->root );
    heap_free( writer->current_ns );
    WsFreeHeap( writer->output_heap );
    heap_free( writer );
}

static void write_insert_eof( struct writer *writer, struct node *eof )
{
    if (!writer->root) writer->root = eof;
    else
    {
        eof->parent = writer->root;
        list_add_tail( &writer->root->children, &eof->entry );
    }
    writer->current = eof;
}

static void write_insert_bof( struct writer *writer, struct node *bof )
{
    writer->root->parent = bof;
    list_add_tail( &bof->children, &writer->root->entry );
    writer->current = writer->root = bof;
}

static void write_insert_node( struct writer *writer, struct node *node )
{
    node->parent = writer->current;
    if (writer->current == writer->root)
    {
        struct list *eof = list_tail( &writer->root->children );
        list_add_before( eof, &node->entry );
    }
    else list_add_tail( &writer->current->children, &node->entry );
    writer->current = node;
}

static HRESULT write_init_state( struct writer *writer )
{
    struct node *node;

    heap_free( writer->current_ns );
    writer->current_ns = NULL;
    destroy_nodes( writer->root );
    writer->root = NULL;

    if (!(node = alloc_node( WS_XML_NODE_TYPE_EOF ))) return E_OUTOFMEMORY;
    write_insert_eof( writer, node );
    writer->state = WRITER_STATE_INITIAL;
    return S_OK;
}

/**************************************************************************
 *          WsCreateWriter		[webservices.@]
 */
HRESULT WINAPI WsCreateWriter( const WS_XML_WRITER_PROPERTY *properties, ULONG count,
                               WS_XML_WRITER **handle, WS_ERROR *error )
{
    struct writer *writer;
    ULONG i, max_depth = 32, max_attrs = 128, trim_size = 4096, max_size = 65536, max_ns = 32;
    WS_CHARSET charset = WS_CHARSET_UTF8;
    HRESULT hr;

    TRACE( "%p %u %p %p\n", properties, count, handle, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!handle) return E_INVALIDARG;
    if (!(writer = alloc_writer())) return E_OUTOFMEMORY;

    set_writer_prop( writer, WS_XML_WRITER_PROPERTY_MAX_DEPTH, &max_depth, sizeof(max_depth) );
    set_writer_prop( writer, WS_XML_WRITER_PROPERTY_MAX_ATTRIBUTES, &max_attrs, sizeof(max_attrs) );
    set_writer_prop( writer, WS_XML_WRITER_PROPERTY_BUFFER_TRIM_SIZE, &trim_size, sizeof(trim_size) );
    set_writer_prop( writer, WS_XML_WRITER_PROPERTY_CHARSET, &charset, sizeof(charset) );
    set_writer_prop( writer, WS_XML_WRITER_PROPERTY_BUFFER_MAX_SIZE, &max_size, sizeof(max_size) );
    set_writer_prop( writer, WS_XML_WRITER_PROPERTY_MAX_MIME_PARTS_BUFFER_SIZE, &max_size, sizeof(max_size) );
    set_writer_prop( writer, WS_XML_WRITER_PROPERTY_MAX_NAMESPACES, &max_ns, sizeof(max_ns) );

    for (i = 0; i < count; i++)
    {
        hr = set_writer_prop( writer, properties[i].id, properties[i].value, properties[i].valueSize );
        if (hr != S_OK)
        {
            free_writer( writer );
            return hr;
        }
    }

    hr = get_writer_prop( writer, WS_XML_WRITER_PROPERTY_BUFFER_MAX_SIZE, &max_size, sizeof(max_size) );
    if (hr != S_OK)
    {
        free_writer( writer );
        return hr;
    }

    hr = WsCreateHeap( max_size, 0, NULL, 0, &writer->output_heap, NULL );
    if (hr != S_OK)
    {
        free_writer( writer );
        return hr;
    }

    hr = write_init_state( writer );
    if (hr != S_OK)
    {
        free_writer( writer );
        return hr;
    }

    *handle = (WS_XML_WRITER *)writer;
    return S_OK;
}

/**************************************************************************
 *          WsFreeWriter		[webservices.@]
 */
void WINAPI WsFreeWriter( WS_XML_WRITER *handle )
{
    struct writer *writer = (struct writer *)handle;

    TRACE( "%p\n", handle );
    free_writer( writer );
}

#define XML_BUFFER_INITIAL_ALLOCATED_SIZE 256
static struct xmlbuf *alloc_xmlbuf( WS_HEAP *heap )
{
    struct xmlbuf *ret;

    if (!(ret = ws_alloc( heap, sizeof(*ret) ))) return NULL;
    if (!(ret->ptr = ws_alloc( heap, XML_BUFFER_INITIAL_ALLOCATED_SIZE )))
    {
        ws_free( heap, ret );
        return NULL;
    }
    ret->heap           = heap;
    ret->size_allocated = XML_BUFFER_INITIAL_ALLOCATED_SIZE;
    ret->size           = 0;
    return ret;
}

static void free_xmlbuf( struct xmlbuf *xmlbuf )
{
    if (!xmlbuf) return;
    ws_free( xmlbuf->heap, xmlbuf->ptr );
    ws_free( xmlbuf->heap, xmlbuf );
}

/**************************************************************************
 *          WsCreateXmlBuffer		[webservices.@]
 */
HRESULT WINAPI WsCreateXmlBuffer( WS_HEAP *heap, const WS_XML_BUFFER_PROPERTY *properties,
                                  ULONG count, WS_XML_BUFFER **handle, WS_ERROR *error )
{
    struct xmlbuf *xmlbuf;

    if (!heap || !handle) return E_INVALIDARG;
    if (count) FIXME( "properties not implemented\n" );

    if (!(xmlbuf = alloc_xmlbuf( heap ))) return E_OUTOFMEMORY;

    *handle = (WS_XML_BUFFER *)xmlbuf;
    return S_OK;
}

/**************************************************************************
 *          WsGetWriterProperty		[webservices.@]
 */
HRESULT WINAPI WsGetWriterProperty( WS_XML_WRITER *handle, WS_XML_WRITER_PROPERTY_ID id,
                                    void *buf, ULONG size, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;

    TRACE( "%p %u %p %u %p\n", handle, id, buf, size, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer->output_type) return WS_E_INVALID_OPERATION;

    switch (id)
    {
    case WS_XML_WRITER_PROPERTY_BYTES:
    {
        WS_BYTES *bytes = buf;
        if (size != sizeof(*bytes)) return E_INVALIDARG;
        bytes->bytes  = writer->output_buf->ptr;
        bytes->length = writer->output_buf->size;
        return S_OK;
    }
    default:
        return get_writer_prop( writer, id, buf, size );
    }
}

static void set_output_buffer( struct writer *writer, struct xmlbuf *xmlbuf )
{
    /* free current buffer if it's ours */
    if (writer->output_buf && writer->output_buf->heap == writer->output_heap)
    {
        free_xmlbuf( writer->output_buf );
    }
    writer->output_buf   = xmlbuf;
    writer->output_type  = WS_XML_WRITER_OUTPUT_TYPE_BUFFER;
    writer->write_bufptr = xmlbuf->ptr;
    writer->write_pos    = 0;
}

/**************************************************************************
 *          WsSetOutput		[webservices.@]
 */
HRESULT WINAPI WsSetOutput( WS_XML_WRITER *handle, const WS_XML_WRITER_ENCODING *encoding,
                            const WS_XML_WRITER_OUTPUT *output, const WS_XML_WRITER_PROPERTY *properties,
                            ULONG count, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    struct node *node;
    HRESULT hr;
    ULONG i;

    TRACE( "%p %p %p %p %u %p\n", handle, encoding, output, properties, count, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer) return E_INVALIDARG;

    for (i = 0; i < count; i++)
    {
        hr = set_writer_prop( writer, properties[i].id, properties[i].value, properties[i].valueSize );
        if (hr != S_OK) return hr;
    }

    if ((hr = write_init_state( writer )) != S_OK) return hr;

    switch (encoding->encodingType)
    {
    case WS_XML_WRITER_ENCODING_TYPE_TEXT:
    {
        WS_XML_WRITER_TEXT_ENCODING *text = (WS_XML_WRITER_TEXT_ENCODING *)encoding;
        if (text->charSet != WS_CHARSET_UTF8)
        {
            FIXME( "charset %u not supported\n", text->charSet );
            return E_NOTIMPL;
        }
        break;
    }
    default:
        FIXME( "encoding type %u not supported\n", encoding->encodingType );
        return E_NOTIMPL;
    }
    switch (output->outputType)
    {
    case WS_XML_WRITER_OUTPUT_TYPE_BUFFER:
    {
        struct xmlbuf *xmlbuf;

        if (!(xmlbuf = alloc_xmlbuf( writer->output_heap ))) return E_OUTOFMEMORY;
        set_output_buffer( writer, xmlbuf );
        break;
    }
    default:
        FIXME( "output type %u not supported\n", output->outputType );
        return E_NOTIMPL;
    }

    if (!(node = alloc_node( WS_XML_NODE_TYPE_BOF ))) return E_OUTOFMEMORY;
    write_insert_bof( writer, node );
    return S_OK;
}

/**************************************************************************
 *          WsSetOutputToBuffer		[webservices.@]
 */
HRESULT WINAPI WsSetOutputToBuffer( WS_XML_WRITER *handle, WS_XML_BUFFER *buffer,
                                    const WS_XML_WRITER_PROPERTY *properties, ULONG count,
                                    WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    struct xmlbuf *xmlbuf = (struct xmlbuf *)buffer;
    struct node *node;
    HRESULT hr;
    ULONG i;

    TRACE( "%p %p %p %u %p\n", handle, buffer, properties, count, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !xmlbuf) return E_INVALIDARG;

    for (i = 0; i < count; i++)
    {
        hr = set_writer_prop( writer, properties[i].id, properties[i].value, properties[i].valueSize );
        if (hr != S_OK) return hr;
    }

    if ((hr = write_init_state( writer )) != S_OK) return hr;
    set_output_buffer( writer, xmlbuf );

    if (!(node = alloc_node( WS_XML_NODE_TYPE_BOF ))) return E_OUTOFMEMORY;
    write_insert_bof( writer, node );
    return S_OK;
}

static HRESULT write_grow_buffer( struct writer *writer, ULONG size )
{
    struct xmlbuf *buf = writer->output_buf;
    SIZE_T new_size;
    void *tmp;

    if (buf->size_allocated >= writer->write_pos + size)
    {
        buf->size = writer->write_pos + size;
        return S_OK;
    }
    new_size = max( buf->size_allocated * 2, writer->write_pos + size );
    if (!(tmp = ws_realloc( buf->heap, buf->ptr, new_size ))) return E_OUTOFMEMORY;
    writer->write_bufptr = buf->ptr = tmp;
    buf->size_allocated = new_size;
    buf->size = writer->write_pos + size;
    return S_OK;
}

static inline void write_char( struct writer *writer, unsigned char ch )
{
    writer->write_bufptr[writer->write_pos++] = ch;
}

static inline void write_bytes( struct writer *writer, const BYTE *bytes, ULONG len )
{
    memcpy( writer->write_bufptr + writer->write_pos, bytes, len );
    writer->write_pos += len;
}

static HRESULT write_attribute( struct writer *writer, WS_XML_ATTRIBUTE *attr )
{
    WS_XML_UTF8_TEXT *text = (WS_XML_UTF8_TEXT *)attr->value;
    unsigned char quote = attr->singleQuote ? '\'' : '"';
    const WS_XML_STRING *prefix;
    ULONG size;
    HRESULT hr;

    if (attr->prefix) prefix = attr->prefix;
    else prefix = writer->current->hdr.prefix;

    /* ' prefix:attr="value"' */

    size = attr->localName->length + 4 /* ' =""' */;
    if (prefix) size += prefix->length + 1 /* ':' */;
    if (text) size += text->value.length;
    if ((hr = write_grow_buffer( writer, size )) != S_OK) return hr;

    write_char( writer, ' ' );
    if (prefix)
    {
        write_bytes( writer, prefix->bytes, prefix->length );
        write_char( writer, ':' );
    }
    write_bytes( writer, attr->localName->bytes, attr->localName->length );
    write_char( writer, '=' );
    write_char( writer, quote );
    if (text) write_bytes( writer, text->value.bytes, text->value.length );
    write_char( writer, quote );

    /* FIXME: ignoring namespace */
    return S_OK;
}

static inline BOOL is_current_namespace( struct writer *writer, const WS_XML_STRING *ns )
{
    return (WsXmlStringEquals( writer->current_ns, ns, NULL ) == S_OK);
}

static HRESULT set_current_namespace( struct writer *writer, const WS_XML_STRING *ns )
{
    WS_XML_STRING *str;
    if (!(str = alloc_xml_string( ns->bytes, ns->length ))) return E_OUTOFMEMORY;
    heap_free( writer->current_ns );
    writer->current_ns = str;
    return S_OK;
}

static HRESULT write_startelement( struct writer *writer )
{
    WS_XML_ELEMENT_NODE *elem = &writer->current->hdr;
    ULONG size, i;
    HRESULT hr;

    /* '<prefix:localname prefix:attr="value"... xmlns:prefix="ns"' */

    size = elem->localName->length + 1 /* '<' */;
    if (elem->prefix) size += elem->prefix->length + 1 /* ':' */;
    if (elem->ns->length && !is_current_namespace( writer, elem->ns ))
    {
        size += strlen(" xmlns") + elem->ns->length + 3 /* '=""' */;
        if (elem->prefix) size += elem->prefix->length + 1 /* ':' */;
    }
    if ((hr = write_grow_buffer( writer, size )) != S_OK) return hr;

    write_char( writer, '<' );
    if (elem->prefix)
    {
        write_bytes( writer, elem->prefix->bytes, elem->prefix->length );
        write_char( writer, ':' );
    }
    write_bytes( writer, elem->localName->bytes, elem->localName->length );
    for (i = 0; i < elem->attributeCount; i++)
    {
        if ((hr = write_attribute( writer, elem->attributes[i] )) != S_OK) return hr;
    }
    if (elem->ns->length && !is_current_namespace( writer, elem->ns ))
    {
        if ((hr = set_current_namespace( writer, elem->ns )) != S_OK) return hr;

        write_bytes( writer, (const BYTE *)" xmlns", 6 );
        if (elem->prefix)
        {
            write_char( writer, ':' );
            write_bytes( writer, elem->prefix->bytes, elem->prefix->length );
        }
        write_char( writer, '=' );
        write_char( writer, '"' );
        write_bytes( writer, elem->ns->bytes, elem->ns->length );
        write_char( writer, '"' );
    }
    return S_OK;
}

static HRESULT write_endelement( struct writer *writer )
{
    struct node *node = find_parent_element( writer->current, NULL, NULL );
    WS_XML_ELEMENT_NODE *elem = &node->hdr;
    ULONG size;
    HRESULT hr;

    if (!elem) return WS_E_INVALID_FORMAT;

    /* '</prefix:localname>' */

    size = elem->localName->length + 3 /* '</>' */;
    if (elem->prefix) size += elem->prefix->length + 1 /* ':' */;
    if ((hr = write_grow_buffer( writer, size )) != S_OK) return hr;

    write_char( writer, '<' );
    write_char( writer, '/' );
    if (elem->prefix)
    {
        write_bytes( writer, elem->prefix->bytes, elem->prefix->length );
        write_char( writer, ':' );
    }
    write_bytes( writer, elem->localName->bytes, elem->localName->length );
    write_char( writer, '>' );
    return S_OK;
}

static HRESULT write_endstartelement( struct writer *writer )
{
    HRESULT hr;
    if ((hr = write_startelement( writer )) != S_OK) return hr;
    if ((hr = write_grow_buffer( writer, 1 )) != S_OK) return hr;
    write_char( writer, '>' );
    return S_OK;
}

/**************************************************************************
 *          WsWriteEndAttribute		[webservices.@]
 */
HRESULT WINAPI WsWriteEndAttribute( WS_XML_WRITER *handle, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;

    TRACE( "%p %p\n", handle, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer) return E_INVALIDARG;
    if (writer->state != WRITER_STATE_STARTATTRIBUTE) return WS_E_INVALID_OPERATION;

    writer->state = WRITER_STATE_STARTELEMENT;
    return S_OK;
}

static HRESULT write_close_element( struct writer *writer )
{
    HRESULT hr;

    if (writer->state == WRITER_STATE_STARTELEMENT)
    {
        /* '/>' */
        if ((hr = write_startelement( writer )) != S_OK) return hr;
        if ((hr = write_grow_buffer( writer, 2 )) != S_OK) return hr;
        write_char( writer, '/' );
        write_char( writer, '>' );

        writer->current = writer->current->parent;
        writer->state   = WRITER_STATE_STARTENDELEMENT;
        return S_OK;
    }
    else
    {
        struct node *node = alloc_node( WS_XML_NODE_TYPE_END_ELEMENT );
        if (!node) return E_OUTOFMEMORY;

        /* '</prefix:localname>' */
        if ((hr = write_endelement( writer )) != S_OK)
        {
            free_node( node );
            return hr;
        }

        write_insert_node( writer, node );
        writer->current = node->parent;
        writer->state   = WRITER_STATE_ENDELEMENT;
        return S_OK;
    }
    return WS_E_INVALID_OPERATION;
}

/**************************************************************************
 *          WsWriteEndElement		[webservices.@]
 */
HRESULT WINAPI WsWriteEndElement( WS_XML_WRITER *handle, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;

    TRACE( "%p %p\n", handle, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer) return E_INVALIDARG;

    return write_close_element( writer );
}

/**************************************************************************
 *          WsWriteEndStartElement		[webservices.@]
 */
HRESULT WINAPI WsWriteEndStartElement( WS_XML_WRITER *handle, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    HRESULT hr;

    TRACE( "%p %p\n", handle, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer) return E_INVALIDARG;
    if (writer->state != WRITER_STATE_STARTELEMENT) return WS_E_INVALID_OPERATION;

    if ((hr = write_endstartelement( writer )) != S_OK) return hr;
    writer->state = WRITER_STATE_ENDSTARTELEMENT;
    return S_OK;
}

static HRESULT write_add_attribute( struct writer *writer, const WS_XML_STRING *prefix,
                                    const WS_XML_STRING *localname, const WS_XML_STRING *ns,
                                    BOOL single )
{
    WS_XML_ATTRIBUTE *attr;
    WS_XML_ELEMENT_NODE *elem = &writer->current->hdr;
    HRESULT hr;

    if (!(attr = heap_alloc_zero( sizeof(*attr) ))) return E_OUTOFMEMORY;

    if (!prefix) prefix = elem->prefix;

    attr->singleQuote = !!single;
    if (prefix && !(attr->prefix = alloc_xml_string( prefix->bytes, prefix->length )))
    {
        free_attribute( attr );
        return E_OUTOFMEMORY;
    }
    if (!(attr->localName = alloc_xml_string( localname->bytes, localname->length )))
    {
        free_attribute( attr );
        return E_OUTOFMEMORY;
    }
    if (!(attr->ns = alloc_xml_string( ns->bytes, ns->length )))
    {
        free_attribute( attr );
        return E_OUTOFMEMORY;
    }
    if ((hr = append_attribute( elem, attr )) != S_OK)
    {
        free_attribute( attr );
        return hr;
    }
    writer->state = WRITER_STATE_STARTATTRIBUTE;
    return S_OK;
}

/**************************************************************************
 *          WsWriteStartAttribute		[webservices.@]
 */
HRESULT WINAPI WsWriteStartAttribute( WS_XML_WRITER *handle, const WS_XML_STRING *prefix,
                                      const WS_XML_STRING *localname, const WS_XML_STRING *ns,
                                      BOOL single, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;

    TRACE( "%p %s %s %s %d %p\n", handle, debugstr_xmlstr(prefix), debugstr_xmlstr(localname),
           debugstr_xmlstr(ns), single, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !localname || !ns) return E_INVALIDARG;

    if (writer->state != WRITER_STATE_STARTELEMENT) return WS_E_INVALID_OPERATION;

    return write_add_attribute( writer, prefix, localname, ns, single );
}

/**************************************************************************
 *          WsWriteStartCData		[webservices.@]
 */
HRESULT WINAPI WsWriteStartCData( WS_XML_WRITER *handle, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    HRESULT hr;

    TRACE( "%p %p\n", handle, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer) return E_INVALIDARG;

    /* flush current start element if necessary */
    if (writer->state == WRITER_STATE_STARTELEMENT && ((hr = write_endstartelement( writer )) != S_OK))
        return hr;

    if ((hr = write_grow_buffer( writer, 9 )) != S_OK) return hr;
    write_bytes( writer, (const BYTE *)"<![CDATA[", 9 );
    writer->state = WRITER_STATE_STARTCDATA;
    return S_OK;
}

/**************************************************************************
 *          WsWriteEndCData		[webservices.@]
 */
HRESULT WINAPI WsWriteEndCData( WS_XML_WRITER *handle, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    HRESULT hr;

    TRACE( "%p %p\n", handle, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer) return E_INVALIDARG;
    if (writer->state != WRITER_STATE_STARTCDATA) return WS_E_INVALID_OPERATION;

    if ((hr = write_grow_buffer( writer, 3 )) != S_OK) return hr;
    write_bytes( writer, (const BYTE *)"]]>", 3 );
    writer->state = WRITER_STATE_ENDCDATA;
    return S_OK;
}

static HRESULT write_add_element_node( struct writer *writer, const WS_XML_STRING *prefix,
                                       const WS_XML_STRING *localname, const WS_XML_STRING *ns )
{
    struct node *node;
    WS_XML_ELEMENT_NODE *elem, *current = &writer->current->hdr;
    HRESULT hr;

    /* flush current start element if necessary */
    if (writer->state == WRITER_STATE_STARTELEMENT && ((hr = write_endstartelement( writer )) != S_OK))
        return hr;

    if (!prefix && current->node.nodeType == WS_XML_NODE_TYPE_ELEMENT)
        prefix = current->prefix;

    if (!(node = alloc_node( WS_XML_NODE_TYPE_ELEMENT ))) return E_OUTOFMEMORY;
    elem = &node->hdr;

    if (prefix && !(elem->prefix = alloc_xml_string( prefix->bytes, prefix->length )))
    {
        free_node( node );
        return E_OUTOFMEMORY;
    }
    if (!(elem->localName = alloc_xml_string( localname->bytes, localname->length )))
    {
        free_node( node );
        return E_OUTOFMEMORY;
    }
    if (!(elem->ns = alloc_xml_string( ns->bytes, ns->length )))
    {
        free_node( node );
        return E_OUTOFMEMORY;
    }
    write_insert_node( writer, node );
    writer->state = WRITER_STATE_STARTELEMENT;
    return S_OK;
}

/**************************************************************************
 *          WsWriteStartElement		[webservices.@]
 */
HRESULT WINAPI WsWriteStartElement( WS_XML_WRITER *handle, const WS_XML_STRING *prefix,
                                    const WS_XML_STRING *localname, const WS_XML_STRING *ns,
                                    WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;

    TRACE( "%p %s %s %s %p\n", handle, debugstr_xmlstr(prefix), debugstr_xmlstr(localname),
           debugstr_xmlstr(ns), error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !localname || !ns) return E_INVALIDARG;

    return write_add_element_node( writer, prefix, localname, ns );
}

static inline void write_set_attribute_value( struct writer *writer, WS_XML_TEXT *text )
{
    WS_XML_ELEMENT_NODE *elem = &writer->current->hdr;
    elem->attributes[elem->attributeCount - 1]->value = text;
}

/**************************************************************************
 *          WsWriteText		[webservices.@]
 */
HRESULT WINAPI WsWriteText( WS_XML_WRITER *handle, const WS_XML_TEXT *text, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    WS_XML_UTF8_TEXT *dst, *src = (WS_XML_UTF8_TEXT *)text;
    HRESULT hr;

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

    if (!writer || !text) return E_INVALIDARG;

    if (text->textType != WS_XML_TEXT_TYPE_UTF8)
    {
        FIXME( "text type %u not supported\n", text->textType );
        return E_NOTIMPL;
    }

    if (writer->state == WRITER_STATE_STARTATTRIBUTE)
    {
        if (!(dst = alloc_utf8_text( src->value.bytes, src->value.length )))
            return E_OUTOFMEMORY;

        write_set_attribute_value( writer, &dst->text );
    }
    else
    {
        if ((hr = write_grow_buffer( writer, src->value.length )) != S_OK) return hr;
        write_bytes( writer, src->value.bytes, src->value.length );
    }

    return S_OK;
}

static WS_XML_TEXT *widechar_to_xmltext( const WCHAR *src, WS_XML_TEXT_TYPE type )
{
    switch (type)
    {
    case WS_XML_TEXT_TYPE_UTF8:
    {
        WS_XML_UTF8_TEXT *text;
        int len = WideCharToMultiByte( CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL ) - 1;
        if (!(text = alloc_utf8_text( NULL, len ))) return NULL;
        WideCharToMultiByte( CP_UTF8, 0, src, -1, (char *)text->value.bytes, text->value.length, NULL, NULL );
        return &text->text;
    }
    default:
        FIXME( "unhandled type %u\n", type );
        return NULL;
    }
}

static WS_XML_UTF8_TEXT *format_bool( const BOOL *ptr )
{
    static const unsigned char bool_true[] = {'t','r','u','e'}, bool_false[] = {'f','a','l','s','e'};
    if (*ptr) return alloc_utf8_text( bool_true, sizeof(bool_true) );
    else return alloc_utf8_text( bool_false, sizeof(bool_false) );
}

static WS_XML_UTF8_TEXT *format_int8( const INT8 *ptr )
{
    char buf[5]; /* "-128" */
    int len = wsprintfA( buf, "%d", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static WS_XML_UTF8_TEXT *format_int16( const INT16 *ptr )
{
    char buf[7]; /* "-32768" */
    int len = wsprintfA( buf, "%d", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static WS_XML_UTF8_TEXT *format_int32( const INT32 *ptr )
{
    char buf[12]; /* "-2147483648" */
    int len = wsprintfA( buf, "%d", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static WS_XML_UTF8_TEXT *format_int64( const INT64 *ptr )
{
    char buf[21]; /* "-9223372036854775808" */
    int len = wsprintfA( buf, "%I64d", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static WS_XML_UTF8_TEXT *format_uint8( const UINT8 *ptr )
{
    char buf[4]; /* "255" */
    int len = wsprintfA( buf, "%u", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static WS_XML_UTF8_TEXT *format_uint16( const UINT16 *ptr )
{
    char buf[6]; /* "65535" */
    int len = wsprintfA( buf, "%u", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static WS_XML_UTF8_TEXT *format_uint32( const UINT32 *ptr )
{
    char buf[11]; /* "4294967295" */
    int len = wsprintfA( buf, "%u", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static WS_XML_UTF8_TEXT *format_uint64( const UINT64 *ptr )
{
    char buf[21]; /* "18446744073709551615" */
    int len = wsprintfA( buf, "%I64u", *ptr );
    return alloc_utf8_text( (const unsigned char *)buf, len );
}

static HRESULT write_add_text_node( struct writer *writer, WS_XML_TEXT *value )
{
    struct node *node;
    WS_XML_TEXT_NODE *text;

    if (!(node = alloc_node( WS_XML_NODE_TYPE_TEXT ))) return E_OUTOFMEMORY;
    text = (WS_XML_TEXT_NODE *)node;
    text->text = value;

    write_insert_node( writer, node );
    writer->state = WRITER_STATE_TEXT;
    return S_OK;
}

static HRESULT write_text_node( struct writer *writer )
{
    HRESULT hr;
    WS_XML_TEXT_NODE *node = (WS_XML_TEXT_NODE *)writer->current;
    WS_XML_UTF8_TEXT *text = (WS_XML_UTF8_TEXT *)node->text;

    if ((hr = write_grow_buffer( writer, text->value.length )) != S_OK) return hr;
    write_bytes( writer, text->value.bytes, text->value.length );
    return S_OK;
}

static HRESULT write_type_text( struct writer *writer, WS_TYPE_MAPPING mapping,
                                WS_XML_TEXT *text )
{
    HRESULT hr;

    switch (mapping)
    {
    case WS_ELEMENT_TYPE_MAPPING:
    case WS_ELEMENT_CONTENT_TYPE_MAPPING:
        if ((hr = write_endstartelement( writer )) != S_OK) return hr;
        if ((hr = write_add_text_node( writer, text )) != S_OK) return hr;
        return write_text_node( writer );

    case WS_ATTRIBUTE_TYPE_MAPPING:
        write_set_attribute_value( writer, text );
        return S_OK;

    case WS_ANY_ELEMENT_TYPE_MAPPING:
        switch (writer->state)
        {
        case WRITER_STATE_STARTATTRIBUTE:
            write_set_attribute_value( writer, text );
            writer->state = WRITER_STATE_STARTELEMENT;
            return S_OK;

        case WRITER_STATE_STARTELEMENT:
            if ((hr = write_endstartelement( writer )) != S_OK) return hr;
            if ((hr = write_add_text_node( writer, text )) != S_OK) return hr;
            return write_text_node( writer );

        default:
            FIXME( "writer state %u not handled\n", writer->state );
            return E_NOTIMPL;
        }

    default:
        FIXME( "mapping %u not implemented\n", mapping );
        return E_NOTIMPL;
    }
}

static HRESULT write_type_bool( struct writer *writer, WS_TYPE_MAPPING mapping,
                                const WS_BOOL_DESCRIPTION *desc, const BOOL *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_bool( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_int8( struct writer *writer, WS_TYPE_MAPPING mapping,
                                const WS_INT8_DESCRIPTION *desc, const INT8 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_int8( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_int16( struct writer *writer, WS_TYPE_MAPPING mapping,
                                 const WS_INT16_DESCRIPTION *desc, const INT16 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_int16( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_int32( struct writer *writer, WS_TYPE_MAPPING mapping,
                                 const WS_INT32_DESCRIPTION *desc, const INT32 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_int32( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_int64( struct writer *writer, WS_TYPE_MAPPING mapping,
                                 const WS_INT64_DESCRIPTION *desc, const INT64 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_int64( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_uint8( struct writer *writer, WS_TYPE_MAPPING mapping,
                                 const WS_UINT8_DESCRIPTION *desc, const UINT8 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_uint8( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_uint16( struct writer *writer, WS_TYPE_MAPPING mapping,
                                  const WS_UINT16_DESCRIPTION *desc, const UINT16 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_uint16( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_uint32( struct writer *writer, WS_TYPE_MAPPING mapping,
                                  const WS_UINT32_DESCRIPTION *desc, const UINT32 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_uint32( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_uint64( struct writer *writer, WS_TYPE_MAPPING mapping,
                                  const WS_UINT64_DESCRIPTION *desc, const UINT64 *value )
{
    WS_XML_UTF8_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = format_uint64( value ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, &text->text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_wsz( struct writer *writer, WS_TYPE_MAPPING mapping,
                               const WS_WSZ_DESCRIPTION *desc, const WCHAR *value )
{
    WS_XML_TEXT *text;
    HRESULT hr;

    if (desc)
    {
        FIXME( "description not supported\n" );
        return E_NOTIMPL;
    }
    if (!(text = widechar_to_xmltext( value, WS_XML_TEXT_TYPE_UTF8 ))) return E_OUTOFMEMORY;
    if ((hr = write_type_text( writer, mapping, text )) == S_OK) return S_OK;
    heap_free( text );
    return hr;
}

static HRESULT write_type_struct( struct writer *, WS_TYPE_MAPPING, const WS_STRUCT_DESCRIPTION *,
                                  const void * );

static HRESULT write_type_struct_field( struct writer *writer, WS_TYPE_MAPPING mapping,
                                        const WS_FIELD_DESCRIPTION *desc, const void *value )
{
    HRESULT hr;

    if (desc->options && desc->options != WS_FIELD_POINTER &&
        desc->options != WS_FIELD_OPTIONAL &&
        desc->options != (WS_FIELD_POINTER | WS_FIELD_OPTIONAL))
    {
        FIXME( "options 0x%x not supported\n", desc->options );
        return E_NOTIMPL;
    }

    switch (desc->mapping)
    {
    case WS_ATTRIBUTE_FIELD_MAPPING:
        if ((hr = write_add_attribute( writer, NULL, desc->localName, desc->ns, FALSE )) != S_OK)
            return hr;
        break;

    case WS_TEXT_FIELD_MAPPING:
        break;

    default:
        FIXME( "field mapping %u not supported\n", desc->mapping );
        return E_NOTIMPL;
    }

    switch (desc->type)
    {
    case WS_STRUCT_TYPE:
    {
        const void * const *ptr = value;
        if ((hr = write_type_struct( writer, mapping, desc->typeDescription, *ptr )) != S_OK) return hr;
        break;
    }
    case WS_BOOL_TYPE:
    {
        const BOOL *ptr = value;
        if ((hr = write_type_bool( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_INT8_TYPE:
    {
        const INT8 *ptr = value;
        if ((hr = write_type_int8( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_INT16_TYPE:
    {
        const INT16 *ptr = value;
        if ((hr = write_type_int16( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_INT32_TYPE:
    {
        const INT32 *ptr = value;
        if ((hr = write_type_int32( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_INT64_TYPE:
    {
        const INT64 *ptr = value;
        if ((hr = write_type_int64( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_UINT8_TYPE:
    {
        const UINT8 *ptr = value;
        if ((hr = write_type_uint8( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_UINT16_TYPE:
    {
        const UINT16 *ptr = value;
        if ((hr = write_type_uint16( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_UINT32_TYPE:
    {
        const UINT32 *ptr = value;
        if ((hr = write_type_uint32( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_UINT64_TYPE:
    {
        const UINT64 *ptr = value;
        if ((hr = write_type_uint64( writer, mapping, desc->typeDescription, ptr )) != S_OK) return hr;
        break;
    }
    case WS_WSZ_TYPE:
    {
        const WCHAR * const *ptr = value;
        if ((hr = write_type_wsz( writer, mapping, desc->typeDescription, *ptr )) != S_OK) return hr;
        break;
    }
    default:
        FIXME( "type %u not implemented\n", desc->type );
        return E_NOTIMPL;
    }

    return S_OK;
}

static HRESULT write_type_struct( struct writer *writer, WS_TYPE_MAPPING mapping,
                                  const WS_STRUCT_DESCRIPTION *desc, const void *value )
{
    ULONG i;
    HRESULT hr;
    const char *ptr;

    if (!desc) return E_INVALIDARG;

    if (desc->structOptions)
    {
        FIXME( "struct options 0x%x not supported\n", desc->structOptions );
        return E_NOTIMPL;
    }

    for (i = 0; i < desc->fieldCount; i++)
    {
        ptr = (const char *)value + desc->fields[i]->offset;
        if ((hr = write_type_struct_field( writer, mapping, desc->fields[i], ptr )) != S_OK)
            return hr;
    }

    return S_OK;
}

static HRESULT write_type( struct writer *writer, WS_TYPE_MAPPING mapping, WS_TYPE type,
                           const void *desc, WS_WRITE_OPTION option, const void *value,
                           ULONG size )
{
    switch (type)
    {
    case WS_STRUCT_TYPE:
    {
        const void * const *ptr = value;

        if (!desc || option != WS_WRITE_REQUIRED_POINTER || size != sizeof(*ptr))
            return E_INVALIDARG;

        return write_type_struct( writer, mapping, desc, *ptr );
    }
    case WS_BOOL_TYPE:
    {
        const BOOL *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_bool( writer, mapping, desc, ptr );
    }
    case WS_INT8_TYPE:
    {
        const INT8 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_int8( writer, mapping, desc, ptr );
    }
    case WS_INT16_TYPE:
    {
        const INT16 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_int16( writer, mapping, desc, ptr );
    }
    case WS_INT32_TYPE:
    {
        const INT32 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_int32( writer, mapping, desc, ptr );
    }
    case WS_INT64_TYPE:
    {
        const INT64 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_int64( writer, mapping, desc, ptr );
    }
    case WS_UINT8_TYPE:
    {
        const UINT8 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_uint8( writer, mapping, desc, ptr );
    }
    case WS_UINT16_TYPE:
    {
        const UINT16 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_uint16( writer, mapping, desc, ptr );
    }
    case WS_UINT32_TYPE:
    {
        const UINT32 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_uint32( writer, mapping, desc, ptr );
    }
    case WS_UINT64_TYPE:
    {
        const UINT64 *ptr = value;
        if (option != WS_WRITE_REQUIRED_VALUE || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_uint64( writer, mapping, desc, ptr );
    }
    case WS_WSZ_TYPE:
    {
        const WCHAR * const *ptr = value;
        if (option != WS_WRITE_REQUIRED_POINTER || size != sizeof(*ptr)) return E_INVALIDARG;
        return write_type_wsz( writer, mapping, desc, *ptr );
    }
    default:
        FIXME( "type %u not supported\n", type );
        return E_NOTIMPL;
    }
}

/**************************************************************************
 *          WsWriteAttribute		[webservices.@]
 */
HRESULT WINAPI WsWriteAttribute( WS_XML_WRITER *handle, const WS_ATTRIBUTE_DESCRIPTION *desc,
                                 WS_WRITE_OPTION option, const void *value, ULONG size,
                                 WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    HRESULT hr;

    TRACE( "%p %p %u %p %u %p\n", handle, desc, option, value, size, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !desc || !desc->attributeLocalName || !desc->attributeNs || !value)
        return E_INVALIDARG;

    if (writer->state != WRITER_STATE_STARTELEMENT) return WS_E_INVALID_OPERATION;

    if ((hr = write_add_attribute( writer, NULL, desc->attributeLocalName, desc->attributeNs,
                                   FALSE )) != S_OK) return hr;

    if ((hr = write_type( writer, WS_ATTRIBUTE_TYPE_MAPPING, desc->type, desc->typeDescription,
                          option, value, size )) != S_OK) return hr;

    writer->state = WRITER_STATE_STARTELEMENT;
    return S_OK;
}

/**************************************************************************
 *          WsWriteElement		[webservices.@]
 */
HRESULT WINAPI WsWriteElement( WS_XML_WRITER *handle, const WS_ELEMENT_DESCRIPTION *desc,
                               WS_WRITE_OPTION option, const void *value, ULONG size,
                               WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    HRESULT hr;

    TRACE( "%p %p %u %p %u %p\n", handle, desc, option, value, size, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !desc || !desc->elementLocalName || !desc->elementNs || !value)
        return E_INVALIDARG;

    if ((hr = write_add_element_node( writer, NULL, desc->elementLocalName, desc->elementNs )) != S_OK)
        return hr;

    if ((hr = write_type( writer, WS_ANY_ELEMENT_TYPE_MAPPING, desc->type, desc->typeDescription,
                          option, value, size )) != S_OK) return hr;

    return write_close_element( writer );
}

/**************************************************************************
 *          WsWriteType		[webservices.@]
 */
HRESULT WINAPI WsWriteType( WS_XML_WRITER *handle, WS_TYPE_MAPPING mapping, WS_TYPE type,
                            const void *desc, WS_WRITE_OPTION option, const void *value,
                            ULONG size, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    HRESULT hr;

    TRACE( "%p %u %u %p %u %p %u %p\n", handle, mapping, type, desc, option, value,
           size, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !value) return E_INVALIDARG;

    switch (mapping)
    {
    case WS_ATTRIBUTE_TYPE_MAPPING:
        if (writer->state != WRITER_STATE_STARTATTRIBUTE) return WS_E_INVALID_FORMAT;
        hr = write_type( writer, mapping, type, desc, option, value, size );
        break;

    case WS_ELEMENT_TYPE_MAPPING:
    case WS_ELEMENT_CONTENT_TYPE_MAPPING:
        if (writer->state != WRITER_STATE_STARTELEMENT) return WS_E_INVALID_FORMAT;
        hr = write_type( writer, mapping, type, desc, option, value, size );
        break;

    case WS_ANY_ELEMENT_TYPE_MAPPING:
        hr = write_type( writer, mapping, type, desc, option, value, size );
        break;

    default:
        FIXME( "mapping %u not implemented\n", mapping );
        return E_NOTIMPL;
    }

    return hr;
}

static WS_TYPE map_value_type( WS_VALUE_TYPE type )
{
    switch (type)
    {
    case WS_BOOL_VALUE_TYPE:     return WS_BOOL_TYPE;
    case WS_INT8_VALUE_TYPE:     return WS_INT8_TYPE;
    case WS_INT16_VALUE_TYPE:    return WS_INT16_TYPE;
    case WS_INT32_VALUE_TYPE:    return WS_INT32_TYPE;
    case WS_INT64_VALUE_TYPE:    return WS_INT64_TYPE;
    case WS_UINT8_VALUE_TYPE:    return WS_UINT8_TYPE;
    case WS_UINT16_VALUE_TYPE:   return WS_UINT16_TYPE;
    case WS_UINT32_VALUE_TYPE:   return WS_UINT32_TYPE;
    case WS_UINT64_VALUE_TYPE:   return WS_UINT64_TYPE;
    case WS_FLOAT_VALUE_TYPE:    return WS_FLOAT_TYPE;
    case WS_DOUBLE_VALUE_TYPE:   return WS_DOUBLE_TYPE;
    case WS_DECIMAL_VALUE_TYPE:  return WS_DECIMAL_TYPE;
    case WS_DATETIME_VALUE_TYPE: return WS_DATETIME_TYPE;
    case WS_TIMESPAN_VALUE_TYPE: return WS_TIMESPAN_TYPE;
    case WS_GUID_VALUE_TYPE:     return WS_GUID_TYPE;
    default:
        FIXME( "unhandled type %u\n", type );
        return ~0u;
    }
}

/**************************************************************************
 *          WsWriteValue		[webservices.@]
 */
HRESULT WINAPI WsWriteValue( WS_XML_WRITER *handle, WS_VALUE_TYPE value_type, const void *value,
                             ULONG size, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    WS_TYPE_MAPPING mapping;
    WS_TYPE type;

    TRACE( "%p %u %p %u %p\n", handle, value_type, value, size, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !value || (type = map_value_type( value_type )) == ~0u) return E_INVALIDARG;

    switch (writer->state)
    {
    case WRITER_STATE_STARTATTRIBUTE:
        mapping = WS_ATTRIBUTE_TYPE_MAPPING;
        break;

    case WRITER_STATE_STARTELEMENT:
        mapping = WS_ELEMENT_TYPE_MAPPING;
        break;

    default:
        return WS_E_INVALID_FORMAT;
    }

    return write_type( writer, mapping, type, NULL, WS_WRITE_REQUIRED_VALUE, value, size );
}

/**************************************************************************
 *          WsWriteXmlBuffer		[webservices.@]
 */
HRESULT WINAPI WsWriteXmlBuffer( WS_XML_WRITER *handle, WS_XML_BUFFER *buffer, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    struct xmlbuf *xmlbuf = (struct xmlbuf *)buffer;
    HRESULT hr;

    TRACE( "%p %p %p\n", handle, buffer, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !xmlbuf) return E_INVALIDARG;

    if ((hr = write_grow_buffer( writer, xmlbuf->size )) != S_OK) return hr;
    write_bytes( writer, xmlbuf->ptr, xmlbuf->size );
    return S_OK;
}

/**************************************************************************
 *          WsWriteXmlBufferToBytes		[webservices.@]
 */
HRESULT WINAPI WsWriteXmlBufferToBytes( WS_XML_WRITER *handle, WS_XML_BUFFER *buffer,
                                        const WS_XML_WRITER_ENCODING *encoding,
                                        const WS_XML_WRITER_PROPERTY *properties, ULONG count,
                                        WS_HEAP *heap, void **bytes, ULONG *size, WS_ERROR *error )
{
    struct writer *writer = (struct writer *)handle;
    struct xmlbuf *xmlbuf = (struct xmlbuf *)buffer;
    HRESULT hr;
    char *buf;
    ULONG i;

    TRACE( "%p %p %p %p %u %p %p %p %p\n", handle, buffer, encoding, properties, count, heap,
           bytes, size, error );
    if (error) FIXME( "ignoring error parameter\n" );

    if (!writer || !xmlbuf || !heap || !bytes) return E_INVALIDARG;

    if (encoding && encoding->encodingType != WS_XML_WRITER_ENCODING_TYPE_TEXT)
    {
        FIXME( "encoding type %u not supported\n", encoding->encodingType );
        return E_NOTIMPL;
    }

    for (i = 0; i < count; i++)
    {
        hr = set_writer_prop( writer, properties[i].id, properties[i].value, properties[i].valueSize );
        if (hr != S_OK) return hr;
    }

    if (!(buf = ws_alloc( heap, xmlbuf->size ))) return WS_E_QUOTA_EXCEEDED;
    memcpy( buf, xmlbuf->ptr, xmlbuf->size );
    *bytes = buf;
    return S_OK;
}