// Copyright (C) 2004-2009 Christoph Kubisch & Eike Decker
// This file is part of the "Luxinia Engine".
// For conditions of distribution and use, see copyright notice in luxiniapublicsdk.txt

#include <luxcore/contvector.h>
#include <luxcore/memorygeneric.h>
#include <string.h>

void	lxContVector_reserve(lxContVector_t *cv,uint n)
{
	const uint 
		e = cv->elemsize,
		s = lxContVector_size(cv),
		c = lxContVector_capacity(cv);

	if (c >= n) return;
	if (c == 0)
	{
		if (cv->alignsize){
			cv->beg = (byte*)lxMemGenZallocAligned(e * n, cv->alignsize);
		}
		else{
			cv->beg = (byte*)lxMemGenZalloc(e * n);
		}
	}
	else
	{
		if (cv->alignsize){
			cv->beg = (byte*)lxMemGenReallocAligned(cv->beg, n*e,  cv->alignsize, c*e);
		}
		else{
			cv->beg = (byte*)lxMemGenRealloc(cv->beg, e * n, c*e);
		}
	}
	cv->end = cv->beg + s*e;
	cv->eos = cv->beg + n*e;
	LUX_ASSERT(lxContVector_capacity(cv) >= n);
	LUX_ASSERT(lxContVector_size(cv) == s);
}

void ContVector_prepGrowth(lxContVector_t *cv, uint delta)
{
	const uint s = lxContVector_size(cv);
	const uint smallThreshold = CONTVECTOR_SMALL;
	if (s < smallThreshold)
	{
		lxContVector_reserve(cv,LX_MAX(smallThreshold, delta));
	}
	else
	{
		const uint multiply = 3;
		const uint divide = 2;
		const uint suggestedSize = (s * multiply) / divide;
		lxContVector_reserve(cv,LX_MAX(s + delta, suggestedSize));
	}
}

lxContVector_t lxContVector_copy(lxContVector_t* cv)
{
	lxContVector_t	ncv = lxContVector_new(cv->elemsize);
	lxContVector_reserve(&ncv,lxContVector_size(cv));
	memcpy(ncv.beg, cv->beg, ContVector_memused(cv));

	return ncv;
}

void	lxContVector_pushBack(lxContVector_t *cv, const void *data)
{
	if (cv->end == cv->eos) 
	{
		ContVector_prepGrowth(cv,1);
	}

	memcpy(cv->end,data,cv->elemsize);
}

void	lxContVector_popBack(lxContVector_t *cv)
{
	LUX_ASSERT(!lxContVector_isEmpty(cv));
	cv->end--;
}

void	lxContVector_clear(lxContVector_t *cv){
	if(!lxContVector_isEmpty(cv)){
		if (cv->alignsize){
			lxMemGenFreeAligned(cv->beg,lxContVector_size(cv)*cv->elemsize);
		}
		else{
			lxMemGenFree(cv->beg,lxContVector_size(cv)*cv->elemsize);
		}

		cv->end = cv->beg;
		cv->eos = cv->beg;
	}
}

LUX_INLINE void	lxContVector_insertMany(lxContVector_t *cv, uint idx, const void *data, uint cnt)
{
	const uint 
		e = cv->elemsize,
		s = lxContVector_size(cv),
		c = lxContVector_capacity(cv);

	const uint
		bytes = e*cnt;

	if (s+cnt > c) 
	{
		ContVector_prepGrowth(cv,cnt);
	}

	{
		byte*	idxpos = (byte*)lxContVector_at(cv,idx);

		if (idx < s){
			memmove(idxpos+bytes,idxpos,(s-idx)*e);
		}
		memcpy(idxpos,data,bytes);
		cv->end += (e*cnt);
	}
}

void	lxContVector_insert(lxContVector_t *cv, uint idx, const void *data)
{
	lxContVector_insertMany(cv,idx,data,1);
}

void	lxContVector_insertRepeat(lxContVector_t *cv, uint idx, void *data, uint cnt)
{
	const uint 
		e = cv->elemsize,
		s = lxContVector_size(cv),
		c = lxContVector_capacity(cv);

	const uint
		bytes = e*cnt;

	if (s+cnt > c) 
	{
		ContVector_prepGrowth(cv,cnt);
	}

	{
		byte*	idxpos = (byte*)lxContVector_at(cv,idx);
		byte*	endpos = idxpos+bytes;
		
		if (idx < s){
			memmove(idxpos+bytes,idxpos,(s-idx)*e);
		}
		for (; idxpos < endpos; idxpos += e){
			memcpy(idxpos,data,e);
		}
		
		cv->end = idxpos;
	}
}

LUX_INLINE void	lxContVector_removeMany(lxContVector_t *cv, uint idx, uint cnt)
{
	const uint 
		e = cv->elemsize,
		s = lxContVector_size(cv),
		c = lxContVector_capacity(cv);

	const uint
		ncnt = LX_MIN(cnt,(s-idx));

	const uint
		bytes = e*ncnt;

	byte*	idxpos = (byte*)lxContVector_at(cv,idx);
	byte*	frompos = idxpos+bytes;

	if (frompos < cv->end){
		memmove(idxpos,frompos,bytes);
	}

	cv->end -= bytes;
}

void	lxContVector_remove(lxContVector_t *cv, uint idx)
{
	lxContVector_removeMany(cv,idx,1);
}



void	lxContVector_shrink(lxContVector_t *cv)
{
	const uint 
		e = cv->elemsize,
		s = lxContVector_size(cv),
		c = lxContVector_capacity(cv);

	if (s == 0) return;

	if (cv->alignsize){
		cv->beg = (byte*)lxMemGenReallocAligned(cv->beg, s*e, cv->alignsize, c*e);
	}
	else{
		cv->beg = (byte*)lxMemGenRealloc(cv->beg,s * e, c*e);
	}

	cv->end = cv->beg + s*e;
	cv->eos = cv->end;
	LUX_ASSERT(lxContVector_capacity(cv) >= s);
	LUX_ASSERT(lxContVector_size(cv) == s);
}

void	lxContVector_resize(lxContVector_t *cv,uint cnt, const void *fill)
{
	const uint 
		e = cv->elemsize,
		s = lxContVector_size(cv),
		c = lxContVector_capacity(cv);

	if (c == cnt) return;

	lxContVector_reserve(cv,cnt);
	if (cnt > s){
		byte* curend = cv->end;
		byte* end = cv->eos;
		const byte* LUX_RESTRICT from = (const byte*)fill;

		for (; curend < end; curend += e){
			memcpy(curend,from,e);
		}
		cv->end = end;
	}
}
