/*  Implementation of a ring buffer for reports
 *
 * Copyright 2015 CodeWeavers, Aric Stewart
 *
 * 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 "config.h"
#include <stdarg.h>
#define NONAMELESSUNION
#include "hid.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(hid);

#define POINTER_UNUSED 0xffffffff
#define BASE_BUFFER_SIZE 32
#define MIN_BUFFER_SIZE 2
#define MAX_BUFFER_SIZE 512

struct ReportRingBuffer
{
    UINT start, end, size;

    UINT *pointers;
    UINT pointer_alloc;
    UINT buffer_size;

    CRITICAL_SECTION lock;

    BYTE *buffer;
};

struct ReportRingBuffer* RingBuffer_Create(UINT buffer_size)
{
    struct ReportRingBuffer *ring;
    int i;

    TRACE("Create Ring Buffer with buffer size %i\n",buffer_size);

    ring = HeapAlloc(GetProcessHeap(), 0, sizeof(*ring));
    if (!ring)
        return NULL;
    ring->start = ring->end = 0;
    ring->size = BASE_BUFFER_SIZE;
    ring->buffer_size = buffer_size;
    ring->pointer_alloc = 2;
    ring->pointers = HeapAlloc(GetProcessHeap(), 0, sizeof(UINT) * ring->pointer_alloc);
    if (!ring->pointers)
    {
        HeapFree(GetProcessHeap(), 0, ring);
        return NULL;
    }
    for (i = 0; i < ring->pointer_alloc; i++)
        ring->pointers[i] = POINTER_UNUSED;
    ring->buffer = HeapAlloc(GetProcessHeap(), 0, buffer_size * ring->size);
    if (!ring->buffer)
    {
        HeapFree(GetProcessHeap(), 0, ring->pointers);
        HeapFree(GetProcessHeap(), 0, ring);
        return NULL;
    }
    InitializeCriticalSection(&ring->lock);
    ring->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": RingBuffer.lock");
    return ring;
}

void RingBuffer_Destroy(struct ReportRingBuffer *ring)
{
    HeapFree(GetProcessHeap(), 0, ring->buffer);
    HeapFree(GetProcessHeap(), 0, ring->pointers);
    ring->lock.DebugInfo->Spare[0] = 0;
    DeleteCriticalSection(&ring->lock);
    HeapFree(GetProcessHeap(), 0, ring);
}

UINT RingBuffer_GetBufferSize(struct ReportRingBuffer *ring)
{
    return ring->buffer_size;
}

UINT RingBuffer_GetSize(struct ReportRingBuffer *ring)
{
    return ring->size;
}

NTSTATUS RingBuffer_SetSize(struct ReportRingBuffer *ring, UINT size)
{
    BYTE* new_buffer;
    int i;

    if (size < MIN_BUFFER_SIZE || size > MAX_BUFFER_SIZE)
        return STATUS_INVALID_PARAMETER;
    if (size == ring->size)
        return STATUS_SUCCESS;

    EnterCriticalSection(&ring->lock);
    ring->start = ring->end = 0;
    for (i = 0; i < ring->pointer_alloc; i++)
    {
        if (ring->pointers[i] != POINTER_UNUSED)
            ring->pointers[i] = 0;
    }
    new_buffer = HeapAlloc(GetProcessHeap(), 0, ring->buffer_size * size);
    if (!new_buffer)
    {
        LeaveCriticalSection(&ring->lock);
        return STATUS_NO_MEMORY;
    }
    HeapFree(GetProcessHeap(), 0, ring->buffer);
    ring->buffer = new_buffer;
    ring->size = size;
    LeaveCriticalSection(&ring->lock);
    return STATUS_SUCCESS;
}

void RingBuffer_Read(struct ReportRingBuffer *ring, UINT index, void *output, UINT *size)
{
    void *ret = NULL;

    EnterCriticalSection(&ring->lock);
    if (index >= ring->pointer_alloc || ring->pointers[index] == POINTER_UNUSED)
    {
        LeaveCriticalSection(&ring->lock);
        *size = 0;
        return;
    }
    if (ring->pointers[index] == ring->end)
    {
        LeaveCriticalSection(&ring->lock);
        *size = 0;
    }
    else
    {
        ret = &ring->buffer[ring->pointers[index] * ring->buffer_size];
        memcpy(output, ret, ring->buffer_size);
        ring->pointers[index]++;
        if (ring->pointers[index] == ring->size)
            ring->pointers[index] = 0;
        LeaveCriticalSection(&ring->lock);
        *size = ring->buffer_size;
    }
}

UINT RingBuffer_AddPointer(struct ReportRingBuffer *ring)
{
    UINT idx;
    EnterCriticalSection(&ring->lock);
    for (idx = 0; idx < ring->pointer_alloc; idx++)
        if (ring->pointers[idx] == POINTER_UNUSED)
            break;
    if (idx >= ring->pointer_alloc)
    {
        int count = idx = ring->pointer_alloc;
        ring->pointer_alloc *= 2;
        ring->pointers = HeapReAlloc(GetProcessHeap(), 0, ring->pointers, sizeof(UINT) * ring->pointer_alloc);
        for( ;count < ring->pointer_alloc; count++)
            ring->pointers[count] = POINTER_UNUSED;
    }
    ring->pointers[idx] = ring->end;
    LeaveCriticalSection(&ring->lock);
    return idx;
}

void RingBuffer_RemovePointer(struct ReportRingBuffer *ring, UINT index)
{
    EnterCriticalSection(&ring->lock);
    if (index < ring->pointer_alloc)
        ring->pointers[index] = POINTER_UNUSED;
    LeaveCriticalSection(&ring->lock);
}

void RingBuffer_Write(struct ReportRingBuffer *ring, void *data)
{
    UINT i;

    EnterCriticalSection(&ring->lock);
    memcpy(&ring->buffer[ring->end * ring->buffer_size], data, ring->buffer_size);
    ring->end++;
    if (ring->end == ring->size)
        ring->end = 0;
    if (ring->start == ring->end)
    {
        ring->start++;
        if (ring->start == ring->size)
            ring->start = 0;
    }
    for (i = 0; i < ring->pointer_alloc; i++)
        if (ring->pointers[i] == ring->end)
            ring->pointers[i] = ring->start;
    LeaveCriticalSection(&ring->lock);
}