/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2008. All Rights Reserved.							  */
/* Open Source Software - may be modified and shared by FRC teams. The code   */
/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib.  */
/*----------------------------------------------------------------------------*/

#include "Resource.h"
#include "WPIErrors.h"
#include "ErrorBase.h"

ReentrantSemaphore Resource::m_createLock;

/**
 * Allocate storage for a new instance of Resource.
 * Allocate a bool array of values that will get initialized to indicate that no resources
 * have been allocated yet. The indicies of the resources are [0 .. elements - 1].
 */
Resource::Resource(uint32_t elements)
{
	Synchronized sync(m_createLock);
	m_size = elements;
	m_isAllocated = new bool[m_size];
	for (uint32_t i=0; i < m_size; i++)
	{
		m_isAllocated[i] = false;
	}
}

/**
 * Factory method to create a Resource allocation-tracker *if* needed.
 *
 * @param r -- address of the caller's Resource pointer. If *r == NULL, this
 *    will construct a Resource and make *r point to it. If *r != NULL, i.e.
 *    the caller already has a Resource instance, this won't do anything.
 * @param elements -- the number of elements for this Resource allocator to
 *    track, that is, it will allocate resource numbers in the range
 *    [0 .. elements - 1].
 */
/*static*/ void Resource::CreateResourceObject(Resource **r, uint32_t elements)
{
	Synchronized sync(m_createLock);
	if (*r == NULL)
	{
		*r = new Resource(elements);
	}
}

/**
 * Delete the allocated array or resources.
 * This happens when the module is unloaded (provided it was statically allocated).
 */
Resource::~Resource()
{
	delete[] m_isAllocated;
}

/**
 * Allocate a resource.
 * When a resource is requested, mark it allocated. In this case, a free resource value
 * within the range is located and returned after it is marked allocated.
 */
uint32_t Resource::Allocate(const char *resourceDesc)
{
	Synchronized sync(m_allocateLock);
	for (uint32_t i=0; i < m_size; i++)
	{
		if (!m_isAllocated[i])
		{
			m_isAllocated[i] = true;
			return i;
		}
	}
	wpi_setWPIErrorWithContext(NoAvailableResources, resourceDesc);
	return ~0ul;
}

/**
 * Allocate a specific resource value.
 * The user requests a specific resource value, i.e. channel number and it is verified
 * unallocated, then returned.
 */
uint32_t Resource::Allocate(uint32_t index, const char *resourceDesc)
{
	Synchronized sync(m_allocateLock);
	if (index >= m_size)
	{
		wpi_setWPIErrorWithContext(ChannelIndexOutOfRange, resourceDesc);
		return ~0ul;
	}
	if ( m_isAllocated[index] )
	{
		wpi_setWPIErrorWithContext(ResourceAlreadyAllocated, resourceDesc);
		return ~0ul;
	}
	m_isAllocated[index] = true;
	return index;
}


/**
 * Free an allocated resource.
 * After a resource is no longer needed, for example a destructor is called for a channel assignment
 * class, Free will release the resource value so it can be reused somewhere else in the program.
 */
void Resource::Free(uint32_t index)
{
	Synchronized sync(m_allocateLock);
	if (index == ~0ul) return;
	if (index >= m_size)
	{
		wpi_setWPIError(NotAllocated);
		return;
	}
	if (!m_isAllocated[index])
	{
		wpi_setWPIError(NotAllocated);
		return;
	}
	m_isAllocated[index] = false;
}
