/*	Copyright (C) 2003-2008 Free Electron Organization
	Any use of this software requires a license.  If a valid license
	was not distributed with this file, visit freeelectron.org. */

#include "data.pmh"

#define FE_RA_START_CNT	1
#define FE_RA_GROW_FACTOR 2.0
#define FE_RA_GROW_COUNT(c) ((IWORD)((((IWORD)(c * FE_RA_GROW_FACTOR))==c)? \
							(c + 1):(c * FE_RA_GROW_FACTOR)))

namespace fe
{

RecordArraySB::RecordArraySB(void)
{
	m_ppSB = NULL;
	m_pSN = NULL;
	m_allocated = 0;
	m_used = 0;
	m_weak = FALSE;
}

RecordArraySB::RecordArraySB(sp<LayoutSB> spLayout)
{
	m_ppSB = NULL;
	m_pSN = NULL;
	m_allocated = 0;
	m_used = 0;
	m_weak = FALSE;
	setLayout(spLayout);
}

RecordArraySB::RecordArraySB(const RecordArraySB &other) : Counted()
{
	copy(other);
}

RecordArraySB::RecordArraySB(sp<LayoutSB> spLayout, UWORD aCount)
{
	m_ppSB = NULL;
	m_pSN = NULL;
	m_allocated = 0;
	m_used = 0;
	m_weak = FALSE;
	setLayout(spLayout);

	if(!spLayout->locked())
	{
		spLayout->lock();
	}

	// create its own store so that it will delete the memory when necessary
	sp<StoreI> spStore(new SegmentStore());
	spStore->setLayout(spLayout);

	sp<Scope> spScope = spLayout->scope();

	void *pArrayRoot = spStore->createSB(aCount);
	for(UWORD i = 0;i < aCount; i++)
	{
		WeakRecordSB record;
		record.m_hpLayout = spLayout;
		record.m_pStateBlock = pArrayRoot;
		spLayout->constructAttributes(record.data());
		spScope->assignIDNumber(record);

		//* NOTE RA add does Store acquire, so release refs done by createSB
		add(record);
		spStore->releaseSB(record);

#if FE_SCOPE_SUPPORT_WATCHERS
		spScope->watch(record);
#endif
		pArrayRoot = (void *)(((UWORD)pArrayRoot) + spStore->skipSizeSB());
	}
}

RecordArraySB::~RecordArraySB(void)
{
	clear();
}

void RecordArraySB::setLayout(const sp<LayoutSB>& rspLayout)
{
	m_spLayout = rspLayout;

	U32 index = m_spLayout->scope()->serialIndex();
	if(!m_spLayout->locked()) { m_spLayout->lock(); }
	m_serialOffset = m_spLayout->offsetTable()[index];
	if(m_weak && m_serialOffset<0)
	{
		feX(e_refused,"RecordArray::setLayout",
				"weak array%s requires serial number in layout%s",
#if FE_COUNTED_TRACK
				(" \""+name()+"\"").c_str(),
				(" \""+layout()->name()+"\"").c_str()
#else
				"",""
#endif
				);
	}
}

void RecordArraySB::clear(void)
{
	for(IWORD i = 0;i < m_used; i++)
	{
		releaseSB(i);
	}
	if(m_ppSB)
	{
		deallocate((void *)m_ppSB);
		m_ppSB = NULL;
	}
	if(m_pSN)
	{
		deallocate((void *)m_pSN);
		m_pSN = NULL;
	}
	m_allocated = 0;
	m_used = 0;

#ifdef FE_RA_INDEX
	m_sb_map.clear();
#endif

	// need to keep layout since RAs in a RG are assumed to be valid even if
	// empty
	// m_spLayout=NULL;
}

void RecordArraySB::copy(const RecordArraySB &other)
{
	setLayout(other.m_spLayout);
	m_allocated = other.m_allocated;
	m_used = other.m_used;
	IWORD allocSz = m_allocated * sizeof(void *);
	m_ppSB = (void **)allocate(allocSz);
	memcpy((void *)m_ppSB, (void *)other.m_ppSB, allocSz);
	if(m_weak)
	{
		allocSz = m_allocated * sizeof(IWORD);
		m_pSN = (IWORD *)allocate(allocSz);
		if(other.m_weak)
		{
			memcpy((void *)m_pSN, (void *)other.m_pSN, allocSz);
		}
	}
	for(IWORD i = 0;i < m_used; i++)
	{
		acquireSB(i);
	}
	if(m_weak && !other.m_weak)
	{
		FEASSERT(m_pSN);
		RecordSB record;
		for(IWORD i = 0;i < m_used; i++)
		{
			record.set(m_ppSB[i]);
			m_pSN[i]=readSerialNumber(i);
		}
	}
#ifdef FE_RA_INDEX
	m_sb_map = other.m_sb_map;
#endif
}

RecordArraySB &RecordArraySB::operator=(const RecordArraySB &other)
{
	if(this != &other)
	{
		clear();
		copy(other);
	}
	return *this;
}

bool RecordArraySB::add(sp<RecordArraySB> spRA, IWORD start, IWORD size)
{
	FEASSERT(m_allocated >= m_used);

	if(m_used && m_spLayout.isValid())
	{
		if(m_spLayout != spRA->layout())
		{
			return false;
		}
	}
	else
	{
		setLayout(spRA->layout());
	}

	IWORD needed = m_used + size;
	while(m_allocated < needed)
	{
		grow();
	}

	RecordArraySB *pRA = spRA.raw();

	IWORD j = start;
	for(IWORD i = m_used; i < needed; i++, j++)
	{
		m_ppSB[i] = pRA->m_ppSB[j];
		acquireSB(i);
#ifdef FE_RA_INDEX
		m_sb_map[m_ppSB[i]] = i;
#endif
	}

	I32 was=m_used;
	m_used += size;

	if(m_weak)
	{
		j = start;
		FEASSERT(m_pSN);
		if(pRA->m_weak)
		{
			FEASSERT(pRA->m_pSN);
			for(IWORD i = was; i < needed; i++, j++)
			{
				m_pSN[i] = pRA->m_pSN[j];
			}
		}
		else
		{
			for(IWORD i = was; i < needed; i++, j++)
			{
				m_pSN[i]=readSerialNumber(i);
			}
		}
	}

	return true;
}

bool RecordArraySB::add(const RecordSB &record, IWORD *index)
{
	if(find(record)) { return false; }

	FEASSERT(m_allocated >= m_used);

	if(m_used && m_spLayout.isValid())
	{
		if(m_spLayout != record.layout())
		{
			return false;
		}
	}
	else
	{
		setLayout(record.layout());
	}

	if(m_allocated == m_used)
	{
		grow();
	}

	if(index)
	{
		*index = m_used;
	}

	m_ppSB[m_used] = record.data();
	acquireSB(m_used);
#ifdef FE_RA_INDEX
	m_sb_map[m_ppSB[m_used]] = m_used;
#endif
	m_used++;


	if(m_weak)
	{
		FEASSERT(m_pSN);
//		feLog("RecordArray::add %d SN %d\n",
//				m_used-1,readSerialNumber(m_used-1));
		m_pSN[m_used-1]=readSerialNumber(m_used-1);
	}


	return true;
}

bool RecordArraySB::addCreate(void)
{
	// since we are creating the record, we can avoid the find

	FEASSERT(m_allocated >= m_used);

	if(!m_spLayout.isValid())
	{
		return false;
	}

	if(m_allocated == m_used)
	{
		grow();
	}

	RecordSB record = m_spLayout->createRecord();

	m_ppSB[m_used] = record.data();
	acquireSB(m_used);
#ifdef FE_RA_INDEX
	m_sb_map[m_ppSB[m_used]] = m_used;
#endif
	m_used++;


	if(m_weak)
	{
		feX("RecordArraySB::addCreate",
			"useless call when array is weak...record will instantly destruct");
		return false;
	}


	return true;
}

bool RecordArraySB::find(const RecordSB &record)
{
#ifdef FE_RA_INDEX
	t_sb_map::iterator i_r = m_sb_map.find(record.data());
	return (i_r != m_sb_map.end());
#else

	for(IWORD i = 0; i < m_used; i++)
	{
		if(m_ppSB[i] == record.data())
		{
			return true;
		}
	}
	return false;
#endif
}

void RecordArraySB::prune(void)
{
	if(m_weak)
	{
		for(IWORD i = m_used-1; i >= 0; i--)
		{
			if(!m_ppSB[i] || m_pSN[i]!=readSerialNumber(i))
			{
				remove(i);
			}
		}
	}
}

bool RecordArraySB::remove(const RecordSB &record, IWORD *index)
{
#ifdef FE_RA_INDEX
	t_sb_map::iterator i_r = m_sb_map.find(record.data());
	if(i_r != m_sb_map.end())
	{
		int i = i_r->second;
		if(m_ppSB[i] == record.data())
		{
			if(index)
			{
				*index = i;
			}
			remove(i);
			return true;
		}
		else
		{
			feX("impossibility");
		}
	}
	return false;
#else
	for(IWORD i = 0; i < m_used; i++)
	{
		if(m_ppSB[i] == record.data())
		{
			if(index)
			{
				*index = i;
			}
			remove(i);
			return true;
		}
	}
	return false;
#endif
}

void RecordArraySB::remove(IWORD index)
{
#ifdef FE_RA_INDEX
	m_sb_map.erase(m_ppSB[index]);
#endif
	releaseSB(index);
	if(m_weak)
	{
		FEASSERT(m_pSN);
		m_pSN[index]= -1;
	}
	m_used--;
	if(m_used < 0)
	{
		feX("impossibility");
	}
	if(m_used && index < m_used)
	{
		m_ppSB[index] = m_ppSB[m_used];
#ifdef FE_RA_INDEX
		m_sb_map.erase(m_ppSB[m_used]);
		m_sb_map[m_ppSB[index]] = index;
#endif
		if(m_weak)
		{
			m_pSN[index] = m_pSN[m_used];
		}
	}
}

void RecordArraySB::grow(void)
{
	if(m_allocated)
	{
		m_allocated = FE_RA_GROW_COUNT(m_allocated);
		m_ppSB = (void **)reallocate((void *)m_ppSB,
			m_allocated * sizeof(void *) );
		if(m_weak)
		{
			FEASSERT(m_pSN);
			m_pSN = (IWORD *)reallocate((void *)m_pSN,
				m_allocated * sizeof(IWORD) );
		}
	}
	else
	{
		FEASSERT(!m_ppSB);
		m_allocated = FE_RA_START_CNT;
		m_ppSB = (void **)allocate(m_allocated * sizeof(void *));
		if(m_weak)
		{
			FEASSERT(!m_pSN);
			m_pSN = (IWORD *)allocate(m_allocated * sizeof(IWORD) );
		}
	}
	if(!m_ppSB || (m_weak && !m_pSN))
	{
		feX("fe::RecordArray::grow","could not allocate memory");
	}
}

IWORD PtrRecordArraySBInfo::output(std::ostream &ostrm, void *instance,
		t_serialMode mode)
{
	feX(e_unsupported,
		"PtrRecordArrayInfo::output",
		"record array output should be done via fe::Stream");
	return 0;
}

void PtrRecordArraySBInfo::input(std::istream &istrm, void *instance,
		t_serialMode mode)
{
	feX(e_unsupported,
		"PtrRecordArrayInfo::input",
		"record array input should be done via fe::Stream");
}

IWORD PtrRecordArraySBInfo::iosize(void)
{
	return c_implicit;
}

bool PtrRecordArraySBInfo::getConstruct(void)
{
	return true;
}

typedef sp<RecordArraySB> RecordArraySBPtr;

void PtrRecordArraySBInfo::construct(void *instance)
{
	new(instance)RecordArraySBPtr;
}

void PtrRecordArraySBInfo::destruct(void *instance)
{
	((RecordArraySBPtr *)instance)->~RecordArraySBPtr();
}


} /* namespace */
