/****************************************************************************
 * KONOHA COPYRIGHT, LICENSE NOTICE, AND DISCRIMER
 *
 * Copyright (c) 2005-2008, Kimio Kuramitsu <kimio at ynu.ac.jp>
 *           (c) 2008-      Konoha Software Foundation
 * All rights reserved.
 *
 * You may choose one of the following two licenses when you use konoha.
 * See www.konohaware.org/license.html for further information.
 *
 * (1) GNU General Public License 2.0      (with    KONOHA_UNDER_GPL2)
 * (2) Konoha Software Foundation License 1.0
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/* ************************************************************************ */

#include"commons.h"

/* ************************************************************************ */

#ifdef __cplusplus
extern "C" {
#endif

/* ======================================================================== */
/* [barray] */
/* @data */

typedef struct {
	size_t capacity;
} knh_barray_t ;

/* ------------------------------------------------------------------------ */

size_t knh_barray_capacity(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_barray_t *h = ((knh_barray_t*)a);
		return h[-1].capacity;
	}
}

/* ------------------------------------------------------------------------ */

knh_uchar_t *knh_barray_malloc(Ctx *ctx, size_t capacity)
{
	capacity = KNH_SIZE(capacity);
	if(capacity > 0) {
		knh_barray_t *h = (knh_barray_t*)KNH_MALLOC(ctx, capacity + sizeof(knh_barray_t));
		h->capacity = capacity;
		knh_bzero(h+1, capacity);
		return (knh_uchar_t*)(h+1);
	}
	else {
		return NULL;
	}
}

/* ------------------------------------------------------------------------ */

void knh_barray_free(knh_uchar_t *a)
{
	if(a != NULL) {
		knh_barray_t *h = ((knh_barray_t*)a) - 1;
		KNH_FREE(h, h->capacity + sizeof(knh_barray_t));
	}
}

/* ------------------------------------------------------------------------ */

knh_uchar_t *knh_barray_resize(Ctx *ctx, knh_uchar_t *a, size_t newsize)
{
	if(a == NULL) {
		return knh_barray_malloc(ctx, newsize);
	}
	else if(newsize == 0) {
		knh_barray_t *h = ((knh_barray_t*)a) - 1;
		KNH_FREE(h, h->capacity + sizeof(knh_barray_t));
		return NULL;
	}
	newsize = KNH_SIZE(newsize);
	{
		knh_barray_t *h = ((knh_barray_t*)a) - 1, *newh;
		knh_uchar_t *newa = NULL;
		size_t size = h->capacity;
		newh = (knh_barray_t*)KNH_MALLOC(ctx, newsize + sizeof(knh_barray_t));
		newh->capacity = newsize;
		newa = (knh_uchar_t*)(newh + 1);
		if(size > 0) {
			knh_memcpy(newa, a, size);
		}
		if(newsize > size) {
			knh_bzero(&newa[size], newsize - size);
		}
		KNH_FREE(h, h->capacity + sizeof(knh_barray_t));
		return newa;
	}
}

/* ======================================================================== */
/* [oarray] */
/* @data */

typedef struct {
	size_t capacity;
	union {
		struct { knh_short_t op1;  knh_short_t op2; } s;
		size_t opsize;
	};
} knh_oarray_t ;

/* ------------------------------------------------------------------------ */

size_t knh_oarray_capacity(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a);
		return h[-1].capacity;
	}
}

/* ------------------------------------------------------------------------ */

size_t knh_oarray_opsize(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a);
		return h[-1].opsize;
	}
}

/* ------------------------------------------------------------------------ */

void knh_oarray_set_opsize(void *a, knh_short_t opsize)
{
	if(a != NULL) {
		knh_oarray_t *h = ((knh_oarray_t*)a);
		h[-1].opsize = opsize;
	}
}

/* ------------------------------------------------------------------------ */

int knh_oarray_op1(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a);
		return h[-1].s.op1;
	}
}

/* ------------------------------------------------------------------------ */

void knh_oarray_setop1(void *a, knh_short_t op1)
{
	if(a != NULL) {
		knh_oarray_t *h = ((knh_oarray_t*)a);
		h[-1].s.op1 = op1;
	}
}

/* ------------------------------------------------------------------------ */

int knh_oarray_op2(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a);
		return h[-1].s.op2;
	}
}

/* ------------------------------------------------------------------------ */

void knh_oarray_setop2(void *a, knh_short_t op2)
{
	if(a != NULL) {
		knh_oarray_t *h = ((knh_oarray_t*)a);
		h[-1].s.op2 = op2;
	}
}

/* ------------------------------------------------------------------------ */

Object** knh_oarray_malloc(Ctx *ctx, size_t capacity, Object *value)
{
	if(capacity > 0) {
		knh_oarray_t *h = (knh_oarray_t*)KNH_MALLOC(ctx, (capacity * sizeof(Object*)) + sizeof(knh_oarray_t));
		h->capacity = capacity;
		h->opsize = 0;
		{
			Object **a = (Object**)(h+1);
			size_t i = 0;
			for(i = 0; i < capacity; i++) {
				a[i] = value;
			}
			knh_Object_RCplus(value, capacity);
			return a;
		}
	}
	else {
		return NULL;
	}
}

/* ------------------------------------------------------------------------ */

Object **knh_oarray_resize(Ctx *ctx, Object **a, size_t newsize)
{
	if(a == NULL) {
		if(newsize == 0) newsize = 8;
		return knh_oarray_malloc(ctx, newsize, KNH_NULL);
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a) - 1, *newh;
		Object **newa = NULL;
		size_t i, size = h->capacity;
		if (size < newsize) {
			newh = (knh_oarray_t*)KNH_MALLOC(ctx, (sizeof(Object*) * newsize) + sizeof(knh_oarray_t));
			newh->capacity = newsize;
			newh->opsize = h->opsize;
			newa = (Object**)(newh + 1);
			if(size > 0) {
				knh_memcpy(newa, a, (sizeof(Object*) * size));
			}
			for(i = size; i < newsize; i++) {
				KNH_INITv(newa[i], KNH_NULL);
			}
			if(size > 0) {
				KNH_FREE(h, (sizeof(Object*) * size) + sizeof(knh_oarray_t));
			}
			return newa;
		}
		else if(newsize < size) {
			if(newsize > 0) {
				newh = (knh_oarray_t*)KNH_MALLOC(ctx, (sizeof(Object*) * newsize) + sizeof(knh_oarray_t));
				newh->capacity = newsize;
				newh->opsize = h->opsize;
				newa = (Object**)(newh + 1);
				knh_memcpy(newa, a, (sizeof(Object*) * size));
			}
			else {
				DBG2_P("resize to 0");
				newa = NULL;
			}
			for(i = newsize; i < size; i++) {
				knh_Object_RCdec(a[i]);
				if(knh_Object_isRC0(a[i])) {
					knh_Object_free(ctx, a[i]);
				}
			}
			KNH_FREE(h, (sizeof(Object*) * size) + sizeof(knh_oarray_t));
			return newa;
		}
		else { /* size == newsize */
			return a;
		}
	}
}

/* ------------------------------------------------------------------------ */

Object** knh_oarray_grow(Ctx *ctx, Object **a)
{
	if(a == NULL) {
		return knh_oarray_malloc(ctx, 8, KNH_NULL);
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a) - 1, *newh;
		Object **newa = NULL;
		size_t i, size = h->capacity, newsize = h->capacity * 2;
		KNH_ASSERT(size > 0);
		newh = (knh_oarray_t*)KNH_MALLOC(ctx, (sizeof(Object*) * newsize) + sizeof(knh_oarray_t));
		newh->capacity = newsize;
		newh->opsize = h->opsize;
		newa = (Object**)(newh + 1);
		knh_memcpy(newa, a, (sizeof(Object*) * size));
		for(i = size; i < newsize; i++) {
			KNH_INITv(newa[i], KNH_NULL);
		}
		KNH_FREE(h, (sizeof(Object*) * size) + sizeof(knh_oarray_t));
		return newa;
	}
}

/* ------------------------------------------------------------------------ */

Object **knh_oarray_copy(Ctx *ctx, Object **a, size_t s, size_t offset)
{
	if(a == NULL) {
		return NULL;
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a) - 1, *newh;
		if(s + offset > h->capacity) {
			offset = h->capacity - s;
		}
		if(offset == 0) {
			return NULL;
		}
		else {
			Object **newa = NULL;
			size_t i;
			newh = (knh_oarray_t*)KNH_MALLOC(ctx, (sizeof(Object*) * offset) + sizeof(knh_oarray_t));
			newh->capacity = offset;
			newh->opsize = 0;
			newa = (Object**)(newh + 1);
			knh_memcpy(newa, &a[s] , (sizeof(Object*) * offset));
			for(i = 0; i < offset; i++) {
				knh_Object_RCinc(newa[i]);
			}
			return newa;
		}
	}
}

/* ------------------------------------------------------------------------ */

void knh_oarray_traverse(Ctx *ctx, Object **a, f_traverse ftr)
{
	if(a == NULL) {
		return;
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a) - 1;
		size_t i = 0;
		for(i = 0; i < h->capacity; i++) {
			ftr(ctx, a[i]);
		}
		if(IS_SWEEP(ftr) && h->capacity > 0) {
			KNH_FREE(h, (h->capacity * sizeof(Object*)) + sizeof(knh_oarray_t));
		}
	}
}

/* ------------------------------------------------------------------------ */

void knh_oarray_free(Ctx *ctx, knh_Object_t **a)
{
	if(a == NULL) {
		return ;
	}
	else {
		knh_oarray_t *h = ((knh_oarray_t*)a) - 1;
		int i;
		for(i = 0; i < h->capacity; i++) {
			knh_Object_RCdec(a[i]);
			if(knh_Object_isRC0(a[i])) {
				knh_Object_free(ctx, a[i]);
			}
		}
		KNH_FREE(h, (h->capacity * sizeof(Object*)) + sizeof(knh_oarray_t));
	}
}

/* ======================================================================== */
/* [barray] */
/* @data */

typedef struct {
	size_t capacity;
	size_t sizeofeach;
	union {
		struct { knh_short_t op1;  knh_short_t op2; } s;
		size_t opsize;
	};
	void (*farrayinit)(Ctx *, void *a);
	void (*farraytraverse)(Ctx *ctx, void *a, f_traverse ftr);
	int  (*farraycompar)(const void *, const void *);
} knh_array_t ;

/* ------------------------------------------------------------------------ */

size_t knh_array_capacity(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_array_t *h = ((knh_array_t*)a);
		return h[-1].capacity;
	}
}

/* ------------------------------------------------------------------------ */

size_t knh_array_opsize(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_array_t *h = ((knh_array_t*)a);
		return h[-1].opsize;
	}
}

/* ------------------------------------------------------------------------ */

void knh_array_set_opsize(void *a, knh_short_t opsize)
{
	if(a != NULL) {
		knh_array_t *h = ((knh_array_t*)a);
		h[-1].opsize = opsize;
	}
}

/* ------------------------------------------------------------------------ */

int knh_array_op1(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_array_t *h = ((knh_array_t*)a);
		return h[-1].s.op1;
	}
}

/* ------------------------------------------------------------------------ */

void knh_array_set_op1(void *a, knh_short_t op1)
{
	if(a != NULL) {
		knh_array_t *h = ((knh_array_t*)a);
		h[-1].s.op1 = op1;
	}
}

/* ------------------------------------------------------------------------ */

int knh_array_op2(void *a)
{
	if(a == NULL) {
		return 0;
	}
	else {
		knh_array_t *h = ((knh_array_t*)a);
		return h[-1].s.op2;
	}
}

/* ------------------------------------------------------------------------ */

void knh_array_set_op2(void *a, knh_short_t op2)
{
	if(a != NULL) {
		knh_array_t *h = ((knh_array_t*)a);
		h[-1].s.op2 = op2;
	}
}

/* ------------------------------------------------------------------------ */

void *knh_array_malloc(Ctx *ctx, size_t capacity, size_t sizeofeach, void (*farrayinit)(Ctx *, void *), void (*farraytraverse)(Ctx*, void*, f_traverse))
{
	if(capacity > 0) {
		knh_array_t *h = (knh_array_t*)KNH_MALLOC(ctx, (capacity * sizeofeach) + sizeof(knh_array_t));
		h->capacity = capacity;
		h->sizeofeach = sizeofeach;
		h->farrayinit = farrayinit;
		h->farraytraverse = farraytraverse;
		h->opsize = 0;
		h->farraycompar = NULL;
		{
			void *a = (void*)(h+1);
			size_t i = 0;
			for(i = 0; i < capacity; i++) {
				farrayinit(NULL, a + (i * sizeofeach));
			}
			return a;
		}
	}
	else {
		return NULL;
	}
}

/* ------------------------------------------------------------------------ */

void *knh_array_resize(Ctx *ctx, void *a, size_t newsize)
{
	knh_array_t *h = ((knh_array_t*)a) - 1, *newh = NULL;
	void *newa = NULL;
	size_t i, size = h->capacity;
	if (size < newsize) {
		newh = (knh_array_t*)KNH_MALLOC(ctx, (h->sizeofeach * newsize) + sizeof(knh_array_t));
		newh->capacity = newsize;
		newh->sizeofeach = h->sizeofeach;
		newh->farrayinit = h->farrayinit;
		newh->farraytraverse = h->farraytraverse;
		newh->opsize = h->opsize;
		newh->farraycompar = h->farraycompar;
		newa = newh + 1;
		if(size > 0) {
			knh_memcpy(newa, a, (newh->sizeofeach * size));
		}
		for(i = size; i < newsize; i++) {
			newh->farrayinit(NULL, newa + (i * newh->sizeofeach));
		}
		if(size > 0) {
			KNH_FREE(h, (h->sizeofeach * size) + sizeof(knh_array_t));
		}
		return newa;
	}
	else if(newsize < size) {
		if(newsize > 0) {
			newh = (knh_array_t*)KNH_MALLOC(ctx, (h->sizeofeach * newsize) + sizeof(knh_array_t));
			newh->capacity = newsize;
			newh->sizeofeach = h->sizeofeach;
			newh->farrayinit = h->farrayinit;
			newh->farraytraverse = h->farraytraverse;
			newh->opsize = h->opsize;
			newh->farraycompar = h->farraycompar;
			newa = newh + 1;
			knh_memcpy(newa, a, (newh->sizeofeach * size));
		}
		else {
			DBG2_P("resize to 0");
			newa = NULL;
		}
		for(i = newsize; i < size; i++) {
			newh->farraytraverse(ctx, a + (i * newh->sizeofeach), knh_Object_RCsweep);
		}
		KNH_FREE(h, (h->sizeofeach * size) + sizeof(knh_array_t));
		return newa;
	}
	else { /* size == newssiz */
		return a;
	}
}

/* ------------------------------------------------------------------------ */

void knh_array_clear(Ctx *ctx, void *a)
{
	if(a == NULL) {
		return ;
	}
	else {
		knh_array_t *h = ((knh_array_t*)a) - 1;
		size_t i;
		for(i = 0; i < h->capacity; i++) {
			h->farrayinit(ctx, a + (i * h->sizeofeach));
		}
	}
}

/* ------------------------------------------------------------------------ */

void *knh_array_traverse(Ctx *ctx, void *a, f_traverse ftr)
{
	if(a == NULL) {
		return NULL;
	}
	else {
		knh_array_t *h = ((knh_array_t*)a) - 1;
		size_t i = 0;
		for(i = 0; i < h->capacity; i++) {
			h->farraytraverse(ctx, a + (i * h->sizeofeach), ftr);
		}
		if(IS_SWEEP(ftr) && h->capacity > 0) {
			KNH_FREE(h, (h->capacity * h->sizeofeach) + sizeof(knh_array_t));
			return NULL;
		}
		return a;
	}
}

/* ======================================================================== */
/* [sort] */

void* knh_array_fcompare(void *a)
{
	if(a != NULL) {
		knh_array_t *h = ((knh_array_t*)a);
		return h[-1].farraycompar;
	}
	return NULL;
}

/* ------------------------------------------------------------------------ */

void knh_array_set_fcompare(void *a, int (*farraycompar)(const void *, const void *))
{
	if(a != NULL) {
		knh_array_t *h = ((knh_array_t*)a);
		h[-1].farraycompar = farraycompar;
	}
}

/* ------------------------------------------------------------------------ */

void knh_array_sort(void *a)
{
	if(a != NULL) {
		knh_array_t *h = ((knh_array_t*)a) - 1;
		if(h->farraycompar != NULL) {
			knh_sort(a, h->capacity, h->sizeofeach, h->farraycompar);
		}
		else {
			DBG_P("cannot sort !!");
		}
	}
}

/* ------------------------------------------------------------------------ */

#ifdef __cplusplus
}
#endif
