////////////////////////////////////////////////////////////////////
// PLY.h
//
// Copyright 2023 cDc@seacave
// Distributed under the Boost Software License, Version 1.0
// (See http://www.boost.org/LICENSE_1_0.txt)
//
// PLY polygon files parser.
// (originally by Greg Turk, heavily modified by cDc@seacave)
// 
// A PLY file contains a single polygonal _object_.
// 
// An object is composed of lists of _elements_.  Typical elements are
// vertices, faces, edges and materials.
// 
// Each type of element for a given object has one or more _properties_
// associated with the element type.  For instance, a vertex element may
// have as properties three floating-point values x,y,z and three unsigned
// chars for red, green and blue.


#ifndef __SEACAVE_PLY_H__
#define __SEACAVE_PLY_H__


// I N C L U D E S /////////////////////////////////////////////////


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

#define PLY_OKAY   0                  // ply routine worked okay 
#define PLY_ERROR -1                  // error in ply routine 


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

class IO_API PLY
{
public:
	// scalar data types supported by PLY format
	enum FileType {
		ASCII     = 1,                // ascii PLY file
		BINARY_BE = 2,                // binary PLY file, big endian
		BINARY_LE = 3,                // binary PLY file, little endian
	};
	enum DataType {
		StartType = 0,
		Int8      = 1,
		Int16     = 2,
		Int32     = 3,
		Uint8     = 4,
		Uint16    = 5,
		Uint32    = 6,
		Float32   = 7,
		Float64   = 8,
		EndType   = 9,
	};
	enum ListType {
		SCALAR    = 0,
		LIST      = 1,
		STRING    = 2,
	};
	enum RuleType {
		AVERAGE_RULE  = 1,
		MAJORITY_RULE = 2,
		MINIMUM_RULE  = 3,
		MAXIMUM_RULE  = 4,
		SAME_RULE     = 5,
		RANDOM_RULE   = 6,
	};

	// description of a property
	struct PlyProperty {
		std::string name;             // property name 
		int external_type;            // file's data type 
		int internal_type;            // program's data type 
		int offset;                   // offset bytes of prop in a struct 

		int is_list;                  // 0 = scalar, 1 = list, 2 = char string 
		int count_external;           // file's count type 
		int count_internal;           // program's count type 
		int count_offset;             // offset byte for list count 
	};

	// description of an element
	struct PlyElement {
		std::string name;             // element name 
		int num;                      // number of elements in this object 
		int size;                     // size of element (bytes) or -1 if variable 
		std::vector<PlyProperty*> props;// list of properties in the file 
		std::vector<char> store_prop; // flags: property wanted by user? 
		int other_offset;             // offset to un-asked-for props, or -1 if none
		int other_size;               // size of other_props structure 
	};

	// describes other properties in an element
	struct PlyOtherProp {
		std::string name;             // element name 
		int size;                     // size of other_props 
		std::vector<PlyProperty*> props;// list of properties in other_props 
	};

	// for storing other_props for an other element
	struct OtherData {
		void* other_props;
	};

	// data for one "other" element
	struct OtherElem {
		std::string elem_name;        // names of other elements 
		int elem_count;               // count of instances of each element 
		OtherData** other_data;       // actual property data for the elements 
		PlyOtherProp* other_props;    // description of the property data 
	};

	// "other" elements, not interpreted by user
	struct PlyOtherElems {
		std::vector<OtherElem> other_list;// list of data for other elements 
	};

	// rules for combining "other" properties
	struct PlyPropRules {
		PlyElement* elem;             // element whose rules we are making 
		int* rule_list;               // types of rules (AVERAGE_PLY, MAJORITY_PLY, etc.) 
		uint32_t max_props;           // maximum number of properties we have room for now 
		std::vector<void*> props;     // list of properties we're combining 
		std::vector<float> weights;   // list of weights of the properties 
	};

	struct PlyRuleList {
		LPCSTR name;                  // name of the rule 
		char* element;                // name of element that rule applies to 
		char* property;               // name of property that rule applies to 
		struct PlyRuleList* next;     // pointer for linked list of rules 
	};

	// property propagation rules
	struct RuleName {
		int code;
		std::string name;
	};

protected:
	struct ValueType {
		union {
			int8_t i8;
			int16_t i16;
			int32_t i32;
			uint8_t u8;
			uint16_t u16;
			uint32_t u32;
			float f;
			double d;
		};
	};

public:
	PLY();
	~PLY();

	bool read(LPCSTR);
	bool read(SEACAVE::ISTREAM*);
	bool write(LPCSTR, int, LPCSTR*, int, size_t memBufferSize=0);
	bool write(SEACAVE::OSTREAM*, int, LPCSTR*, int, size_t memBufferSize=0);
	void release();

	void set_legacy_type_names();

	void get_info(float*, int*);

	void append_comment(const char*);
	void append_obj_info(const char*);
	void copy_comments(const PLY&);
	void copy_obj_info(const PLY&);
	std::vector<std::string>& get_comments();
	std::vector<std::string>& get_obj_info();

	void describe_property(const char*, const PlyProperty&);
	void describe_property(const char*, int nprops, const PlyProperty*);
	void get_property(const char*, PlyProperty*);
	void get_element(void*);

	PlyOtherElems* get_other_element();

	int get_element_list(std::vector<std::string>&) const;
	void setup_property(const PlyProperty&);
	LPCSTR setup_element_read(int, int*);
	PlyOtherProp* get_other_properties(int);

	int get_elements_count() const { return (int)elems.size(); }
	int get_current_element_count() const { return which_elem->num; }
	void element_count(const char*, int);
	void describe_element(const char*, int);
	void describe_property(const PlyProperty&);
	void describe_other_properties(PlyOtherProp*, int);
	void describe_other_elements( PlyOtherElems*);
	void get_element_setup(const char*, int, PlyProperty*);
	int get_element_description(const char*, std::vector<PlyProperty*>&) const;
	void element_layout(const char*, int, int, PlyProperty*);

	bool header_complete();
	void put_element_setup(const char*);
	void put_element(const void*);
	void put_other_elements();

	PlyPropRules* init_rule(const char*);
	void modify_rule(PlyPropRules*, const char*, int);
	void start_props(PlyPropRules*);
	void weight_props(float, void*);
	void* get_new_props();
	void set_prop_rules(PlyRuleList*);
	PlyRuleList* append_prop_rule(PlyRuleList*, const char*, const char*);

	// find an element in a ply's list 
	PlyElement* find_element(const char*) const;
	// find a property in an element's list 
	int find_property(PlyElement*, const char*) const;

	static inline bool equal_strings(const char* s1, const char* s2) { return _tcscmp(s1, s2) == 0; }

protected:
	// write to a file the word describing a PLY file data type 
	void write_scalar_type(int);

	// read a line from a file and break it up into separate words 
	typedef SEACAVE::TokenInputStream<false> STRISTREAM;
	char** get_words(STRISTREAM&, int*, char**);

	// write an item to a file 
	void write_binary_item(const ValueType&, int, int);
	void write_ascii_item(const ValueType&, int, int);

	// return the value of a stored item 
	static void get_stored_item(const void*, int, ValueType&);

	// get binary or ascii item and store it according to ptr and type 
	void get_binary_item(int, ValueType&);
	static void get_ascii_item(const char*, int, ValueType&);

	// store a value into where a pointer and a type specify 
	static void store_item(void*, int, const ValueType&, int);

	// add information to a PLY file descriptor 
	void add_element(const char**, int);
	void add_property(const char**, int);
	void add_comment(const char*);
	void add_obj_info(const char*);

	// get a bunch of elements from a file 
	void ascii_get_element(uint8_t*);
	void binary_get_element(uint8_t*);

	void setup_other_props(PlyElement*);
	PlyOtherProp* get_other_properties(PlyElement*, int);
	PlyOtherProp* get_other_properties(const char*, int);

	int matches_rule_name(const char*);
	int get_prop_type(const char*);

	// copy a property 
	static void copy_property(PlyProperty&, const PlyProperty&);

	// return the value as T stored in val as type 
	template <typename T>
	static inline T ValueType2Type(const ValueType& val, int type) {
		switch (type) {
		case Int8:
			return (T)val.i8;
		case Int16:
			return (T)val.i16;
		case Int32:
			return (T)val.i32;
		case Uint8:
			return (T)val.u8;
		case Uint16:
			return (T)val.u16;
		case Uint32:
			return (T)val.u32;
		case Float32:
			return (T)val.f;
		case Float64:
			return (T)val.d;
		}
		ASSERT("error: bad type" == NULL);
		return T(0);
	}

protected:
	// description of PLY file
	std::string filename;             // file name 
	int file_type;                    // ascii or binary 
	float version;                    // version number of file 
	std::vector<PlyElement*> elems;   // list of elements 
	std::vector<std::string> comments;// list of comments 
	std::vector<std::string> obj_info;// list of object info items 
	PlyElement* which_elem;           // element we're currently reading or writing 
	PlyOtherElems* other_elems;       // "other" elements from a PLY file 
	PlyPropRules* current_rules;      // current propagation rules 
	PlyRuleList* rule_list;           // rule list from user 
	std::vector<double> vals;         // rule list from user 

	union {
		SEACAVE::ISTREAM* istream;    // input file pointer
		SEACAVE::OSTREAM* ostream;    // output file pointer
	};
	SEACAVE::MemFile* mfp;            // mem-file pointer (optional)
	const char* const* write_type_names; // names of scalar types to be used for writing (new types by default)

	static const char* const type_names[9]; // names of scalar types 
	static const char* const old_type_names[9]; // old names of types for backward compatibility 
	static const int ply_type_size[9];
	static const RuleName rule_name_list[7];
};

#endif // __SEACAVE_PLY_H__
