/*
 *	Running Object Table
 *
 *      Copyright 2007  Robert Shearman
 *
 * 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 <string.h>

#include "winerror.h"
#include "windef.h"
#include "winbase.h"

#include "irot.h"

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

WINE_DEFAULT_DEBUG_CHANNEL(rpcss);

/* define the structure of the running object table elements */
struct rot_entry
{
    struct list        entry;
    InterfaceData *object; /* marshaled running object*/
    InterfaceData *moniker; /* marshaled moniker that identifies this object */
    MonikerComparisonData *moniker_data; /* moniker comparison data that identifies this object */
    DWORD              cookie; /* cookie identifying this object */
    FILETIME           last_modified;
    LONG               refs;
};

static struct list RunningObjectTable = LIST_INIT(RunningObjectTable);

static CRITICAL_SECTION csRunningObjectTable;
static CRITICAL_SECTION_DEBUG critsect_debug =
{
    0, 0, &csRunningObjectTable,
    { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
      0, 0, { (DWORD_PTR)(__FILE__ ": csRunningObjectTable") }
};
static CRITICAL_SECTION csRunningObjectTable = { &critsect_debug, -1, 0, 0, 0, 0 };

static LONG last_cookie = 1;

static inline void rot_entry_release(struct rot_entry *rot_entry)
{
    if (!InterlockedDecrement(&rot_entry->refs))
    {
        HeapFree(GetProcessHeap(), 0, rot_entry->object);
        HeapFree(GetProcessHeap(), 0, rot_entry->moniker);
        HeapFree(GetProcessHeap(), 0, rot_entry->moniker_data);
        HeapFree(GetProcessHeap(), 0, rot_entry);
    }
}

HRESULT IrotRegister(
    IrotHandle h,
    const MonikerComparisonData *data,
    const InterfaceData *obj,
    const InterfaceData *mk,
    const FILETIME *time,
    DWORD grfFlags,
    IrotCookie *cookie,
    IrotContextHandle *ctxt_handle)
{
    struct rot_entry *rot_entry;
    struct rot_entry *existing_rot_entry;
    HRESULT hr;

    if (grfFlags & ~(ROTFLAGS_REGISTRATIONKEEPSALIVE|ROTFLAGS_ALLOWANYCLIENT))
    {
        WINE_ERR("Invalid grfFlags: 0x%08x\n", grfFlags & ~(ROTFLAGS_REGISTRATIONKEEPSALIVE|ROTFLAGS_ALLOWANYCLIENT));
        return E_INVALIDARG;
    }

    rot_entry = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*rot_entry));
    if (!rot_entry)
        return E_OUTOFMEMORY;

    rot_entry->refs = 1;
    rot_entry->object = HeapAlloc(GetProcessHeap(), 0, FIELD_OFFSET(InterfaceData, abData[obj->ulCntData]));
    if (!rot_entry->object)
    {
        rot_entry_release(rot_entry);
        return E_OUTOFMEMORY;
    }
    rot_entry->object->ulCntData = obj->ulCntData;
    memcpy(&rot_entry->object->abData, obj->abData, obj->ulCntData);

    rot_entry->last_modified = *time;

    rot_entry->moniker = HeapAlloc(GetProcessHeap(), 0, FIELD_OFFSET(InterfaceData, abData[mk->ulCntData]));
    if (!rot_entry->moniker)
    {
        rot_entry_release(rot_entry);
        return E_OUTOFMEMORY;
    }
    rot_entry->moniker->ulCntData = mk->ulCntData;
    memcpy(&rot_entry->moniker->abData, mk->abData, mk->ulCntData);

    rot_entry->moniker_data = HeapAlloc(GetProcessHeap(), 0, FIELD_OFFSET(MonikerComparisonData, abData[data->ulCntData]));
    if (!rot_entry->moniker_data)
    {
        rot_entry_release(rot_entry);
        return E_OUTOFMEMORY;
    }
    rot_entry->moniker_data->ulCntData = data->ulCntData;
    memcpy(&rot_entry->moniker_data->abData, data->abData, data->ulCntData);

    EnterCriticalSection(&csRunningObjectTable);

    hr = S_OK;

    LIST_FOR_EACH_ENTRY(existing_rot_entry, &RunningObjectTable, struct rot_entry, entry)
    {
        if ((existing_rot_entry->moniker_data->ulCntData == data->ulCntData) &&
            !memcmp(&data->abData, &existing_rot_entry->moniker_data->abData, data->ulCntData))
        {
            hr = MK_S_MONIKERALREADYREGISTERED;
            WINE_TRACE("moniker already registered with cookie %d\n", existing_rot_entry->cookie);
            break;
        }
    }

    list_add_tail(&RunningObjectTable, &rot_entry->entry);

    LeaveCriticalSection(&csRunningObjectTable);

    /* gives a registration identifier to the registered object*/
    *cookie = rot_entry->cookie = InterlockedIncrement(&last_cookie);
    *ctxt_handle = rot_entry;

    return hr;
}

HRESULT IrotRevoke(
    IrotHandle h,
    IrotCookie cookie,
    IrotContextHandle *ctxt_handle,
    PInterfaceData *obj,
    PInterfaceData *mk)
{
    struct rot_entry *rot_entry;

    WINE_TRACE("%d\n", cookie);

    EnterCriticalSection(&csRunningObjectTable);
    LIST_FOR_EACH_ENTRY(rot_entry, &RunningObjectTable, struct rot_entry, entry)
    {
        if (rot_entry->cookie == cookie)
        {
            HRESULT hr = S_OK;

            list_remove(&rot_entry->entry);
            LeaveCriticalSection(&csRunningObjectTable);

            *obj = MIDL_user_allocate(FIELD_OFFSET(InterfaceData, abData[rot_entry->object->ulCntData]));
            *mk = MIDL_user_allocate(FIELD_OFFSET(InterfaceData, abData[rot_entry->moniker->ulCntData]));
            if (*obj && *mk)
            {
                (*obj)->ulCntData = rot_entry->object->ulCntData;
                memcpy((*obj)->abData, rot_entry->object->abData, (*obj)->ulCntData);
                (*mk)->ulCntData = rot_entry->moniker->ulCntData;
                memcpy((*mk)->abData, rot_entry->moniker->abData, (*mk)->ulCntData);
            }
            else
            {
                MIDL_user_free(*obj);
                MIDL_user_free(*mk);
                hr = E_OUTOFMEMORY;
            }

            rot_entry_release(rot_entry);
            *ctxt_handle = NULL;
            return hr;
        }
    }
    LeaveCriticalSection(&csRunningObjectTable);

    return E_INVALIDARG;
}

HRESULT IrotIsRunning(
    IrotHandle h,
    const MonikerComparisonData *data)
{
    const struct rot_entry *rot_entry;
    HRESULT hr = S_FALSE;

    WINE_TRACE("\n");

    EnterCriticalSection(&csRunningObjectTable);

    LIST_FOR_EACH_ENTRY(rot_entry, &RunningObjectTable, const struct rot_entry, entry)
    {
        if ((rot_entry->moniker_data->ulCntData == data->ulCntData) &&
            !memcmp(&data->abData, &rot_entry->moniker_data->abData, data->ulCntData))
        {
            hr = S_OK;
            break;
        }
    }
    LeaveCriticalSection(&csRunningObjectTable);

    return hr;
}

HRESULT IrotGetObject(
    IrotHandle h,
    const MonikerComparisonData *moniker_data,
    PInterfaceData *obj,
    IrotCookie *cookie)
{
    const struct rot_entry *rot_entry;

    WINE_TRACE("%p\n", moniker_data);

    *cookie = 0;

    EnterCriticalSection(&csRunningObjectTable);

    LIST_FOR_EACH_ENTRY(rot_entry, &RunningObjectTable, const struct rot_entry, entry)
    {
        HRESULT hr = S_OK;
        if ((rot_entry->moniker_data->ulCntData == moniker_data->ulCntData) &&
            !memcmp(&moniker_data->abData, &rot_entry->moniker_data->abData, moniker_data->ulCntData))
        {
            *obj = MIDL_user_allocate(FIELD_OFFSET(InterfaceData, abData[rot_entry->object->ulCntData]));
            if (*obj)
            {
                (*obj)->ulCntData = rot_entry->object->ulCntData;
                memcpy((*obj)->abData, rot_entry->object->abData, (*obj)->ulCntData);

                *cookie = rot_entry->cookie;
            }
            else
                hr = E_OUTOFMEMORY;

            LeaveCriticalSection(&csRunningObjectTable);

            return hr;
        }
    }

    LeaveCriticalSection(&csRunningObjectTable);

    return MK_E_UNAVAILABLE;
}

HRESULT IrotNoteChangeTime(
    IrotHandle h,
    IrotCookie cookie,
    const FILETIME *last_modified_time)
{
    struct rot_entry *rot_entry;

    WINE_TRACE("%d %p\n", cookie, last_modified_time);

    EnterCriticalSection(&csRunningObjectTable);
    LIST_FOR_EACH_ENTRY(rot_entry, &RunningObjectTable, struct rot_entry, entry)
    {
        if (rot_entry->cookie == cookie)
        {
            rot_entry->last_modified = *last_modified_time;
            LeaveCriticalSection(&csRunningObjectTable);
            return S_OK;
        }
    }
    LeaveCriticalSection(&csRunningObjectTable);

    return E_INVALIDARG;
}

HRESULT IrotGetTimeOfLastChange(
    IrotHandle h,
    const MonikerComparisonData *moniker_data,
    FILETIME *time)
{
    const struct rot_entry *rot_entry;
    HRESULT hr = MK_E_UNAVAILABLE;

    WINE_TRACE("%p\n", moniker_data);

    memset(time, 0, sizeof(*time));

    EnterCriticalSection(&csRunningObjectTable);
    LIST_FOR_EACH_ENTRY(rot_entry, &RunningObjectTable, const struct rot_entry, entry)
    {
        if ((rot_entry->moniker_data->ulCntData == moniker_data->ulCntData) &&
            !memcmp(&moniker_data->abData, &rot_entry->moniker_data->abData, moniker_data->ulCntData))
        {
            *time = rot_entry->last_modified;
            hr = S_OK;
            break;
        }
    }
    LeaveCriticalSection(&csRunningObjectTable);

    return hr;
}

HRESULT IrotEnumRunning(
    IrotHandle h,
    PInterfaceList *list)
{
    const struct rot_entry *rot_entry;
    HRESULT hr = S_OK;
    ULONG moniker_count = 0;
    ULONG i = 0;

    WINE_TRACE("\n");

    EnterCriticalSection(&csRunningObjectTable);

    LIST_FOR_EACH_ENTRY( rot_entry, &RunningObjectTable, const struct rot_entry, entry )
        moniker_count++;

    *list = MIDL_user_allocate(FIELD_OFFSET(InterfaceList, interfaces[moniker_count]));
    if (*list)
    {
        (*list)->size = moniker_count;
        LIST_FOR_EACH_ENTRY( rot_entry, &RunningObjectTable, const struct rot_entry, entry )
        {
            (*list)->interfaces[i] = MIDL_user_allocate(FIELD_OFFSET(InterfaceData, abData[rot_entry->moniker->ulCntData]));
            if (!(*list)->interfaces[i])
            {
                ULONG end = i - 1;
                for (i = 0; i < end; i++)
                    MIDL_user_free((*list)->interfaces[i]);
                MIDL_user_free(*list);
                hr = E_OUTOFMEMORY;
                break;
            }
            (*list)->interfaces[i]->ulCntData = rot_entry->moniker->ulCntData;
            memcpy((*list)->interfaces[i]->abData, rot_entry->moniker->abData, rot_entry->moniker->ulCntData);
            i++;
        }
    }
    else
        hr = E_OUTOFMEMORY;

    LeaveCriticalSection(&csRunningObjectTable);

    return hr;
}

void __RPC_USER IrotContextHandle_rundown(IrotContextHandle ctxt_handle)
{
    struct rot_entry *rot_entry = ctxt_handle;
    EnterCriticalSection(&csRunningObjectTable);
    list_remove(&rot_entry->entry);
    LeaveCriticalSection(&csRunningObjectTable);
    rot_entry_release(rot_entry);
}

void * __RPC_USER MIDL_user_allocate(SIZE_T size)
{
    return HeapAlloc(GetProcessHeap(), 0, size);
}

void __RPC_USER MIDL_user_free(void * p)
{
    HeapFree(GetProcessHeap(), 0, p);
}