/*	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"

namespace fe
{
namespace data
{

BinaryReader::BinaryReader(sp<Scope> spScope)
{
	m_spScope = spScope;
	m_spRecordGroupType =
		m_spScope->typeMaster()->lookupType<sp<RecordGroup> >();
	m_spRecordArrayType =
		m_spScope->typeMaster()->lookupType<sp<RecordArray> >();
	m_spRecordType =
		m_spScope->typeMaster()->lookupType<Record>();
	m_spWeakRecordType =
		m_spScope->typeMaster()->lookupType<WeakRecord>();
	reset();
}

BinaryReader::~BinaryReader(void)
{
}

void BinaryReader::reset(void)
{
	m_rgs.clear();
	m_ras.clear();
	m_rs.clear();
	m_wiringList.clear();
	m_wkWiringList.clear();
	Record unset_record;
	m_rs.push_back(unset_record); // index 0 for unset record
	m_rgs[0] = NULL; // index 0 for unset recordgroup
	m_ras[0] = NULL; // index 0 for unset recordarray
	m_attrInfos.clear();
}

sp<RecordGroup> BinaryReader::input(std::istream &istrm)
{
	for(U8 code = getCode(istrm); code != e_end; code = getCode(istrm))
	{
		switch(code)
		{
			case e_info:
				readInfo(istrm);
				break;
			case e_attribute:
				readAttribute(istrm);
				break;
			case e_layout:
				readLayout(istrm);
				break;
			case e_state:
				readState(istrm);
				break;
			case e_group:
				readRecordGroup(istrm);
				break;
			default:
				feX(e_invalidFile,
					"BinaryReader::input",
					"corrupt input stream");
		}
	}

	wireRecords();
	recordGroupsToArrays();

	sp<RecordGroup> spRG;
	if(m_rgs.size() >= 1)
	{
		spRG = m_rgs[1];
	}
	reset();
	return spRG;
}

U8 BinaryReader::getCode(std::istream &istrm)
{
#ifdef DOCCODE
	String code_doc;
	fe::input(istrm, code_doc);
#endif
	U8 code;
	fe::input(istrm, code);
	return code;
}

void BinaryReader::readState(std::istream &istrm)
{
	// INPUT: layout id
	I32 layout_id;
	fe::input(istrm, layout_id);

	if(m_layoutInfos.find(layout_id) == m_layoutInfos.end())
	{
		feX(e_cannotFind,
			"BinaryReader::readArray",
			"corrupt input stream");
	}
	LayoutInfo &layout_info = m_layoutInfos[layout_id];

	// INPUT: state block count
	U32 count;
	fe::input(istrm, count);

	for(U32 i = 0; i < count; i++)
	{
		// INPUT: state block
		readRecord(istrm, layout_info);
	}
}

void BinaryReader::wireRecords(void)
{
	for(t_wiring::iterator it = m_wiringList.begin();
		it != m_wiringList.end(); it++)
	{
		if((unsigned int)it->m_id >= m_rs.size())
		{
			feX(e_cannotFind,
				"BinaryReader::wireRecords"
				"stream corrupt");
		}
		it->m_aRecord(it->m_record) = m_rs[it->m_id];
	}
	for(t_wk_wiring::iterator it = m_wkWiringList.begin();
		it != m_wkWiringList.end(); it++)
	{
		if((unsigned int)it->m_id >= m_rs.size())
		{
			feX(e_cannotFind,
				"BinaryReader::wireRecords"
				"stream corrupt");
		}
		it->m_aRecord(it->m_record) = m_rs[it->m_id];
	}
}

void BinaryReader::recordGroupsToArrays(void)
{
	for(t_id_ra::iterator i_ra = m_ras.begin(); i_ra != m_ras.end(); i_ra++)
	{
		if(i_ra->first == 0) { continue; }
		sp<RecordArray> spRA = i_ra->second;
		sp<RecordGroup> spRG = m_rgs[i_ra->first];

		RecordGroup::iterator i_rg = spRG->begin();
		if(i_rg == spRG->end()) { return; }
		sp<RecordArray> spRGRA(*i_rg);
		for(int i = 0; i < spRGRA->length(); i++)
		{
			spRA->add(spRGRA->getRecord(i));
		}
	}
}

void BinaryReader::readRecord(std::istream &istrm, LayoutInfo &layout_info)
{
	Record record = m_spScope->createRecord(layout_info.m_spLayout);
	m_rs.push_back(record);
	for(std::vector<AttributeInfo>::iterator it =
			layout_info.m_attributeInfos.begin();
			it != layout_info.m_attributeInfos.end(); it++)
	{
		if(it->m_spAttribute.isValid())
		{
			void *instance = record.rawAttribute(it->m_index);
			sp<BaseType> spBT = it->m_spAttribute->type();
			if(spBT == m_spRecordGroupType)
			{
				// INPUT: record group id
				U32 rgid;
				fe::input(istrm, rgid);

				if(rgid != 0)
				{
					sp<RecordGroup> *pspRG;
					pspRG = reinterpret_cast<sp<RecordGroup> *>(instance);
					sp<RecordGroup> spRG = m_rgs[rgid];
					if(!spRG.isValid())
					{
						spRG = new RecordGroup();
						m_rgs[rgid] = spRG;
					}
					*pspRG = spRG;
				}
			}
			else if(spBT == m_spRecordArrayType)
			{
				// INPUT: record array id
				U32 raid;
				fe::input(istrm, raid);

				if(raid != 0)
				{
					sp<RecordArray> *pspRA;
					pspRA = reinterpret_cast<sp<RecordArray> *>(instance);

					sp<RecordGroup> spRG = m_rgs[raid];
					sp<RecordArray> spRA = m_ras[raid];

					if(!spRG.isValid())
					{
						spRG = new RecordGroup();
						m_rgs[raid] = spRG;
					}

					if(!spRA.isValid())
					{
						spRA = new RecordArray();
						m_ras[raid] = spRA;
					}

					*pspRA = spRA;
				}
			}
			else if(spBT == m_spRecordType)
			{
				// INPUT: record id
				U32 rid;
				fe::input(istrm, rid);

				if(rid != 0)
				{
					RecordWiringInfo wiring;
					wiring.m_record = record;
					wiring.m_aRecord.setup(m_spScope,it->m_spAttribute->name());
					wiring.m_id = rid;
					m_wiringList.push_back(wiring);
				}

			}
			else if(spBT == m_spWeakRecordType)
			{
				// INPUT: record id
				U32 rid;
				fe::input(istrm, rid);

				if(rid != 0)
				{
					WeakRecordWiringInfo wiring;
					wiring.m_record = record;
					wiring.m_aRecord.setup(m_spScope,it->m_spAttribute->name());
					wiring.m_id = rid;
					m_wkWiringList.push_back(wiring);
				}

			}
			else if(spBT->getInfo().isValid())
			{
				spBT->getInfo()->input(istrm, instance,
						BaseType::Info::e_binary);
			}
			else
			{
				skip(istrm, it->m_skipsize);
			}
		}
		else
		{
			skip(istrm, it->m_skipsize);
		}
	}
}

void BinaryReader::skip(std::istream &istrm, int skipsize)
{
	if(skipsize == BaseType::Info::c_implicit)
	{
		feX("BinaryReader::skip",
			"cannot skip implicit");
	}
	else if(skipsize == BaseType::Info::c_explicit)
	{
		// INPUT: skipsize
		U32 explicit_skipsize;
		fe::input(istrm, explicit_skipsize);
		fe::skip(istrm, explicit_skipsize);
	}
	else
	{
		fe::skip(istrm, skipsize);
	}
}

void BinaryReader::readRecordGroup(std::istream &istrm)
{
	// INPUT: rg id
	int rgid;
	fe::input(istrm, rgid);
	sp<RecordGroup> spRG = m_rgs[rgid];
	if(!spRG.isValid())
	{
		spRG = new RecordGroup();
		m_rgs[rgid] = spRG;
	}

	// INPUT: record count
	U32 count;
	fe::input(istrm, count);

	for(U32 i = 0; i < count; i++)
	{
		// INPUT: record id
		U32 rid;
		fe::input(istrm, rid);
		if(rid >= m_rs.size())
		{
			feX("BinaryReader::readRecordGroup",
				"input record group w/o having input necessary records");
		}
		spRG->add(m_rs[rid]);
	}
}

void BinaryReader::readAttribute(std::istream &istrm)
{
	AttributeInfo info;

	// INPUT: attribute name
	fe::input(istrm, info.m_name);

	// INPUT: typename count
	U32 typename_count;
	fe::input(istrm, typename_count);
	for(UWORD j = 0; j < typename_count; j++)
	{
		// INPUT: typename
		String type_name;
		fe::input(istrm, type_name);
		info.m_typenames.push_back(type_name);
	}
	// INPUT: type size
	I32 skipsize;
	fe::input(istrm, skipsize);
	info.m_skipsize=skipsize;

	sp<BaseType> spBT;
	String tstr;
	String matched_typename;
	for(std::list<String>::iterator it = info.m_typenames.begin();
		it != info.m_typenames.end(); it++)
	{
		// lookup type for attribute
		spBT = m_spScope->typeMaster()->lookupName(*it);
		tstr.cat(*it);
		tstr.cat(" ");
		if(spBT.isValid())
		{
			matched_typename = *it;
			break;
		}
	}
	if(spBT.isValid())
	{
		sp<Attribute> spAttribute;
		// look for attribute
		spAttribute = m_spScope->findAttribute(info.m_name);
		if(spAttribute.isValid())
		{
			// type check existing attribute
			if(spAttribute->type() != spBT)
			{
				feX(e_typeMismatch,
					"BinaryReader::readLayout",
					"type mismatch for \'%s\'",
					info.m_name.c_str());
			}
		}
		else
		{
			// support new attribute
			spAttribute = m_spScope->support(info.m_name, matched_typename);
		}
		// check for local help
		if(spAttribute->type()->getInfo().isValid())
		{
			// check serialized type sizes
			if(spAttribute->type()->getInfo()->iosize() == info.m_skipsize)
			{
				info.m_spAttribute = spAttribute;
			}
			else
			{
				feX(e_typeMismatch,
					"BinaryReader::readAttribute",
					"attribute serialized size mismatch for \'%s\'",
					info.m_name.c_str());
			}
		}
	}
	else
	{
		feX(e_cannotFind,
			"BinaryReader::readAttribute",
			"cannot find type \'%s\'", tstr.c_str());
	}

	m_attrInfos.push_back(info);
}

void BinaryReader::readInfo(std::istream &istrm)
{
	// INPUT: version
	U32 version;
	fe::input(istrm, version);
	if(version != FE_SERIAL_VERSION)
	{
		feX(e_invalidFile,
			"BinaryReader::readInfo",
			"version mismatch");
	}
}

void BinaryReader::readLayout(std::istream &istrm)
{
	// INPUT: layout id
	U32 id;
	fe::input(istrm, id);
	LayoutInfo &layout_info = m_layoutInfos[id];
	if(layout_info.m_spLayout.isValid())
	{
		feX(e_invalidFile,
			"BinaryReader::readLayout",
			"layout already read");
	}

	// INPUT: layout name
	String name;
	fe::input(istrm, name);
	sp<Layout> spL = m_spScope->lookupLayout(name);

	// INPUT: attribute count
	U32 attribute_count;
	fe::input(istrm, attribute_count);

	layout_info.m_attributeInfos.resize(attribute_count);
	for(UWORD i = 0; i < attribute_count; i++)
	{
		// INPUT: attribute id
		U32 attribute_id;
		fe::input(istrm, attribute_id);
		// id indexing starts at 1
		layout_info.m_attributeInfos[i] = m_attrInfos[attribute_id-1];
	}

	// if no matching layout, create one
	if(!spL.isValid())
	{
		spL = m_spScope->declare(name);
	}

	// if layout is not locked, populate
	//if(!spL->locked())
	{
		for(UWORD i = 0; i < layout_info.m_attributeInfos.size(); i++)
		{
			// populate
			spL->populate(layout_info.m_attributeInfos[i].m_spAttribute);
		}
	}

	// set layout info for actual layout
	spL->initialize();
	//if(!spL->locked()){ spL->lock(); }

	for(UWORD i = 0; i < layout_info.m_attributeInfos.size(); i++)
	{
		AttributeInfo &tinfo = layout_info.m_attributeInfos[i];
		sp<Attribute> spAttribute = tinfo.m_spAttribute;
		// look for attribute
		spAttribute = m_spScope->findAttribute(tinfo.m_name, tinfo.m_index);
		if(spAttribute.isValid())
		{
			// check if layout does not have has attribute
			if(!spL->checkAttribute(tinfo.m_index))
			{
				tinfo.m_spAttribute = NULL;
			}
		}
		else
		{
			if(tinfo.m_skipsize == BaseType::Info::c_implicit)
			{
				feX(e_typeMismatch,
					"BinaryReader::readLayout",
					"cannot skip variable size input attribute \'%s\'",
					tinfo.m_name.c_str());
			}
		}
	}
	layout_info.m_spLayout = spL;
}



} /* namespace */
} /* namespace */

