////////////////////////////////////////////////////////////////////
// PLY.cpp
//
// Copyright 2023 cDc@seacave
// Distributed under the Boost Software License, Version 1.0
// (See http://www.boost.org/LICENSE_1_0.txt)

#include "Common.h"
#include "PLY.h"

using namespace SEACAVE;


// D E F I N E S ///////////////////////////////////////////////////

#define NO_OTHER_PROPS  -1

#define DONT_STORE_PROP  0
#define STORE_PROP       1

#define OTHER_PROP       0
#define NAMED_PROP       1

#define abort_ply(...)   { VERBOSE(__VA_ARGS__); exit(-1); }


// S T R U C T S ///////////////////////////////////////////////////

const char* const PLY::type_names[] = {
	"invalid", "int8", "int16", "int32", "uint8", "uint16", "uint32", "float32", "float64",
};

const char* const PLY::old_type_names[] = {
	"invalid", "char", "short", "int", "uchar", "ushort", "uint", "float", "double",
};

const int PLY::ply_type_size[] = {
	0, 1, 2, 4, 1, 2, 4, 4, 8
};

const PLY::RuleName PLY::rule_name_list[] = {
	{AVERAGE_RULE, "avg"},
	{RANDOM_RULE, "rnd"},
	{MINIMUM_RULE, "max"},
	{MAXIMUM_RULE, "min"},
	{MAJORITY_RULE, "major"},
	{SAME_RULE, "same"},
	{-1, "end_marker"}
};



/******************/
/*  Construction  */
/******************/


/******************************************************************************
Init PLY data as empty.

Entry:

Exit:
******************************************************************************/

PLY::PLY()
	:
	which_elem(NULL), other_elems(NULL), current_rules(NULL), rule_list(NULL),
	istream(NULL), mfp(NULL), write_type_names(type_names)
{
}

PLY::~PLY()
{
	release();
}


/******************************************************************************
Free the memory used by a PLY file.
******************************************************************************/

void PLY::release()
{
	if (mfp) {
		delete mfp;
		mfp = NULL;
		ostream = NULL;
	} else
	if (istream) {
		if (static_cast<Stream*>(istream)->getInputStream(ISTREAM::LAYER_ID_IN)) {
			IOSTREAM* const iostream(static_cast<Stream*>(istream)->getIOStream(ISTREAM::LAYER_ID_IN, OSTREAM::LAYER_ID_OUT));
			if (iostream)
				delete iostream;
			else
				delete istream;
		} else {
			ASSERT(static_cast<Stream*>(ostream)->getOutputStream(OSTREAM::LAYER_ID_OUT));
			IOSTREAM* const iostream(static_cast<Stream*>(ostream)->getIOStream(ISTREAM::LAYER_ID_IN, OSTREAM::LAYER_ID_OUT));
			if (iostream)
				delete iostream;
			else
				delete ostream;
		}
		istream = NULL;
	}
	if (!elems.empty()) {
		for (size_t i=0; i<elems.size(); ++i) {
			PlyElement* elem = elems[i];
			if (!elem->props.empty()) {
				for (size_t j=0; j<elem->props.size(); ++j)
					delete elem->props[j];
				elem->props.clear();
				elem->store_prop.clear();
			}
			delete elem;
		}
		elems.clear();
	}
	if (other_elems) {
		for (size_t i=0; i<other_elems->other_list.size(); ++i) {
			OtherElem& elem = other_elems->other_list[i];
			delete[] elem.other_data;
			delete elem.other_props;
		}
		delete other_elems; other_elems = NULL;
	}
	comments.clear();
	obj_info.clear();
	vals.clear();
}



/*************/
/*  Writing  */
/*************/


/******************************************************************************
Given a file pointer, get ready to write PLY data to the file.

Entry:
fp         - the given file pointer
nelems     - number of elements in object
elem_names - list of element names
file_type  - file type, either ascii or binary
memBufferSize - memory file initial size (useful if the ply size is unknown)

Exit:
returns a pointer to a PlyFile, used to refer to this file, or NULL if error
******************************************************************************/

bool PLY::write(LPCSTR _filename, int nelems, LPCSTR* elem_names, int _file_type, size_t memBufferSize)
{
	filename = _filename;
	if (memBufferSize == 0) {
		// create output file now
		File* const pf(new File(filename.c_str(), File::WRITE, File::CREATE | File::TRUNCATE));
		if (!pf->isOpen())
			return false;
		return write(new BufferedOutputStream<true>(pf, 64*1024), nelems, elem_names, _file_type, memBufferSize);
	}
	return write((OSTREAM*)NULL, nelems, elem_names, _file_type, memBufferSize);
}

bool PLY::write(OSTREAM* fp, int nelems, LPCSTR* elem_names, int _file_type, size_t memBufferSize)
{
	// create a record for this object 
	file_type = _file_type;
	version = 1.0;
	other_elems = NULL;

	if (memBufferSize > 0) {
		// write ply into a memory buffer, and save it to disk at the end;
		// useful in case the number of ply elements is not know from the start
		// in order to avoid an additional file copy operation
		ASSERT(fp == NULL && !filename.empty());
		mfp = new MemFile(memBufferSize);
		ostream = mfp;
	} else {
		// directly write ply data to disk;
		// in case the number of ply elements is unknown from the start,
		// they are written into a temporary file and the header is written
		// at the end using a file copy operation
		ASSERT(fp != NULL);
		ostream = fp;
		mfp = NULL;
	}

	// tuck aside the names of the elements 
	elems.resize(nelems);
	for (int i = 0; i < nelems; ++i) {
		PlyElement* elem = new PlyElement;
		elem->name = elem_names[i];
		elem->num = 0;
		elems[i] = elem;
	}
	return true;
}


/******************************************************************************
Describe an element, including its properties and how many will be written
to the file.

Entry:
elem_name - name of element that information is being specified about
nelems    - number of elements of this type to be written
nprops    - number of properties contained in the element
prop_list - list of properties
******************************************************************************/

void PLY::element_layout(
	const char* elem_name,
	int nelems,
	int nprops,
	PlyProperty* prop_list
	)
{
	// look for appropriate element 
	PlyElement *elem = find_element(elem_name);
	if (elem == NULL)
		abort_ply("error: element_layout: can't find element '%s'", elem_name);
	elem->num = nelems;

	// copy the list of properties 
	elem->props.resize(nprops);
	elem->store_prop.resize(nprops);

	for (int i = 0; i < nprops; ++i) {
		PlyProperty *prop = new PlyProperty;
		elem->props[i] = prop;
		elem->store_prop[i] = NAMED_PROP;
		copy_property(*prop, prop_list[i]);
	}
}


/******************************************************************************
Describe a property of an element.

Entry:
elem_name - name of element that information is being specified about
prop      - the new property
******************************************************************************/

void PLY::describe_property(const char* elem_name, const PlyProperty& prop)
{
	// look for appropriate element 
	put_element_setup(elem_name);

	// describe property 
	describe_property(prop);
}

void PLY::describe_property(const char* elem_name, int nprops, const PlyProperty* props)
{
	// look for appropriate element 
	put_element_setup(elem_name);

	// describe properties 
	for (int i=0; i<nprops; ++i)
		describe_property(props[i]);
}


/******************************************************************************
State how many of a given element will be written.

Entry:
elem_name - name of element that information is being specified about
nelems    - number of elements of this type to be written
******************************************************************************/

void PLY::element_count(const char* elem_name, int nelems)
{
	// look for appropriate element 
	PlyElement *elem = find_element(elem_name);
	if (elem == NULL)
		abort_ply("error: element_count: can't find element '%s'", elem_name);
	elem->num = nelems;
}


/******************************************************************************
Signal that we've described everything a PLY file's header and that the
header should be written to the file.

This function can be also called at the end, in which case the file is close,
the header is written in a new file and the already written content is copied
from the temporary file.
******************************************************************************/

bool PLY::header_complete()
{
	std::string filenameTmp;
	if (mfp != NULL) {
		// ply data was written into a memory buffer,
		// now write the header to disk and append the data in memory
		ostream = NULL;
	} else if (!filename.empty() && ostream->getPos() > 0) {
		// close this file, rename it, and open a new file to write the header 
		delete ostream; ostream = NULL;
		filenameTmp = filename+".tmp";
		if (!File::renameFile(filename.c_str(), filenameTmp.c_str()))
			return false;
	}
	if (ostream == NULL) {
		File* const pf(new File(filename.c_str(), File::WRITE, File::CREATE | File::TRUNCATE));
		if (!pf->isOpen())
			return false;
		ostream = new BufferedOutputStream<true>(pf, 64*1024);
	}

	// write header 
	ostream->print("ply\n");

	switch (file_type) {
	case ASCII:
		ostream->print("format ascii 1.0\n");
		break;
	case BINARY_BE:
		ostream->print("format binary_big_endian 1.0\n");
		break;
	case BINARY_LE:
		ostream->print("format binary_little_endian 1.0\n");
		break;
	default:
		abort_ply("error: ply_header_complete: bad file type = %d\n", file_type);
	}

	// write out the comments 
	for (size_t i = 0; i < comments.size(); ++i)
		ostream->print("comment %s\n", comments[i].c_str());

	// write out object information 
	for (size_t i = 0; i < obj_info.size(); ++i)
		ostream->print("obj_info %s\n", obj_info[i].c_str());

	// write out information about each element 
	for (size_t i = 0; i < elems.size(); ++i) {
		PlyElement *elem = elems[i];
		ASSERT(elem->num > 0);
		ostream->print("element %s %d\n", elem->name.c_str(), elem->num);

		// write out each property 
		for (size_t j = 0; j < elem->props.size(); ++j) {
			PlyProperty *prop = elem->props[j];
			if (prop->is_list == LIST) {
				ostream->print("property list ");
				write_scalar_type(prop->count_external);
				ostream->print(" ");
				write_scalar_type(prop->external_type);
				ostream->print(" %s\n", prop->name.c_str());
			} else if (prop->is_list == STRING) {
				ostream->print("property string");
				ostream->print(" %s\n", prop->name.c_str());
			} else {
				ostream->print("property ");
				write_scalar_type(prop->external_type);
				ostream->print(" %s\n", prop->name.c_str());
			}
		}
	}

	ostream->print("end_header\n");

	if (mfp != NULL) {
		// now write the ply data from memory to disk
		ostream->write(mfp->getBuffer(), mfp->getSize());
		delete mfp; mfp = NULL;
	} else if (!filenameTmp.empty()) {
		// append the body of the ply from the temp file, and delete it 
		File ftmp(filenameTmp.c_str(), File::READ, File::OPEN);
		if (!ftmp.isOpen())
			return false;
		Unsigned8Arr buffer(256 * 1024);
		size_t len;
		while ((len = ftmp.read(buffer.data(), buffer.size())) > 0)
			ostream->write(buffer.data(), len);
		ftmp.close();
		File::deleteFile(filenameTmp.c_str());
	} else {
		// element writing is starting next, reset counters 
		for (size_t i = 0; i < elems.size(); ++i)
			elems[i]->num = 0;
	}
	return true;
}


/******************************************************************************
Specify which elements are going to be written.  This should be called
before a call to the routine ply_put_element().

Entry:
elem_name - name of element we're talking about
******************************************************************************/

void PLY::put_element_setup(const char* elem_name)
{
	PlyElement *elem = find_element(elem_name);
	if (elem == NULL)
		abort_ply("error: put_element_setup: can't find element '%s'", elem_name);
	which_elem = elem;
}


/******************************************************************************
Write an element to the file.  This routine assumes that we're
writing the type of element specified in the last call to the routine
put_element_setup().

Entry:
elem_ptr - pointer to the element
******************************************************************************/

void PLY::put_element(const void* elem_ptr)
{
	char *item;
	char *elem_data;
	char **item_ptr;
	ValueType val;
	char **other_ptr;

	PlyElement *elem = which_elem;
	elem_data = (char*)elem_ptr;
	other_ptr = (char**)(elem_data + elem->other_offset);

	// write out either to an ascii or binary file 
	if (file_type == ASCII) {

		// write an ascii file 

		// write out each property of the element 
		for (size_t j = 0; j < elem->props.size(); ++j) {

			PlyProperty *prop = elem->props[j];

			if (elem->store_prop[j] == OTHER_PROP)
				elem_data = *other_ptr;
			else
				elem_data = (char*)elem_ptr;

			switch (prop->is_list) {
			case SCALAR: {  // scalar 
				item = elem_data + prop->offset;
				get_stored_item((void*)item, prop->internal_type, val);
				write_ascii_item(val, prop->internal_type, prop->external_type);
				break;
			}
			case LIST: {   // list 
				item = elem_data + prop->count_offset;
				get_stored_item((void*)item, prop->count_internal, val);
				write_ascii_item(val, prop->count_internal, prop->count_external);
				const int list_count(ValueType2Type<int>(val, prop->count_external));
				item_ptr = (char**)(elem_data + prop->offset);
				item = item_ptr[0];
				const int item_size = ply_type_size[prop->internal_type];
				for (int k = 0; k < list_count; k++) {
					get_stored_item((void*)item, prop->internal_type, val);
					write_ascii_item(val, prop->internal_type, prop->external_type);
					item += item_size;
				}
				break;
			}
			case STRING: {  // string 
				item = elem_data + prop->offset;
				char** str = (char**)item;
				ostream->print("\"%s\"", *str);
				break;
			}
			default:
				abort_ply("error: put_element: bad type = %d", prop->is_list);
			}
		}

		ostream->print("\n");
	} else {

		// write a binary file 

		// write out each property of the element 
		for (size_t j = 0; j < elem->props.size(); ++j) {
			PlyProperty *prop = elem->props[j];
			if (elem->store_prop[j] == OTHER_PROP)
				elem_data = *other_ptr;
			else
				elem_data = (char*)elem_ptr;
			switch (prop->is_list) {
			case SCALAR: {  // scalar 
				item = elem_data + prop->offset;
				get_stored_item((void*)item, prop->internal_type, val);
				write_binary_item(val, prop->internal_type, prop->external_type);
				break;
			}
			case LIST: {    // list 
				item = elem_data + prop->count_offset;
				int item_size = ply_type_size[prop->count_internal];
				get_stored_item((void*)item, prop->count_internal, val);
				write_binary_item(val, prop->count_internal, prop->count_external);
				const int list_count(ValueType2Type<int>(val, prop->count_external));
				item_ptr = (char**)(elem_data + prop->offset);
				item = item_ptr[0];
				item_size = ply_type_size[prop->internal_type];
				for (int k = 0; k < list_count; k++) {
					get_stored_item((void*)item, prop->internal_type, val);
					write_binary_item(val, prop->internal_type, prop->external_type);
					item += item_size;
				}
				break;
			}
			case STRING: {  // string 
				item = elem_data + prop->offset;
				char** str = (char**)item;

				// write the length 
				const int len = (int)_tcslen(*str) + 1;
				ostream->write(&len, sizeof(int));

				// write the string, including the null character 
				ostream->write(*str, len);
				break;
			}
			default:
				abort_ply("error: put_element: bad type = %d", prop->is_list);
			}
		}
	}

	// count element items 
	elem->num++;
}



/*************/
/*  Reading  */
/*************/


/******************************************************************************
Given a file pointer, get ready to read PLY data from the file.

Entry:
fp - the given file pointer

Exit:
nelems     - number of elements in object
elem_names - list of element names
returns a pointer to a PlyFile, used to refer to this file, or NULL if error
******************************************************************************/

bool PLY::read(LPCSTR _filename)
{
	filename = _filename;
	File* const pf(new File(_filename, File::READ, File::OPEN));
	if (!pf->isOpen())
		return false;
	return read(new BufferedInputStream<true>(pf, 64*1024));
}

bool PLY::read(ISTREAM* fp)
{
	// create record for this object 
	ASSERT(elems.empty());
	other_elems = NULL;
	rule_list = NULL;
	istream = fp;

	// read and parse the file's header 
	int nwords;
	char *orig_line;
	STRISTREAM sfp(istream);
	char **words = get_words(sfp, &nwords, &orig_line);
	if (words == NULL)
		return false;
	if (!equal_strings(words[0], "ply")) {
		free(words);
		return false;
	}
	free(words);

	// parse words 
	while ((words = get_words(sfp, &nwords, &orig_line)) != NULL) {
		if (equal_strings(words[0], "format")) {
			if (nwords != 3)
				return false;
			if (equal_strings(words[1], "ascii"))
				file_type = ASCII;
			else if (equal_strings(words[1], "binary_big_endian"))
				file_type = BINARY_BE;
			else if (equal_strings(words[1], "binary_little_endian"))
				file_type = BINARY_LE;
			else
				return false;
			version = (float)atof(words[2]);
		} else if (equal_strings(words[0], "element"))
			add_element((const char**)words, nwords);
		else if (equal_strings(words[0], "property"))
			add_property((const char**)words, nwords);
		else if (equal_strings(words[0], "comment"))
			add_comment(orig_line);
		else if (equal_strings(words[0], "obj_info"))
			add_obj_info(orig_line);
		else if (equal_strings(words[0], "end_header")) {
			free(words);
			break;
		}
		free(words);
	}
	sfp.emptyBuffer();

	// create tags for each property of each element, to be used 
	// later to say whether or not to store each property for the user 
	for (size_t i = 0; i < elems.size(); ++i) {
		PlyElement *elem = elems[i];
		elem->store_prop.resize(elem->props.size());
		for (size_t j = 0; j < elem->props.size(); ++j)
			elem->store_prop[j] = DONT_STORE_PROP;
		elem->other_offset = NO_OTHER_PROPS; // no "other" props by default 
	}
	return true;
}


/******************************************************************************
Get information about a particular element.

Entry:
elem_name - name of element to get information about

Exit:
props     - the list of properties returned
returns number of elements of this type in the file
******************************************************************************/

int PLY::get_element_description(const char* elem_name, std::vector<PlyProperty*>& prop_list) const
{
	// find information about the element 
	PlyElement *elem = find_element(elem_name);
	if (elem == NULL)
		return 0;

	// make a copy of the element's property list 
	prop_list.resize(elem->props.size());
	for (size_t i = 0; i < elem->props.size(); ++i) {
		PlyProperty *prop = new PlyProperty;
		copy_property(*prop, *elem->props[i]);
		prop_list[i] = prop;
	}

	return elem->num;
}


/******************************************************************************
Specify which properties of an element are to be returned.  This should be
called before a call to the routine get_element().

Entry:
elem_name - which element we're talking about
nprops    - number of properties
prop_list - list of properties
******************************************************************************/

void PLY::get_element_setup(
	const char* elem_name,
	int nprops,
	PlyProperty* prop_list
	)
{
	// find information about the element 
	PlyElement *elem = find_element(elem_name);
	which_elem = elem;

	// deposit the property information into the element's description 
	for (int i = 0; i < nprops; ++i) {
		// look for actual property 
		int index = find_property(elem, prop_list[i].name.c_str());
		if (index == -1) {
			DEBUG("warning: Can't find property '%s' in element '%s'", prop_list[i].name.c_str(), elem_name);
			continue;
		}

		// store its description 
		PlyProperty *prop = elem->props[index];
		prop->internal_type = prop_list[i].internal_type;
		prop->offset = prop_list[i].offset;
		prop->count_internal = prop_list[i].count_internal;
		prop->count_offset = prop_list[i].count_offset;

		// specify that the user wants this property 
		elem->store_prop[index] = STORE_PROP;
	}
}


/******************************************************************************
Specify a property of an element that is to be returned.  This should be
called (usually multiple times) before a call to the routine get_element().
This routine should be used in preference to the less flexible old routine
called ply_get_element_setup().

Entry:
elem_name - which element we're talking about
prop      - property to add to those that will be returned
******************************************************************************/

void PLY::get_property(const char* elem_name, PlyProperty* prop)
{
	// find information about the element 
	PlyElement *elem = find_element(elem_name);
	which_elem = elem;

	// deposit the property information into the element's description 
	int index = find_property(elem, prop->name.c_str());
	if (index == -1) {
		DEBUG("warning: Can't find property '%s' in element '%s'", prop->name.c_str(), elem_name);
		return;
	}
	PlyProperty *prop_ptr = elem->props[index];
	prop_ptr->internal_type  = prop->internal_type;
	prop_ptr->offset         = prop->offset;
	prop_ptr->count_internal = prop->count_internal;
	prop_ptr->count_offset   = prop->count_offset;

	// specify that the user wants this property 
	elem->store_prop[index] = STORE_PROP;
}


/******************************************************************************
Read one element from the file.  This routine assumes that we're reading
the type of element specified in the last call to the routine
ply_get_element_setup().

Entry:
elem_ptr - pointer to location where the element information should be put
******************************************************************************/

void PLY::get_element(void* elem_ptr)
{
	if (file_type == ASCII)
		ascii_get_element((uint8_t*)elem_ptr);
	else
		binary_get_element((uint8_t*)elem_ptr);
}


/******************************************************************************
Extract the comments from the header information of a PLY file.

Entry:

Exit:
returns the list of comments
******************************************************************************/

std::vector<std::string>& PLY::get_comments()
{
	return comments;
}


/******************************************************************************
Extract the object information (arbitrary text) from the header information
of a PLY file.

Entry:

Exit:
returns the list of object info lines
******************************************************************************/

std::vector<std::string>& PLY::get_obj_info()
{
	return obj_info;
}


/******************************************************************************
Make ready for "other" properties of an element-- those properties that
the user has not explicitly asked for, but that are to be stashed away
in a special structure to be carried along with the element's other
information.

Entry:
elem    - element for which we want to save away other properties
******************************************************************************/

void PLY::setup_other_props(PlyElement* elem)
{
	int size = 0;

	// Examine each property in decreasing order of size. 
	// We do this so that all data types will be aligned by 
	// word, half-word, or whatever within the structure. 
	for (int type_size = 8; type_size > 0; type_size /= 2) {

		// add up the space taken by each property, and save this information 
		// away in the property descriptor 
		for (size_t i = 0; i < elem->props.size(); ++i) {

			// don't bother with properties we've been asked to store explicitly 
			if (elem->store_prop[i])
				continue;

			PlyProperty *prop = elem->props[i];

			// internal types will be same as external 
			prop->internal_type = prop->external_type;
			prop->count_internal = prop->count_external;

			// list case 
			if (prop->is_list == LIST) {

				// pointer to list 
				if (type_size == sizeof(void *)) {
					prop->offset = size;
					size += sizeof(void *);    // always use size of a pointer here 
				}

				// count of number of list elements 
				if (type_size == ply_type_size[prop->count_external]) {
					prop->count_offset = size;
					size += ply_type_size[prop->count_external];
				}
			}
			// string 
			else if (prop->is_list == STRING) {
				// pointer to string 
				if (type_size == sizeof(char*)) {
					prop->offset = size;
					size += sizeof(char*);
				}
			}
			// scalar 
			else if (type_size == ply_type_size[prop->external_type]) {
				prop->offset = size;
				size += ply_type_size[prop->external_type];
			}
		}

	}

	// save the size for the other_props structure 
	elem->other_size = size;
}


/******************************************************************************
Specify that we want the "other" properties of an element to be tucked
away within the user's structure.

Entry:
elem    - the element that we want to store other_props in
offset  - offset to where other_props will be stored inside user's structure

Exit:
returns pointer to structure containing description of other_props
******************************************************************************/

PLY::PlyOtherProp* PLY::get_other_properties(PlyElement* elem, int offset)
{
	// remember that this is the "current" element 
	which_elem = elem;

	// save the offset to where to store the other_props 
	elem->other_offset = offset;

	// place the appropriate pointers, etc. in the element's property list 
	setup_other_props(elem);

	// create structure for describing other_props 
	PlyOtherProp *other = new PlyOtherProp;
	other->name = elem->name;
	#if 0
	if (elem->other_offset == NO_OTHER_PROPS) {
		other->size = 0;
		other->props = NULL;
		other->nprops = 0;
		return other;
	}
	#endif
	other->size = elem->other_size;
	other->props.reserve(elem->props.size());

	// save descriptions of each "other" property 
	for (size_t i = 0; i < elem->props.size(); ++i) {
		if (elem->store_prop[i])
			continue;
		PlyProperty *prop = new PlyProperty;
		copy_property(*prop, *elem->props[i]);
		other->props.push_back(prop);
	}

	// set other_offset pointer appropriately if there are NO other properties 
	if (other->props.empty())
		elem->other_offset = NO_OTHER_PROPS;

	// return structure 
	return other;
}


/******************************************************************************
Specify that we want the "other" properties of an element to be tucked
away within the user's structure.  The user needn't be concerned for how
these properties are stored.

Entry:
elem_name - name of element that we want to store other_props in
offset    - offset to where other_props will be stored inside user's structure

Exit:
returns pointer to structure containing description of other_props
******************************************************************************/

PLY::PlyOtherProp* PLY::get_other_properties(const char* elem_name, int offset)
{
	// find information about the element 
	PlyElement *elem = find_element(elem_name);
	if (elem == NULL) {
		DEBUG("warning: get_other_properties: Can't find element '%s'", elem_name);
		return NULL;
	}

	PlyOtherProp *other = get_other_properties(elem, offset);
	return other;
}




/*************************/
/*  Other Element Stuff  */
/*************************/





/******************************************************************************
Grab all the data for the current element that a user does not want to
explicitly read in.  Stores this in the PLY object's data structure.

Entry:

Exit:
returns pointer to ALL the "other" element data for this PLY file
******************************************************************************/

PLY::PlyOtherElems* PLY::get_other_element()
{
	PlyElement *elem = which_elem;

	// create room for the new "other" element, initializing the 
	// other data structure if necessary 
	OtherElem other;

	// count of element instances in file 
	other.elem_count = elem->num;

	// save name of element 
	other.elem_name = elem->name;

	// create a list to hold all the current elements 
	other.other_data = new OtherData*[other.elem_count];

	// set up for getting elements 
	other.other_props = get_other_properties(elem->name.c_str(), offsetof(OtherData,other_props));

	// grab all these elements 
	for (int i = 0; i < other.elem_count; ++i) {
		// grab and element from the file 
		other.other_data[i] = new OtherData;
		get_element((uint8_t*)other.other_data[i]);
	}

	// return pointer to the other elements data 
	if (other_elems == NULL)
		other_elems = new PlyOtherElems;
	other_elems->other_list.push_back(other);
	return other_elems;
}


/******************************************************************************
Write out the "other" elements specified for this PLY file.

Entry:
******************************************************************************/

void PLY::put_other_elements()
{
	// make sure we have other elements to write 
	if (other_elems == NULL)
		return;

	// write out the data for each "other" element 
	for (size_t i = 0; i < other_elems->other_list.size(); ++i) {
		OtherElem *other = &(other_elems->other_list[i]);
		put_element_setup(other->elem_name.c_str());

		// write out each instance of the current element 
		for (int j = 0; j < other->elem_count; ++j)
			put_element(other->other_data[j]);
	}
}



/*******************/
/*  Miscellaneous  */
/*******************/


/******************************************************************************
Use old PLY type names during writing for backward compatibility.
******************************************************************************/

void PLY::set_legacy_type_names()
{
	write_type_names = old_type_names;
}


/******************************************************************************
Get version number and file type of a PlyFile.

Entry:

Exit:
version - version of the file
file_type - PLY_ASCII, PLY_BINARY_BE, or PLY_BINARY_LE
******************************************************************************/

void PLY::get_info(float* _version, int* _file_type)
{
	*_version = version;
	*_file_type = file_type;
}


/******************************************************************************
Find an element from the element list of a given PLY object.

Entry:
element - name of element we're looking for

Exit:
returns the element, or NULL if not found
******************************************************************************/

PLY::PlyElement* PLY::find_element(const char* element) const
{
	for (size_t i=0; i<elems.size(); ++i)
		if (equal_strings(element, elems[i]->name.c_str()))
			return elems[i];
	return NULL;
}


/******************************************************************************
Find a property in the list of properties of a given element.

Entry:
elem      - pointer to element in which we want to find the property
prop_name - name of property to find

Exit:
returns the index to position in list
******************************************************************************/

int PLY::find_property(PlyElement* elem, const char* prop_name) const
{
	for (size_t i=0; i<elem->props.size(); ++i)
		if (equal_strings(prop_name, elem->props[i]->name.c_str()))
			return (int)i;
	return -1;
}


/******************************************************************************
Read an element from an ascii file.

Entry:
elem_ptr - pointer to element
******************************************************************************/

void PLY::ascii_get_element(uint8_t* elem_ptr)
{
	char *elem_data, *item;
	char *item_ptr;
	ValueType val;
	char *orig_line;
	char *other_data(NULL);
	int other_flag(0);

	// the kind of element we're reading currently 
	PlyElement *elem = which_elem;

	// do we need to setup for other_props? 
	if (elem->other_offset != NO_OTHER_PROPS) {
		other_flag = 1;
		// make room for other_props 
		other_data = new char[elem->other_size];
		// store pointer in user's structure to the other_props 
		*((char**)(elem_ptr + elem->other_offset)) = other_data;
	}

	// read in the element 
	int nwords;
	char **words;
	{
	STRISTREAM sfp(istream);
	words = get_words(sfp, &nwords, &orig_line);
	if (words == NULL)
		abort_ply("error: get_element: unexpected end of file");
	sfp.emptyBuffer();
	}
	int which_word = 0;

	for (size_t j = 0; j < elem->props.size(); ++j) {
		PlyProperty *prop = elem->props[j];
		const int store_it(elem->store_prop[j] | other_flag);

		// store either in the user's structure or in other_props 
		if (elem->store_prop[j])
			elem_data = (char*)elem_ptr;
		else
			elem_data = other_data;

		if (prop->is_list == LIST) {       // a list 
			// get and store the number of items in the list 
			get_ascii_item(words[which_word++], prop->count_external, val);
			if (store_it) {
				item = elem_data + prop->count_offset;
				store_item(item, prop->count_internal, val, prop->count_external);
			}

			// allocate space for an array of items and store a ptr to the array 
			const int list_count(ValueType2Type<int>(val, prop->count_external));
			char** store_array = (char**)(elem_data + prop->offset);
			if (list_count == 0) {
				if (store_it)
					*store_array = NULL;
			} else {
				const int item_size(ply_type_size[prop->internal_type]);

				if (store_it) {
					item_ptr = new char[item_size * list_count];
					item = item_ptr;
					*store_array = item_ptr;
				}

				// read items and store them into the array 
				for (int k = 0; k < list_count; k++) {
					get_ascii_item(words[which_word++], prop->external_type, val);
					if (store_it) {
						store_item(item, prop->internal_type, val, prop->external_type);
						item += item_size;
					}
				}
			}
		} else if (prop->is_list == STRING) {   // a string 
			if (store_it) {
				item = elem_data + prop->offset;
				*((char**)item) = strdup(words[which_word++]);
			} else {
				which_word++;
			}
		} else {                               // a scalar 
			get_ascii_item(words[which_word++], prop->external_type, val);
			if (store_it) {
				item = elem_data + prop->offset;
				store_item(item, prop->internal_type, val, prop->external_type);
			}
		}
	}

	free(words);
}


/******************************************************************************
Read an element from a binary file.

Entry:
elem_ptr - pointer to an element
******************************************************************************/

void PLY::binary_get_element(uint8_t* elem_ptr)
{
	char *elem_data;
	char *item;
	char *item_ptr;
	ValueType val;
	char *other_data(NULL);
	int other_flag(0);

	// the kind of element we're reading currently 
	PlyElement* elem = which_elem;

	// do we need to setup for other_props? 
	if (elem->other_offset != NO_OTHER_PROPS) {
		other_flag = 1;
		// make room for other_props 
		other_data = new char[elem->other_size];
		// store pointer in user's structure to the other_props 
		*((char**)(elem_ptr + elem->other_offset)) = other_data;
	}

	// read in a number of elements 
	for (size_t j = 0; j < elem->props.size(); ++j) {
		PlyProperty *prop = elem->props[j];
		const int store_it(elem->store_prop[j] | other_flag);

		// store either in the user's structure or in other_props 
		if (elem->store_prop[j])
			elem_data = (char*)elem_ptr;
		else
			elem_data = other_data;

		if (prop->is_list == LIST) {          // list 
			// get and store the number of items in the list 
			get_binary_item(prop->count_external, val);
			if (store_it) {
				item = elem_data + prop->count_offset;
				store_item(item, prop->count_internal, val, prop->count_external);
			}

			// allocate space for an array of items and store a ptr to the array 
			const int list_count(ValueType2Type<int>(val, prop->count_external));
			const int item_size(ply_type_size[prop->internal_type]);
			char** store_array = (char**)(elem_data + prop->offset);
			if (list_count == 0) {
				if (store_it)
					*store_array = NULL;
			} else {
				if (store_it) {
					item_ptr = new char[item_size * list_count];
					item = item_ptr;
					*store_array = item_ptr;
				}

				// read items and store them into the array 
				for (int k = 0; k < list_count; k++) {
					get_binary_item(prop->external_type, val);
					if (store_it) {
						store_item(item, prop->internal_type, val, prop->external_type);
						item += item_size;
					}
				}
			}
		} else if (prop->is_list == STRING) {     // string 
			int len;
			istream->read(&len, sizeof(int));
			char *str = new char[len];
			istream->read(str, len);
			if (store_it) {
				item = elem_data + prop->offset;
				*((char**)item) = str;
			}
		} else {                                   // scalar 
			get_binary_item(prop->external_type, val);
			if (store_it) {
				item = elem_data + prop->offset;
				store_item(item, prop->internal_type, val, prop->external_type);
			}
		}
	}
}


/******************************************************************************
Write to a file the word that represents a PLY data type.

Entry:
code - code for type
******************************************************************************/

void PLY::write_scalar_type(int code)
{
	// make sure this is a valid code 
	if (code <= StartType || code >= EndType)
		abort_ply("error: write_scalar_type: bad data code = %d", code);

	// write the code to a file 
	ostream->print("%s", write_type_names[code]);
}


/******************************************************************************
Get a text line from a file and break it up into words.

IMPORTANT: The calling routine should call "free" on the returned pointer once
finished with it.

Entry:
sfp - string file to read from

Exit:
nwords    - number of words returned
orig_line - the original line of characters
returns a list of words from the line, or NULL if end-of-file
******************************************************************************/

char** PLY::get_words(STRISTREAM& sfp, int* nwords, char** orig_line)
{
	const int BIG_STRING = 4096;
	char str[BIG_STRING];
	char str_copy[BIG_STRING];

	int max_words = 10;
	int num_words = 0;
	char *ptr, *ptr2;

	char** words = (char**)malloc(sizeof(char*) * max_words);

	// read in a line 
	size_t len(sfp.readLine(str, BIG_STRING-2));
	if (len == 0 || len == STREAM_ERROR) {
		*nwords = 0;
		*orig_line = NULL;
		free(words);
		return NULL;
	}

	// convert line-feed and tabs into spaces 
	// (this guarantees that there will be a space before the 
	//  null character at the end of the string) 
	if (str[len-1] == '\r')
		--len;
	str[len] = '\n';

	for (ptr = str, ptr2 = str_copy; ; ptr++, ptr2++) {
		switch (*ptr) {
		case '\t':
			*ptr = ' ';
			*ptr2 = ' ';
			break;
		case '\n':
			*ptr = ' ';
			*(ptr+1) = *ptr2 = '\0';
			goto EXIT_LOOP;
		default:
			*ptr2 = *ptr;
		}
	}
	EXIT_LOOP:

	// find the words in the line 
	ptr = str;
	while (*ptr != '\0') {

		// jump over leading spaces 
		while (*ptr == ' ')
			ptr++;

		// break if we reach the end 
		if (*ptr == '\0')
			break;

		// allocate more room for words if necessary 
		if (num_words >= max_words) {
			max_words += 10;
			words = (char**)realloc(words, sizeof(char*) * max_words);
		}

		if (*ptr == '\"') {  // a quote indicates that we have a string 
			// skip over leading quote 
			ptr++;

			// save pointer to beginning of word 
			words[num_words++] = ptr;

			// find trailing quote or end of line 
			while (*ptr != '\"' && *ptr != '\0')
				ptr++;

			// replace quote with a null character to mark the end of the word 
			// if we are not already at the end of the line 
			if (*ptr != '\0')
				*ptr++ = '\0';
		} else {               // non-string 
			// save pointer to beginning of word 
			words[num_words++] = ptr;

			// jump over non-spaces 
			while (*ptr != ' ')
				ptr++;

			// place a null character here to mark the end of the word 
			*ptr++ = '\0';
		}
	}

	// return the list of words 
	*nwords = num_words;
	*orig_line = str_copy;
	return words;
}


/******************************************************************************
Write out an item to a file as raw binary bytes.

Entry:
val        - item value to be written
double_val - value type
type       - data type to write out
******************************************************************************/

void PLY::write_binary_item(
	const ValueType& val,
	int from_type,
	int to_type
	)
{
	switch (to_type) {
	case Int8: {
		const int8_t v(ValueType2Type<int8_t>(val, from_type));
		ostream->write(&v, 1);
		break; }
	case Int16: {
		const int16_t v(ValueType2Type<int16_t>(val, from_type));
		ostream->write(&v, 2);
		break; }
	case Int32: {
		const int32_t v(ValueType2Type<int32_t>(val, from_type));
		ostream->write(&v, 4);
		break; }
	case Uint8: {
		const uint8_t v(ValueType2Type<uint8_t>(val, from_type));
		ostream->write(&v, 1);
		break; }
	case Uint16: {
		const uint16_t v(ValueType2Type<uint16_t>(val, from_type));
		ostream->write(&v, 2);
		break; }
	case Uint32: {
		const uint32_t v(ValueType2Type<uint32_t>(val, from_type));
		ostream->write(&v, 4);
		break; }
	case Float32: {
		const float v(ValueType2Type<float>(val, from_type));
		ostream->write(&v, 4);
		break; }
	case Float64: {
		const double v(ValueType2Type<double>(val, from_type));
		ostream->write(&v, 8);
		break; }
	default:
		abort_ply("error: write_binary_item: bad type = %d", to_type);
	}
}


/******************************************************************************
Write out an item to a file as ascii characters.

Entry:
val        - item value to be written
double_val - value type
type       - data type to write out
******************************************************************************/

void PLY::write_ascii_item(
	const ValueType& val,
	int from_type,
	int to_type
	)
{
	switch (to_type) {
	case Int8:
	case Int16:
	case Int32:
		ostream->print("%d ", ValueType2Type<int32_t>(val, from_type));
		break;
	case Uint8:
	case Uint16:
	case Uint32:
		ostream->print("%u ", ValueType2Type<uint32_t>(val, from_type));
		break;
	case Float32:
	case Float64:
		ostream->print("%g ", ValueType2Type<double>(val, from_type));
		break;
	default:
		abort_ply("error: write_ascii_item: bad type = %d", to_type);
	}
}


/******************************************************************************
Get the value of an item that is in memory, and place the result
into an integer, an unsigned integer and a double.

Entry:
ptr        - pointer to the item
type       - data type supposedly in the item

Exit:
val        - extracted value
******************************************************************************/

void PLY::get_stored_item(
	const void* ptr,
	int type,
	ValueType& val
	)
{
	switch (type) {
	case Int8:
		val.i8 = *((const int8_t*)ptr);
		break;
	case Uint8:
		val.u8 = *((const uint8_t*)ptr);
		break;
	case Int16:
		val.i16 = *((const int16_t*)ptr);
		break;
	case Uint16:
		val.u16 = *((const uint16_t*)ptr);
		break;
	case Int32:
		val.i32 = *((const int32_t*)ptr);
		break;
	case Uint32:
		val.u32 = *((const uint32_t*)ptr);
		break;
	case Float32:
		val.f = *((const float*)ptr);
		break;
	case Float64:
		val.d = *((const double*)ptr);
		break;
	default:
		abort_ply("error: get_stored_item: bad type = %d", type);
	}
}


/******************************************************************************
Get the value of an item from a binary file, and place the result
into an integer, an unsigned integer and a double.

Entry:
type       - data type supposedly in the word

Exit:
val        - store value
******************************************************************************/

void PLY::get_binary_item(int type, ValueType& val)
{
	switch (type) {
	case Int8:
		istream->read(&val.i8, 1);
		break;
	case Uint8:
		istream->read(&val.u8, 1);
		break;
	case Int16:
		istream->read(&val.i16, 2);
		break;
	case Uint16:
		istream->read(&val.u16, 2);
		break;
	case Int32:
		istream->read(&val.i32, 4);
		break;
	case Uint32:
		istream->read(&val.u32, 4);
		break;
	case Float32:
		istream->read(&val.f, 4);
		break;
	case Float64:
		istream->read(&val.d, 8);
		break;
	default:
		abort_ply("error: get_binary_item: bad type = %d", type);
	}
}


/******************************************************************************
Extract the value of an item from an ascii word, and place the result
into an integer, an unsigned integer and a double.

Entry:
word       - word to extract value from
type       - data type supposedly in the word

Exit:
val        - store value
******************************************************************************/

void PLY::get_ascii_item(const char* word, int type, ValueType& val)
{
	switch (type) {
	case Int8:
		val.i8 = (int8_t)atoi(word);
		break;
	case Uint8:
		val.u8 = (uint8_t)atoi(word);
		break;
	case Int16:
		val.i16 = (int16_t)atoi(word);
		break;
	case Uint16:
		val.u16 = (uint16_t)atoi(word);
		break;
	case Int32:
		val.i32 = atoi(word);
		break;
	case Uint32:
		val.u32 = strtoul(word, (char**)NULL, 10);
		break;
	case Float32:
		val.f = (float)atof(word);
		break;
	case Float64:
		val.d = atof(word);
		break;
	default:
		abort_ply("error: get_ascii_item: bad type = %d", type);
	}
}


/******************************************************************************
Store a value into a place being pointed to, guided by a data type.

Entry:
to_type    - data type
val        - value to be stored
from_type  - value type

Exit:
ptr        - data pointer to stored value
******************************************************************************/

void PLY::store_item(
	void* ptr,
	int to_type,
	const ValueType& val,
	int from_type
	)
{
	switch (to_type) {
	case Int8:
		*((int8_t*)ptr) = ValueType2Type<int8_t>(val, from_type);
		break;
	case Uint8:
		*((uint8_t*)ptr) = ValueType2Type<uint8_t>(val, from_type);
		break;
	case Int16:
		*((int16_t*)ptr) = ValueType2Type<int16_t>(val, from_type);
		break;
	case Uint16:
		*((uint16_t*)ptr) = ValueType2Type<uint16_t>(val, from_type);
		break;
	case Int32:
		*((int32_t*)ptr) = ValueType2Type<int32_t>(val, from_type);
		break;
	case Uint32:
		*((uint32_t*)ptr) = ValueType2Type<uint32_t>(val, from_type);
		break;
	case Float32:
		*((float*)ptr) = ValueType2Type<float>(val, from_type);
		break;
	case Float64:
		*((double*)ptr) = ValueType2Type<double>(val, from_type);
		break;
	default:
		abort_ply("error: store_item: bad type = %d", to_type);
	}
}


/******************************************************************************
Add an element to a PLY file descriptor.

Entry:
words   - list of words describing the element
nwords  - number of words in the list
******************************************************************************/

void PLY::add_element(const char** words, int /*nwords*/)
{
	// create the new element 
	PlyElement *elem = new PlyElement;
	elem->name = words[1];
	elem->num = atoi(words[2]);

	// add the new element to the object's list 
	elems.push_back(elem);
}


/******************************************************************************
Return the type of a property, given the name of the property.

Entry:
name - name of property type

Exit:
returns integer code for property, or 0 if not found
******************************************************************************/

int PLY::get_prop_type(const char* type_name)
{
	// try to match the type name 
	for (int i = StartType + 1; i < EndType; ++i)
		if (equal_strings (type_name, type_names[i]))
			return i;

	// see if we can match an old type name 
	for (int i = StartType + 1; i < EndType; ++i)
		if (equal_strings (type_name, old_type_names[i]))
			return i;

	// if we get here, we didn't find the type 
	return 0;
}


/******************************************************************************
Add a property to a PLY file descriptor.

Entry:
words   - list of words describing the property
nwords  - number of words in the list
******************************************************************************/

void PLY::add_property(const char** words, int /*nwords*/)
{
	// create the new property 
	PlyProperty *prop = new PlyProperty;

	if (equal_strings(words[1], "list")) {          // list 
		prop->count_external = get_prop_type (words[2]);
		prop->external_type = get_prop_type (words[3]);
		prop->name = words[4];
		prop->is_list = LIST;
	} else if (equal_strings(words[1], "string")) {   // string 
		prop->count_external = Int8;
		prop->external_type = Int8;
		prop->name = words[2];
		prop->is_list = STRING;
	} else {                                           // scalar 
		prop->external_type = get_prop_type (words[1]);
		prop->name = words[2];
		prop->is_list = SCALAR;
	}

	// internal types are the same as external by default 
	prop->internal_type = prop->external_type;
	prop->count_internal = prop->count_external;

	// add this property to the list of properties of the current element 
	PlyElement *elem = elems.back();
	elem->props.push_back(prop);
}


/******************************************************************************
Add a comment to a PLY file descriptor.

Entry:
line    - line containing comment
******************************************************************************/

void PLY::add_comment(const char* line)
{
	// skip over "comment" and leading spaces and tabs 
	int i = 7;
	while (line[i] == ' ' || line[i] == '\t')
		i++;
	append_comment(&line[i]);
}


/******************************************************************************
Add a some object information to a PLY file descriptor.

Entry:
line    - line containing text info
******************************************************************************/

void PLY::add_obj_info(const char* line)
{
	// skip over "obj_info" and leading spaces and tabs 
	int i = 8;
	while (line[i] == ' ' || line[i] == '\t')
		i++;
	append_obj_info(&line[i]);
}


/******************************************************************************
Copy a property.
******************************************************************************/

void PLY::copy_property(PlyProperty& dest, const PlyProperty& src)
{
	dest.name = src.name;
	dest.external_type = src.external_type;
	dest.internal_type = src.internal_type;
	dest.offset = src.offset;

	dest.is_list = src.is_list;
	dest.count_external = src.count_external;
	dest.count_internal = src.count_internal;
	dest.count_offset = src.count_offset;
}



/******************************************************************************
Return a list of the names of the elements in a particular PLY file.

Entry:

Exit:
elem_names - the list of element names
returns the number of elements
******************************************************************************/

int PLY::get_element_list(std::vector<std::string>& elem_names) const
{
	// create the list of element names 
	elem_names.resize(elems.size());
	for (size_t i = 0; i < elems.size(); ++i)
		elem_names[i] = elems[i]->name;
	// return the number of elements and the list of element names 
	return (int)elems.size();
}


/******************************************************************************
Append a comment to a PLY file.

Entry:
comment - the comment to append
******************************************************************************/

void PLY::append_comment(const char* comment)
{
	// add comment to list 
	comments.push_back(comment);
}


/******************************************************************************
Copy the comments from one PLY file to another.

Entry:
out_ply - destination file to copy comments to
in_ply  - the source of the comments
******************************************************************************/

void PLY::copy_comments(const PLY& in_ply)
{
	for (size_t i = 0; i < in_ply.comments.size(); ++i)
		append_comment(in_ply.comments[i].c_str());
}


/******************************************************************************
Append object information (arbitrary text) to a PLY file.

Entry:
obj_info - the object info to append
******************************************************************************/

void PLY::append_obj_info(const char* _obj_info)
{
	// add info to list 
	obj_info.push_back(_obj_info);
}


/******************************************************************************
Copy the object information from one PLY file to another.

Entry:
out_ply - destination file to copy object information to
in_ply  - the source of the object information
******************************************************************************/

void PLY::copy_obj_info(const PLY& in_ply)
{
	for (size_t i = 0; i < in_ply.obj_info.size(); ++i)
		append_obj_info(in_ply.obj_info[i].c_str());
}


/******************************************************************************
Specify the index of the next element to be read in from a PLY file.

Entry:
index - index of the element to be read

Exit:
elem_count - the number of elements in the file
returns pointer to the name of this next element
******************************************************************************/

LPCSTR PLY::setup_element_read(int index, int* elem_count)
{
	if ((size_t)index > elems.size()) {
		DEBUG("warning: No element with index %d", index);
		return 0;
	}

	PlyElement* elem = elems[index];

	// set this to be the current element 
	which_elem = elem;

	// return the number of such elements in the file and the element's name 
	*elem_count = elem->num;
	return elem->name.c_str();
}


/******************************************************************************
Specify one of several properties of the current element that is to be
read from a file.  This should be called (usually multiple times) before a
call to the routine get_element().

Entry:
prop    - property to add to those that will be returned
******************************************************************************/

void PLY::setup_property(const PlyProperty& prop)
{
	PlyElement *elem = which_elem;

	// deposit the property information into the element's description 
	int index = find_property(elem, prop.name.c_str());
	if (index == -1) {
		DEBUG("warning: Can't find property '%s' in element '%s'", prop.name.c_str(), elem->name.c_str());
		return;
	}
	PlyProperty *prop_ptr = elem->props[index];
	prop_ptr->internal_type  = prop.internal_type;
	prop_ptr->offset         = prop.offset;
	prop_ptr->count_internal = prop.count_internal;
	prop_ptr->count_offset   = prop.count_offset;

	// specify that the user wants this property 
	elem->store_prop[index] = STORE_PROP;
}


/******************************************************************************
Specify that we want the "other" properties of the current element to be tucked
away within the user's structure.

Entry:
offset  - offset to where other_props will be stored inside user's structure

Exit:
returns pointer to structure containing description of other_props
******************************************************************************/

PLY::PlyOtherProp* PLY::get_other_properties(int offset)
{
	return get_other_properties(which_elem, offset);
}


/******************************************************************************
Describe which element is to be written next and state how many of them will
be written.

Entry:
elem_name - name of element that information is being described
nelems    - number of elements of this type to be written
******************************************************************************/

void PLY::describe_element(const char* elem_name, int nelems)
{
	// look for appropriate element 
	PlyElement *elem = find_element(elem_name);
	if (elem == NULL)
		abort_ply("error: describe_element: can't find element '%s'",elem_name);

	elem->num = nelems;

	// now this element is the current element 
	which_elem = elem;
}


/******************************************************************************
Describe a property of an element.

Entry:
prop      - the new property
******************************************************************************/

void PLY::describe_property(const PlyProperty& prop)
{
	PlyElement *elem = which_elem;

	// copy the new property 
	PlyProperty *elem_prop = new PlyProperty;
	copy_property(*elem_prop, prop);
	elem->props.push_back(elem_prop);
	elem->store_prop.push_back(NAMED_PROP);
}


/******************************************************************************
Describe what the "other" properties are that are to be stored, and where
they are in an element.
******************************************************************************/

void PLY::describe_other_properties(
	PlyOtherProp *other,
	int offset
	)
{
	// look for appropriate element 
	PlyElement *elem = find_element(other->name.c_str());
	if (elem == NULL) {
		DEBUG("warning: describe_other_properties: can't find element '%s'", other->name.c_str());
		return;
	}

	// copy the other properties 
	for (size_t i = 0; i < other->props.size(); ++i) {
		PlyProperty *prop = new PlyProperty;
		copy_property(*prop, *other->props[i]);
		elem->props.push_back(prop);
		elem->store_prop.push_back(OTHER_PROP);
	}

	// save other info about other properties 
	elem->other_size = other->size;
	elem->other_offset = offset;
}


/******************************************************************************
Pass along a pointer to "other" elements that we want to save in a given
PLY file.  These other elements were presumably read from another PLY file.

Entry:
other_elems - info about other elements that we want to store
******************************************************************************/

void PLY::describe_other_elements(PlyOtherElems* _other_elems)
{
	// ignore this call if there is no other element 
	if (_other_elems == NULL)
		return;

	// save pointer to this information 
	other_elems = _other_elems;

	// describe the other properties of this element 
	for (size_t i = 0; i < other_elems->other_list.size(); ++i) {
		OtherElem *other = &(other_elems->other_list[i]);
		element_count(other->elem_name.c_str(), other->elem_count);
		describe_other_properties(other->other_props, offsetof(OtherData,other_props));
	}
}



/******************************************************************************
Initialize the property propagation rules for an element.  Default is to
use averaging (AVERAGE_RULE) for creating all new properties.

Entry:
elem_name - name of the element that we're making the rules for

Exit:
returns pointer to the default rules
******************************************************************************/

PLY::PlyPropRules* PLY::init_rule(const char* elem_name)
{
	PlyElement *elem = find_element(elem_name);
	if (elem == NULL)
		abort_ply("error: init_rule: Can't find element '%s'", elem_name);

	PlyPropRules *rules = new PlyPropRules;
	rules->elem = elem;
	rules->max_props = 0;
	rules->rule_list = NULL;

	// see if there are other rules we should use 
	if (elem->props.empty())
		return rules;

	// default is to use averaging rule 
	rules->rule_list = new int[elem->props.size()];
	for (size_t i = 0; i < elem->props.size(); ++i)
		rules->rule_list[i] = AVERAGE_RULE;

	// try to match the element, property and rule name 
	for (PlyRuleList *list = rule_list; list != NULL; list = list->next) {

		if (!equal_strings(list->element, elem->name.c_str()))
			continue;

		int found_prop = 0;
		for (size_t i = 0; i < elem->props.size(); ++i)
			if (equal_strings(list->property, elem->props[i]->name.c_str())) {

				found_prop = 1;

				// look for matching rule name 
				for (int j = 0; rule_name_list[j].code != -1; ++j)
					if (equal_strings(list->name, rule_name_list[j].name.c_str())) {
						rules->rule_list[i] = rule_name_list[j].code;
						break;
					}
			}

		if (!found_prop) {
			DEBUG("warning: Can't find property '%s' for rule '%s'", list->property, list->name);
			continue;
		}
	}

	return rules;
}


/******************************************************************************
Modify a property propagation rule.

Entry:
rules - rules for the element
prop_name - name of the property whose rule we're modifying
rule_type - type of rule (MAXIMUM_RULE, MINIMUM_RULE, MAJORITY_RULE, etc.)
******************************************************************************/

void PLY::modify_rule(PlyPropRules* rules, const char* prop_name, int rule_type)
{
	PlyElement *elem = rules->elem;

	// find the property and modify its rule type 
	for (size_t i = 0; i < elem->props.size(); ++i)
		if (equal_strings(elem->props[i]->name.c_str(), prop_name)) {
			rules->rule_list[i] = rule_type;
			return;
		}

		// we didn't find the property if we get here 
		abort_ply("error: modify_rule: Can't find property '%s'", prop_name);
}


/******************************************************************************
Begin to create a set of properties from a set of propagation rules.

Entry:
rules - rules for the element
******************************************************************************/

void PLY::start_props(PlyPropRules* rules)
{
	// save pointer to the rules in the PLY object 
	current_rules = rules;
}


/******************************************************************************
Remember a set of properties and their weights for creating a new set of
properties.

Entry:
weight      - weights for this set of properties
other_props - the properties to use
******************************************************************************/

void PLY::weight_props(float weight, void* other_props)
{
	PlyPropRules *rules = current_rules;

	// allocate space for properties and weights, if necessary 
	if (rules->max_props == 0) {
		rules->max_props = 6;
	}
	if (rules->props.size() == rules->max_props) {
		rules->max_props *= 2;
	}
	rules->props.reserve(rules->max_props);
	rules->weights.reserve(rules->max_props);

	// remember these new properties and their weights 
	rules->props.push_back(other_props);
	rules->weights.push_back(weight);
}


/******************************************************************************
Return a pointer to a new set of properties that have been created using
a specified set of property combination rules and a given collection of
"other" properties.

Exit:
returns a pointer to the new properties
******************************************************************************/

void* PLY::get_new_props()
{
	PlyPropRules *rules = current_rules;
	PlyElement *elem = rules->elem;
	PlyProperty *prop;
	int offset;
	int type;
	ValueType val;

	// return NULL if we've got no "other" properties 
	if (elem->other_size == 0)
		return NULL;

	// create room for combined other properties 
	char *new_data = new char[elem->other_size];

	// make sure there is enough room to store values we're to combine 
	vals.resize(rules->props.size());

	// calculate the combination for each "other" property of the element 
	for (size_t i = 0; i < elem->props.size(); ++i) {

		// don't bother with properties we've been asked to store explicitly 
		if (elem->store_prop[i])
			continue;

		prop = elem->props[i];
		offset = prop->offset;
		type = prop->external_type;

		// collect together all the values we're to combine 
		for (size_t j = 0; j < rules->props.size(); ++j) {
			char* data = (char*)rules->props[j];
			void* ptr = (void *)(data + offset);
			get_stored_item((void*)ptr, type, val);
			vals[j] = ValueType2Type<double>(val, type);
		}

		// calculate the combined value 
		switch (rules->rule_list[i]) {
		case AVERAGE_RULE: {
			double sum = 0;
			double weight_sum = 0;
			for (size_t j = 0; j < rules->props.size(); ++j) {
				sum += vals[j] * rules->weights[j];
				weight_sum += rules->weights[j];
			}
			val.d = sum / weight_sum;
			break;
						   }
		case MINIMUM_RULE: {
			val.d = vals[0];
			for (size_t j = 1; j < rules->props.size(); ++j)
				if (val.d > vals[j])
					val.d = vals[j];
			break;
						   }
		case MAXIMUM_RULE: {
			val.d = vals[0];
			for (size_t j = 1; j < rules->props.size(); ++j)
				if (val.d < vals[j])
					val.d = vals[j];
			break;
						  }
		case RANDOM_RULE: {
			val.d = vals[FLOOR2INT(SEACAVE::random() * rules->props.size())];
			break;
						  }
		case SAME_RULE: {
			val.d = vals[0];
			for (size_t j = 1; j < rules->props.size(); ++j)
				if (val.d != vals[j])
					abort_ply("error: get_new_props: Error combining properties that should be the same");
			break;
						}
		default:
			abort_ply("error: get_new_props: Bad rule = %d", rules->rule_list[i]);
		}

		// store the combined value 
		store_item(new_data + offset, type, val, Float64);
	}

	return new_data;
}


/******************************************************************************
Set the list of user-specified property combination rules.
******************************************************************************/

void PLY::set_prop_rules(PlyRuleList* prop_rules)
{
	rule_list = prop_rules;
}


/******************************************************************************
Append a property rule to a growing list of user-specified rules.

Entry:
rule_list - current rule list
name      - name of property combination rule
property  - "element.property" says which property the rule affects

Exit:
returns pointer to the new rule list
******************************************************************************/

PLY::PlyRuleList* PLY::append_prop_rule(
	PlyRuleList* rule_list,
	const char* name,
	const char* property
	)
{
	char *str2;
	char *ptr;

	// find . 
	char *str = strdup(property);
	for (ptr = str; *ptr != '\0' && *ptr != '.'; ptr++) ;

	// split string at . 
	if (*ptr == '.') {
		*ptr = '\0';
		str2 = ptr + 1;
	} else {
		DEBUG("warning: Can't find property '%s' for rule '%s'", property, name);
		return rule_list;
	}

	PlyRuleList *rule = new PlyRuleList;
	rule->name = name;
	rule->element = str;
	rule->property = str2;
	rule->next = NULL;

	// either start rule list or append to it 
	if (rule_list == NULL)
		rule_list = rule;
	else {                      // append new rule to current list 
		PlyRuleList *rule_ptr = rule_list;
		while (rule_ptr->next != NULL)
			rule_ptr = rule_ptr->next;
		rule_ptr->next = rule;
	}

	// return pointer to list 
	return rule_list;
}


/******************************************************************************
See if a name matches the name of any property combination rules.

Entry:
name - name of rule we're trying to match

Exit:
returns 1 if we find a match, 0 if not
******************************************************************************/

int PLY::matches_rule_name(const char* name)
{
	for (int i = 0; rule_name_list[i].code != -1; ++i)
		if (equal_strings(rule_name_list[i].name.c_str(), name))
			return 1;
	return 0;
}
