 /***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Port of xrick, a Rick Dangerous clone, to Rockbox.
 * See http://www.bigorno.net/xrick/
 *
 * Copyright (C) 2008-2014 Pierluigi Vicinanza
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "xrick/system/system.h"

#include "xrick/debug.h"

#include "plugin.h"

/*
 * Local variables
 */
enum
{
    ALIGNMENT = sizeof(void*)  /* this is more of an educated guess; might want to adjust for your specific architecture */
};
static U8 * stackBuffer;
static U8 * stackTop;
static size_t stackSize;
static size_t stackMaxSize;
static bool isMemoryInitialised = false;
IFDEBUG_MEMORY( static size_t maxUsedMemory = 0; );

/*
 * Initialise memory stack
 */
bool sysmem_init(void)
{
    if (isMemoryInitialised)
    {
        return true;
    }

    if (rb->audio_status())
    {
        /* Playback must be stopped the entire time the sound buffer is used.*/
        rb->audio_stop();
    }

    stackBuffer = rb->plugin_get_audio_buffer(&stackMaxSize);
    stackTop = stackBuffer;
    stackSize = 0;
    isMemoryInitialised = true;
    return true;
}

/*
 * Cleanup memory stack
 */
void sysmem_shutdown(void)
{
    if (!isMemoryInitialised)
    {
        return;
    }

    if (stackTop != stackBuffer || stackSize != 0)
    {
        sys_error("(memory) improper deallocation detected");
    }

    IFDEBUG_MEMORY(
        sys_printf("xrick/memory: max memory usage was %u bytes\n", maxUsedMemory);
    );

    isMemoryInitialised = false;
}

/*
 * Allocate a memory-aligned block on top of the memory stack
 */
void *sysmem_push(size_t size)
{
    uintptr_t alignedPtr;
    size_t * allocatedSizePtr;

    size_t neededSize = sizeof(size_t) + size + (ALIGNMENT - 1);
    if (stackSize + neededSize > stackMaxSize)
    {
        sys_error("(memory) tried to allocate a block when memory full");
        return NULL;
    }

    alignedPtr = (((uintptr_t)stackTop) + sizeof(size_t) + ALIGNMENT) & ~((uintptr_t)(ALIGNMENT - 1));

    allocatedSizePtr = (size_t *)(alignedPtr);
    allocatedSizePtr[-1] = neededSize;

    stackTop += neededSize;
    stackSize += neededSize;

    IFDEBUG_MEMORY(
        sys_printf("xrick/memory: allocated %u bytes\n", neededSize);
        if (stackSize > maxUsedMemory) maxUsedMemory = stackSize;
    );

    return (void *)alignedPtr;
}

/*
 * Release block from the top of the memory stack
 */
void sysmem_pop(void * alignedPtr)
{
    size_t allocatedSize;

    if (!alignedPtr)
    {
        return;
    }

    if (stackSize == 0)
    {
        sys_error("(memory) tried to release a block when memory empty");
        return;
    }

    allocatedSize = ((size_t *)(alignedPtr))[-1];
    stackTop -= allocatedSize;
    stackSize -= allocatedSize;

    IFDEBUG_MEMORY(
        if ((uintptr_t)alignedPtr != ((((uintptr_t)stackTop) + sizeof(size_t) + ALIGNMENT) & ~((uintptr_t)(ALIGNMENT - 1))))
        {
            sys_error("(memory) tried to release a wrong block");
            return;
        }
    );

    IFDEBUG_MEMORY(
        sys_printf("xrick/memory: released %u bytes\n", allocatedSize);
    );
}

/* eof */
