/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmarray.c,v 1.4 2008/11/23 15:09:35 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.4 $
 * $Log: rmarray.c,v $
 * Revision 1.4  2008/11/23 15:09:35  wes
 * New routine: rmArrayRemove: removes the I'th element of an array.
 *
 * Revision 1.3  2007/05/14 15:28:05  wes
 * Replaced a few int's with size_t's to support >2GB array sizes
 *
 * Revision 1.2  2006/08/07 12:50:33  wes
 * Added rmArraySet
 *
 * Revision 1.1  2005/02/19 16:36:40  wes
 * Distro merge and consolidation.
 *
 */

#include <rm/rm.h>
#include "rmprivat.h"

static RMenum
private_rmArrayRealloc(RMarray *a)
{
    char *t;
    size_t newSize;

    newSize = a->currentArraySize*a->elementSize + a->elementSize * a->chunkSize;

#if 1
    t = (void *)realloc(a->data, newSize);
    if (t == NULL)
    {
	char buf[512];
	sprintf(buf, "private_rmArrayRealloc error: unable to realloc an array of size %ld bytes. ", (long)newSize);
	rmError(buf);
	return RM_WHACKED;
    }
#else
    /* manual realloc */
    t = (char *)malloc(newSize);
    memcpy(t, a->data, a->currentArraySize*a->elementSize);
    free((void *)(a->data));
#endif
    a->data = t;
    a->currentArraySize += a->chunkSize;
    
    return RM_CHILL;
}

/* 
 * PRIVATE: rmArrayDelete. Deletes an RMarray object.
 */
RMenum
rmArrayDelete(RMarray **a)
{
    void *data;
    
    if ((RM_ASSERT(a, "rmArrayDelete error - the input array object handle is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(*a,"rmArrayDelete error - the input array object is NULL") == RM_WHACKED))
	return RM_WHACKED;

    data = (*a)->data;

    if (data != NULL)
	free(data);

    free((void *)*a);
    *a = NULL;

    return RM_CHILL;
}

/* 
 * PRIVATE: rmArrayNew. Create a new RMarray object. The size of each
 * array element is "elementSize" bytes. The realloc threshold is 
 * "chunksize", which means that adding the chunksize+1'th item will
 * induce a realloc to grow the array. The initial size of the array is
 * initSize items long. Note: you can set initSize to whatever, but
 * internally, if you ask for how many items are in the array, the 
 * answer will be zero until you rmArrayAdd some stuff to it.
 */
RMarray *
rmArrayNew(size_t initSize,
	   size_t chunkSize,
	   size_t elementSize)
{
    /* creates a new array object  */
    RMarray *t = (RMarray *)malloc(sizeof(RMarray));
    memset(t, 0, sizeof(RMarray));
    
    t->nItems = 0;
    t->chunkSize = chunkSize;
    t->elementSize = elementSize;
    t->currentArraySize = initSize;

    if (initSize != 0)
    {
	t->data = (char *)malloc(sizeof(char)*(initSize* elementSize));
	memset(t->data, 0, sizeof(char)*(initSize* elementSize));
/*	t->data = (char *)calloc(initSize, elementSize); */
    }

    return t;
}

/* 
 * PRIVATE: rmArrayGet: returns a pointer to the I'th element of the array.
 * You will need to cast this pointer to something before it can be used.
 */
void *
rmArrayGet(const RMarray *toQuery,
	   size_t indx)
{
    /* get a handle to an item in the array */
    size_t offset;

    char *t;

    if (RM_ASSERT(toQuery, "rmArrayGet error - the input RMarray object is NULL.") == RM_WHACKED)
	return NULL;

    if (indx >= toQuery->nItems)
    {
	char buf[512];
	sprintf(buf,"rmArrayGet warning - request for item #%zd, but there are only %zd items in the array.", indx, toQuery->nItems);
	rmWarning(buf);
	return NULL;
    }

    t = toQuery->data;
    offset = indx * toQuery->elementSize;
    return (void *)(t+offset);
}

/* 
 * PRiVATE: rmArrayAdd. Adds an item to the array at the end of the array.
 * If more space is needed, the array is grown automatically. Elementsize
 * worth of stuff is copied from *newData to the new array element.
 */
RMenum
rmArrayAdd(RMarray *toModify,
	   const void *newData)
{
    off_t offset;
    char *dest;
    
    if (RM_ASSERT(toModify, "rmArrayAdd error - the input RMarray object is NULL.") == RM_WHACKED)
	return RM_WHACKED;
    
    /* see if there's enough room to add one more thingamajig to the array */
    if ((toModify->nItems + 1) >= toModify->currentArraySize)
    {
	if (private_rmArrayRealloc(toModify) != RM_CHILL)
	{
	    rmError("rmArrayAdd() - unable to realloc sufficient space to add more items to the input array. Action fails. ");
	    return RM_WHACKED;
	}
    }
    
    offset = toModify->nItems * toModify->elementSize;
    dest = (char *)(toModify->data) + offset;

    if (newData != NULL)
	memcpy((void *)dest, newData, toModify->elementSize);
    else
	memset((void *)dest, 0, toModify->elementSize);

    toModify->nItems += 1;

    return RM_CHILL;
}

/* 
 * PRIVATE: rmArrayRemove: remove the Ith element from the array. If
 * RMarrays were linked list, this would be trivial. As it is, this
 * routine must do a memory shuffle.
 *
 * 11/20/2008 - this routine is untested.
 */
RMenum
rmArrayRemove(RMarray *toModify,
	      size_t indx)
{
    if (RM_ASSERT(toModify, "rmArrayRemove error - the input RMarray object is NULL.") == RM_WHACKED)
	return RM_WHACKED;

    /* sanity check on the input indx */
    if ((indx < 0) || (indx >= toModify->nItems))
    {
	char buf[256];
	/* possible problem on windows. might need to use %Id rather than %Zd */
	sprintf(buf,"rmArrayRemove error: you asked to delete the %Zd'th item, but the array size is %Zd. \n", indx, toModify->currentArraySize);
	rmError(buf);
	return RM_WHACKED;
    }

    /* 
     * ok, now do the memory shuffle. this code doesn't use any extra
     *  memory, but might be a bit slower than if we just used two memcpys
     * for the whole thing.
     */
    {
	register const int r = toModify->elementSize;
	size_t numToMove = toModify->nItems - (indx+1);
	size_t ii;
	off_t offset;
	char *dest;

	offset = indx*toModify->elementSize;
	dest = (char *)(toModify->data) + offset;

	/* copy stuff from a[i+1] to a[i] */
	for (ii=0; ii<numToMove; ii++, dest+=toModify->elementSize)
	    memcpy((void *)dest, (void *)(dest+r), r);
    }

    /* do the bookkeeping */
    toModify->nItems -= 1;
    
    return RM_CHILL;
}

/* 
 * PRIVATE: rmArrayNumItems. How many elements are in the array right now?
 */
size_t
rmArrayNumItems(const RMarray *toQuery)
{
    if (RM_ASSERT(toQuery, "rmArrayNumItems error - the input array object is NULL") == RM_WHACKED)
	return -1;
    
    return toQuery->nItems;
}

/* 
 * PRIVATE: rmArraySet. This routine hasn't been tested, and may not be
 * thoroughly complete. It is presently not used anywhere.
 */
/* we might implement this if needed */
RMenum
rmArraySet(RMarray *toModify,
	   size_t indx,
	   const void *newData)
{
    char *dest;
    size_t offset;

    if (RM_ASSERT(toModify, "rmArraySet error - the input RMarray object is NULL.") == RM_WHACKED)
	return RM_WHACKED;

    /* set an item in the array */

    /* first, check to see if the array is long enough  */
    if (indx >= toModify->nItems)
    {

	/* nope, the array is smaller than the indx. What to do next? */

	/* for now, we'll throw an error. The app should be sizing the
	   array big enough to hold all its stuff. */
	char buf[512];
	sprintf(buf,"rmArraySet warning - request to set item #%zd, but there are only %zd items in the array.", indx, toModify->nItems);
	rmWarning(buf);
	return RM_WHACKED;
#if 0
        /*
	 * We could throw and error, but nicer behavior is to resize
	 * the array and make the assignment.
	 */
	while (indx >= toModify->nItems) /* this loop can be dangerous */
	    private_rmArrayRealloc(toModify);
#endif
    }
    offset = indx * toModify->elementSize;
    dest = (char *)(toModify->data) + offset;

    if (newData != NULL)
	memcpy((void *)dest, newData, toModify->elementSize);
    else
	memset((void *)dest, 0, toModify->elementSize);

    return RM_CHILL;
}

/* 
 * PRIVATE: rmArraySort. Sort an array using the system qsort call.
 */
void
rmArraySort(RMarray *a,
	    int (*compareFunc)(const void *a, const void *b))
{
    size_t nItems = rmArrayNumItems(a);
    qsort(a->data, nItems, a->elementSize, compareFunc);
}

/* EOF */
