/*
 * Web Services on Devices
 *
 * Copyright 2017 Owen Rudge 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>

#define COBJMACROS

#include "windef.h"
#include "winbase.h"
#include "wine/debug.h"
#include "wine/list.h"
#include "wsdapi.h"

WINE_DEFAULT_DEBUG_CHANNEL(wsdapi);

#define MEMORY_ALLOCATION_MAGIC     0xB10C5EED

#define ALIGNMENT                   (2 * sizeof(void*))
#define ROUND_TO_ALIGNMENT(size)    (((size) + ALIGNMENT - 1) & ~(ALIGNMENT - 1))
#define MEMORY_ALLOCATION_SIZE      ROUND_TO_ALIGNMENT(sizeof(struct memory_allocation))

struct memory_allocation
{
    int magic;
    struct list entry;

    struct list children;
};

static struct memory_allocation *find_allocation(void *ptr)
{
    struct memory_allocation *allocation;

    if (ptr == NULL)
    {
        return NULL;
    }

    allocation = (struct memory_allocation *)((char *)ptr - MEMORY_ALLOCATION_SIZE);

    if (allocation->magic != MEMORY_ALLOCATION_MAGIC)
    {
        return NULL;
    }

    return allocation;
}

static void free_allocation(struct memory_allocation *item)
{
    struct memory_allocation *child, *cursor;

    LIST_FOR_EACH_ENTRY_SAFE(child, cursor, &item->children, struct memory_allocation, entry)
    {
        free_allocation(child);
    }

    list_remove(&item->entry);
    item->magic = 0;
    HeapFree(GetProcessHeap(), 0, item);
}

void * WINAPI WSDAllocateLinkedMemory(void *pParent, SIZE_T cbSize)
{
    struct memory_allocation *allocation, *parent;
    void *ptr;

    TRACE("(%p, %lu)\n", pParent, cbSize);

    ptr = HeapAlloc(GetProcessHeap(), 0, MEMORY_ALLOCATION_SIZE + cbSize);

    if (ptr == NULL)
    {
        return NULL;
    }

    allocation = ptr;
    allocation->magic = MEMORY_ALLOCATION_MAGIC;

    list_init(&allocation->children);

    /* See if we have a parent */
    parent = find_allocation(pParent);

    if (parent != NULL)
    {
        list_add_tail(&parent->children, &allocation->entry);
    }
    else
    {
        list_init(&allocation->entry);
    }

    return (char *)ptr + MEMORY_ALLOCATION_SIZE;
}

void WINAPI WSDAttachLinkedMemory(void *pParent, void *pChild)
{
    struct memory_allocation *parent, *child;

    TRACE("(%p, %p)\n", pParent, pChild);

    child = find_allocation(pChild);
    parent = find_allocation(pParent);

    TRACE("child: %p, parent: %p\n", child, parent);

    if ((child == NULL) || (parent == NULL))
    {
        return;
    }

    list_remove(&child->entry);
    list_add_tail(&parent->children, &child->entry);
}

void WINAPI WSDDetachLinkedMemory(void *pVoid)
{
    struct memory_allocation *allocation;

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

    allocation = find_allocation(pVoid);

    if (allocation == NULL)
    {
        TRACE("Memory allocation not found\n");
        return;
    }

    list_remove(&allocation->entry);
}

void WINAPI WSDFreeLinkedMemory(void *pVoid)
{
    struct memory_allocation *allocation;

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

    allocation = find_allocation(pVoid);

    if (allocation == NULL)
    {
        TRACE("Memory allocation not found\n");
        return;
    }

    free_allocation(allocation);
}