/*
 * Implementation of IReferenceClock
 *
 * Copyright 2004 Raphael Junqueira
 *
 * 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 "quartz_private.h"

#include "wine/debug.h"
#include <assert.h>

WINE_DEFAULT_DEBUG_CHANNEL(quartz);

static int cookie_counter;

struct advise_sink
{
    struct list entry;
    HANDLE handle;
    REFERENCE_TIME due_time, period;
    int cookie;
};

struct system_clock
{
    IReferenceClock IReferenceClock_iface;
    IUnknown IUnknown_inner;
    IUnknown *outer_unk;
    LONG refcount;

    BOOL thread_created;
    HANDLE thread, notify_event, stop_event;
    REFERENCE_TIME last_time;
    CRITICAL_SECTION cs;

    struct list sinks;
};

static inline struct system_clock *impl_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, struct system_clock, IUnknown_inner);
}

static HRESULT WINAPI system_clock_inner_QueryInterface(IUnknown *iface, REFIID iid, void **out)
{
    struct system_clock *clock = impl_from_IUnknown(iface);
    TRACE("clock %p, iid %s, out %p.\n", clock, debugstr_guid(iid), out);

    if (IsEqualGUID(iid, &IID_IUnknown))
        *out = iface;
    else if (IsEqualGUID(iid, &IID_IReferenceClock))
        *out = &clock->IReferenceClock_iface;
    else
    {
        WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid));
        *out = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown *)*out);
    return S_OK;
}

static ULONG WINAPI system_clock_inner_AddRef(IUnknown *iface)
{
    struct system_clock *clock = impl_from_IUnknown(iface);
    ULONG refcount = InterlockedIncrement(&clock->refcount);

    TRACE("%p increasing refcount to %u.\n", clock, refcount);

    return refcount;
}

static ULONG WINAPI system_clock_inner_Release(IUnknown *iface)
{
    struct system_clock *clock = impl_from_IUnknown(iface);
    ULONG refcount = InterlockedDecrement(&clock->refcount);

    TRACE("%p decreasing refcount to %u.\n", clock, refcount);

    if (!refcount)
    {
        if (clock->thread)
        {
            SetEvent(clock->stop_event);
            WaitForSingleObject(clock->thread, INFINITE);
            CloseHandle(clock->thread);
            CloseHandle(clock->notify_event);
            CloseHandle(clock->stop_event);
        }
        clock->cs.DebugInfo->Spare[0] = 0;
        DeleteCriticalSection(&clock->cs);
        heap_free(clock);

        InterlockedDecrement(&object_locks);
    }
    return refcount;
}

static const IUnknownVtbl system_clock_inner_vtbl =
{
    system_clock_inner_QueryInterface,
    system_clock_inner_AddRef,
    system_clock_inner_Release,
};

static inline struct system_clock *impl_from_IReferenceClock(IReferenceClock *iface)
{
    return CONTAINING_RECORD(iface, struct system_clock, IReferenceClock_iface);
}

static DWORD WINAPI SystemClockAdviseThread(void *param)
{
    struct system_clock *clock = param;
    struct advise_sink *sink, *cursor;
    REFERENCE_TIME current_time;
    HANDLE handles[2] = {clock->stop_event, clock->notify_event};

    TRACE("Starting advise thread for clock %p.\n", clock);

    for (;;)
    {
        DWORD timeout = INFINITE;

        EnterCriticalSection(&clock->cs);

        current_time = GetTickCount64() * 10000;

        LIST_FOR_EACH_ENTRY_SAFE(sink, cursor, &clock->sinks, struct advise_sink, entry)
        {
            if (sink->due_time <= current_time)
            {
                if (sink->period)
                {
                    DWORD periods = ((current_time - sink->due_time) / sink->period) + 1;
                    ReleaseSemaphore(sink->handle, periods, NULL);
                    sink->due_time += periods * sink->period;
                }
                else
                {
                    SetEvent(sink->handle);
                    list_remove(&sink->entry);
                    heap_free(sink);
                    continue;
                }
            }

            timeout = min(timeout, (sink->due_time - current_time) / 10000);
        }

        LeaveCriticalSection(&clock->cs);

        if (WaitForMultipleObjects(2, handles, FALSE, timeout) == 0)
            return 0;
    }
}

static void notify_thread(struct system_clock *clock)
{
    if (!InterlockedCompareExchange(&clock->thread_created, TRUE, FALSE))
    {
        clock->notify_event = CreateEventW(NULL, FALSE, FALSE, NULL);
        clock->stop_event = CreateEventW(NULL, TRUE, FALSE, NULL);
        clock->thread = CreateThread(NULL, 0, SystemClockAdviseThread, clock, 0, NULL);
    }
    SetEvent(clock->notify_event);
}

static HRESULT WINAPI SystemClockImpl_QueryInterface(IReferenceClock *iface, REFIID iid, void **out)
{
    struct system_clock *clock = impl_from_IReferenceClock(iface);
    return IUnknown_QueryInterface(clock->outer_unk, iid, out);
}

static ULONG WINAPI SystemClockImpl_AddRef(IReferenceClock *iface)
{
    struct system_clock *clock = impl_from_IReferenceClock(iface);
    return IUnknown_AddRef(clock->outer_unk);
}

static ULONG WINAPI SystemClockImpl_Release(IReferenceClock *iface)
{
    struct system_clock *clock = impl_from_IReferenceClock(iface);
    return IUnknown_Release(clock->outer_unk);
}

static HRESULT WINAPI SystemClockImpl_GetTime(IReferenceClock *iface, REFERENCE_TIME *time)
{
    struct system_clock *clock = impl_from_IReferenceClock(iface);
    REFERENCE_TIME ret;
    HRESULT hr;

    if (!time) {
        return E_POINTER;
    }

    ret = GetTickCount64() * 10000;

    EnterCriticalSection(&clock->cs);

    hr = (ret == clock->last_time) ? S_FALSE : S_OK;
    *time = clock->last_time = ret;

    LeaveCriticalSection(&clock->cs);

    TRACE("clock %p, time %p, returning %s.\n", clock, time, debugstr_time(ret));
    return hr;
}

static HRESULT WINAPI SystemClockImpl_AdviseTime(IReferenceClock *iface,
        REFERENCE_TIME base, REFERENCE_TIME offset, HEVENT event, DWORD_PTR *cookie)
{
    struct system_clock *clock = impl_from_IReferenceClock(iface);
    struct advise_sink *sink;

    TRACE("clock %p, base %s, offset %s, event %#lx, cookie %p.\n",
            clock, debugstr_time(base), debugstr_time(offset), event, cookie);

    if (!event)
        return E_INVALIDARG;

    if (base + offset <= 0)
        return E_INVALIDARG;

    if (!cookie)
        return E_POINTER;

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

    sink->handle = (HANDLE)event;
    sink->due_time = base + offset;
    sink->period = 0;
    sink->cookie = InterlockedIncrement(&cookie_counter);

    EnterCriticalSection(&clock->cs);
    list_add_tail(&clock->sinks, &sink->entry);
    LeaveCriticalSection(&clock->cs);

    notify_thread(clock);

    *cookie = sink->cookie;
    return S_OK;
}

static HRESULT WINAPI SystemClockImpl_AdvisePeriodic(IReferenceClock* iface,
        REFERENCE_TIME start, REFERENCE_TIME period, HSEMAPHORE semaphore, DWORD_PTR *cookie)
{
    struct system_clock *clock = impl_from_IReferenceClock(iface);
    struct advise_sink *sink;

    TRACE("clock %p, start %s, period %s, semaphore %#lx, cookie %p.\n",
            clock, debugstr_time(start), debugstr_time(period), semaphore, cookie);

    if (!semaphore)
        return E_INVALIDARG;

    if (start <= 0 || period <= 0)
        return E_INVALIDARG;

    if (!cookie)
        return E_POINTER;

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

    sink->handle = (HANDLE)semaphore;
    sink->due_time = start;
    sink->period = period;
    sink->cookie = InterlockedIncrement(&cookie_counter);

    EnterCriticalSection(&clock->cs);
    list_add_tail(&clock->sinks, &sink->entry);
    LeaveCriticalSection(&clock->cs);

    notify_thread(clock);

    *cookie = sink->cookie;
    return S_OK;
}

static HRESULT WINAPI SystemClockImpl_Unadvise(IReferenceClock *iface, DWORD_PTR cookie)
{
    struct system_clock *clock = impl_from_IReferenceClock(iface);
    struct advise_sink *sink;

    TRACE("clock %p, cookie %#lx.\n", clock, cookie);

    EnterCriticalSection(&clock->cs);

    LIST_FOR_EACH_ENTRY(sink, &clock->sinks, struct advise_sink, entry)
    {
        if (sink->cookie == cookie)
        {
            list_remove(&sink->entry);
            heap_free(sink);
            LeaveCriticalSection(&clock->cs);
            return S_OK;
        }
    }

    LeaveCriticalSection(&clock->cs);

    return S_FALSE;
}

static const IReferenceClockVtbl SystemClock_vtbl =
{
    SystemClockImpl_QueryInterface,
    SystemClockImpl_AddRef,
    SystemClockImpl_Release,
    SystemClockImpl_GetTime,
    SystemClockImpl_AdviseTime,
    SystemClockImpl_AdvisePeriodic,
    SystemClockImpl_Unadvise
};

HRESULT system_clock_create(IUnknown *outer, IUnknown **out)
{
    struct system_clock *object;
  
    TRACE("outer %p, out %p.\n", outer, out);

    if (!(object = heap_alloc_zero(sizeof(*object))))
    {
        *out = NULL;
        return E_OUTOFMEMORY;
    }

    object->IReferenceClock_iface.lpVtbl = &SystemClock_vtbl;
    object->IUnknown_inner.lpVtbl = &system_clock_inner_vtbl;
    object->outer_unk = outer ? outer : &object->IUnknown_inner;
    object->refcount = 1;
    list_init(&object->sinks);
    InitializeCriticalSection(&object->cs);
    object->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": SystemClockImpl.cs");

    TRACE("Created system clock %p.\n", object);
    *out = &object->IUnknown_inner;

    return S_OK;
}