/*
  defines bob_dom_bob - class that can manipulate the BOB1 format and all the 
  underlying objects
*/

#ifndef BOB_DOM_BOB_INCLUDED
#define BOB_DOM_BOB_INCLUDED

#include "bob_dom_base.h"
#include "settings.h"
#include "../common/ext_utils.h"
#include "../common/ext_array.h"
#include "../common/ext_simple_list.h"
#include "../common/strutils.h"

class bob_dom_bob;

class bob_dom_bodies;
class bob_dom_body;

class bob_dom_materials;
class bob_dom_material3;
class bob_dom_material5;

class bob_dom_points;
class bob_dom_point;

class bob_dom_part;
class bob_dom_face;

class bob_point_map;

enum X2BobType
{
	bobX2,
	bobX3
};


class bob_dom_weight
{
	public:
		struct value
		{
			static const int multiplier = 65536;
		
			short boneIdx;
			int boneCoefficient;
			
			void toFile(bob_dom_otextfilestream& os)
			{
				os << boneIdx << (float)(boneCoefficient / (double)multiplier);
			}

		};
	
	private:
		
	public:
		typedef ext::list<value>::iterator iterator;
		typedef ext::list<value>::const_iterator const_iterator;
		
		bob_error_codes errorCode;
		ext::list<value> values;
		
		bob_dom_weight() { errorCode=e_noError; }
		bob_dom_weight(const bob_dom_weight& other) 
		{ 
			errorCode=other.errorCode; 
			for(const_iterator &it=other.values.begin(); it!=other.values.end(); ++it){
				values.push_back(*it);
			}
		}
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os, int idx);
};

class bob_dom_weights : public bob_dom_section, public ext::array<bob_dom_weight*>
{
	public:
		static const hdr_begin=BOB_SECTION_NAME_WEIGHT_BEGIN;
		static const hdr_end=BOB_SECTION_NAME_WEIGHT_END;
		
		typedef ext::simple_list<bob_dom_weight*> WeightList;
		
		WeightList new_weights;
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os, const bob_point_map *pointMap);
		
		~bob_dom_weights()
		{
			for(iterator &it=begin(); it!=end(); ++it){
				delete *it;
			}
			for(WeightList::iterator &it=new_weights.begin(); it!=new_weights.end(); ++it){
				delete *it;
			}
		}
};

class bob_dom_bones : public bob_dom_section, public ext::list<char*>
{
	public:
		static const hdr_begin=BOB_SECTION_NAME_BONE_BEGIN;
		static const hdr_end=BOB_SECTION_NAME_BONE_END;
		
		bool load(bob_dom_ibinaryfilestream &is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
		
		~bob_dom_bones()
		{
			for(iterator &it=begin(); it!=end(); ++it){
				delete *it;
			}
		}
};

class bob_dom_material
{
	public:
		enum materialType
		{
			mat1,
			mat3,
			mat5,
			mat6
		};
		
		materialType type;
		short index;
		
		bob_error_codes errorCode;
		
		bob_dom_material() { index=0; }
		virtual ~bob_dom_material() { }

		virtual bool load(bob_dom_ibinaryfilestream& is) = 0;
		
		virtual bool toFile(bob_dom_obinaryfilestream& os) = 0;
		virtual bool toFile(bob_dom_otextfilestream& os) = 0;
};

class bob_dom_material1 : public bob_dom_material
{
	public:
		struct rgb
		{
			short r, g, b;
			rgb() { r=g=b=0; }
		};
		
		struct pair
		{
			short value;
			short strength;
			pair() { value=0; strength=0; }
		};
		
		short textureID;
		rgb ambient;
		rgb diffuse;
		rgb specular;
		
		bob_dom_material1()
		{
			type=mat1;
			index=0; textureID=0; errorCode=e_noError;
		}
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
};

inline
bob_dom_ibinaryfilestream& operator >> (bob_dom_ibinaryfilestream& is, bob_dom_material1::pair& right)
{
	is >> right.value >> right.strength;
	return is;
}

inline
bob_dom_obinaryfilestream& operator << (bob_dom_obinaryfilestream& os, bob_dom_material1::pair& right)
{
	os << right.value << right.strength;
	return os;
}

inline
bob_dom_ibinaryfilestream& operator >> (bob_dom_ibinaryfilestream& is, bob_dom_material1::rgb& r)
{
	is >> r.r >> r.g >> r.b;
	return is;
}

inline
bob_dom_obinaryfilestream& operator << (bob_dom_obinaryfilestream& os, bob_dom_material1::rgb& right)
{
	os << right.r << right.g << right.b;
	return os;
}

inline
bob_dom_otextfilestream& operator << (bob_dom_otextfilestream& os, bob_dom_material1::pair& right)
{
	int f=os.flags();
	os << noSemicolons;
	os << right.value << ';' << right.strength << "; ";
	os.flags(f);
	return os;
}

inline
bob_dom_otextfilestream& operator << (bob_dom_otextfilestream& os, bob_dom_material1::rgb& right)
{
	int f=os.flags();
	os << noSemicolons;
	os << right.r << ';' << right.g << ';' << right.b << "; ";
	os.flags(f);
	return os;
}

class bob_dom_material3 : public bob_dom_material1
{
	public:
		int transparency;
		short selfIllumination;
		pair shininess;
		bool destinationBlend;
		bool twoSided;
		bool wireframe;
		short textureValue;
		pair enviromentMap;
		pair bumpMap;
		
		typedef bob_dom_material1 base;
		
		bob_dom_material3() {
			type=mat3;
			transparency=0; selfIllumination=0; destinationBlend=0;
			twoSided=0; wireframe=0; textureValue=0; 
		}
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
};

class bob_dom_material5 : public bob_dom_material3
{
	private:
		
	public:
		typedef bob_dom_material3 base;
		
		pair lightMap;
		
		bob_dom_material5() { type=mat5; }
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
		
};

class material6_value
{
	private:
		static char *m_stringTypes[];
		static int m_stringTypesCount;
		
	public:
		enum Type 
		{
			typeLong=0,
			typeBool=1,
			typeFloat=2,
			typeFloat4=5,
			typeString=8
		};
		
		struct float4
		{
			float f[4];
		};
		
		union TagValue
		{
			bool b;
			int i;
			char *psz;
			float f;
			float4 f4;
		} val;

		Type type;
		char *name;
		
		material6_value() { val.i=0; type=typeLong; name=0; }
		material6_value(Type type) { val.i=0; this->type=type; name=0; }

		~material6_value() { delete name; if(type==typeString) delete val.psz; }
		
		static const char * typeName(int type) { return type < m_stringTypesCount ? m_stringTypes[type] : ""; }
		const char * typeName() const { return typeName(type); }
		static int typeNameCount() { return m_stringTypesCount; }
		
		bob_error_codes load(bob_dom_ibinaryfilestream& is);
		bool toFile(bob_dom_otextfilestream& os);
		bool toFile(bob_dom_obinaryfilestream& os);
};

inline bob_dom_ibinaryfilestream& operator >> (bob_dom_ibinaryfilestream& is, material6_value::float4 &val)
{
	for(int i=0; i < 4; i++){
		is >> val.f[i];
	}
	return is;
}

inline bob_dom_obinaryfilestream& operator << (bob_dom_obinaryfilestream& os, material6_value::float4 &val)
{
	for(int i=0; i < 4; i++){
		os << val.f[i];
	}
	return os;
}

class bob_dom_material6_values : public ext::simple_list<material6_value*>
{
	public:
		typedef ext::simple_list<material6_value*> base;
		typedef base::iterator iterator;
		
	public:
		~bob_dom_material6_values()
		{
			for(iterator it=begin(); it!=end(); ++it){
				delete *it;
			}
		}
		
		bob_error_codes load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_otextfilestream& os);
		bool toFile(bob_dom_obinaryfilestream& os);
};

class bob_dom_material6 : public bob_dom_material
{
	public:
		class Big
		{
			public:
				static const int flag=0x2000000;
				
				char *effect;
				short technique;
				
				bob_dom_material6_values values;
				
				Big() { effect=0; technique=0; }
				~Big() { delete[] effect; }
				
				bob_error_codes load(bob_dom_ibinaryfilestream& is);
				
				bool toFile(bob_dom_otextfilestream& os);
				bool toFile(bob_dom_obinaryfilestream& os);
		};
		
		class Small
		{
			public:	
				struct pair
				{
					char *texture;
					short strength;
					
					pair() { texture=0; }
					~pair() { delete[] texture; }
					
					pair& operator = (const pair& other) 
					{ 
						strcreate(texture, other.texture); strength=other.strength; return *this; 
					}
				};
				
				char *textureFile;
				bob_dom_material1::rgb ambient, diffuse, specular;
				int transparency;
				short selfIllumination;
				bob_dom_material1::pair shininess;
				bool destinationBlend;
				bool twoSided;
				bool wireframe;
				short textureValue;
				pair enviromentMap;
				pair bumpMap;
				pair lightMap;
				pair map4, map5;
				
				Small() { 
					textureFile=0; transparency=0; selfIllumination=0; destinationBlend=false; twoSided=false; wireframe=false;
					textureValue=0;
				}
				~Small() 
				{ 
					delete[] textureFile; 
				}
				
				bob_error_codes load(bob_dom_ibinaryfilestream& is, int flags);
				
				bool toFile(bob_dom_otextfilestream& os);
				bool toFile(bob_dom_obinaryfilestream& os);
		};
		
	public:
#ifdef small // some fucking definition from RPC (god knows where it's included)
	#undef small
#endif
		int flags;
		Small *small;
		Big *big;
		
		bob_dom_material6() { type=mat6; flags=0; small=0; big=0; }
		~bob_dom_material6() { delete small; delete big; }
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
};

inline
bob_dom_ibinaryfilestream& operator >> (bob_dom_ibinaryfilestream& is, bob_dom_material6::Small::pair& right)
{
	is >> right.texture >> right.strength;
	return is;
}

inline
bob_dom_obinaryfilestream& operator << (bob_dom_obinaryfilestream& os, bob_dom_material6::Small::pair& right)
{
	static char *empty="";
	os << (right.texture ? right.texture : empty) << right.strength;
	return os;
}

inline
bob_dom_otextfilestream& operator << (bob_dom_otextfilestream& os, bob_dom_material6::Small::pair& right)
{
	int old=os.flags();
	os << noSemicolons << (*(right.texture)==0 ? "NULL" : right.texture) << ';' << autoSemicolons << right.strength;
	os.flags(old);
	return os;
}

class bob_dom_materials : public bob_dom_section, public ext::list<bob_dom_material*>
{
	private:
		
	public:
		static const hdr_mat6_begin=BOB_SECTION_NAME_MAT6_BEGIN;
		static const hdr_mat5_begin=BOB_SECTION_NAME_MAT5_BEGIN;
		static const hdr_end=BOB_SECTION_NAME_MAT_END;
		
		bob_dom_materials() { }
		~bob_dom_materials() 
		{ 
			for(iterator &it=begin(); it!=end(); ++it) {
				delete *it;
			}
		}
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
		
		bool load(bob_dom_ibinaryfilestream& is);
};

class bob_dom_point
{
	public:
		static double multiplier;
		typedef ext::list<int>::iterator iterator;
		typedef ext::list<int>::const_iterator const_iterator;
		typedef ext::pair<int, int>  pair;
		
		short type;
		ext::list<int> values;
		bob_error_codes errorCode;
		
		int x,y,z;
		pair textureCoords;
		
		bob_dom_point() { errorCode=e_noError; }
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os, int idx);
		
		bool operator ==(const bob_dom_point& right) const
		{
			return x==right.x && y==right.y && z==right.z;
		}
};

// must be included AFTER the bob_dom_point is defined
#include "../x2bc_common/bob_point_map.h"

class bob_dom_points : public bob_dom_section
{
	public:
		static const hdr_begin=BOB_SECTION_NAME_POINT_BEGIN;
		static const hdr_end=BOB_SECTION_NAME_POINT_END;
		
		bob_point_map map;
		
		ext::simple_list<bob_dom_point*> new_points;
		typedef ext::simple_list<bob_dom_point*>::iterator PointIterator;
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
		
		~bob_dom_points()
		{
			for(PointIterator &it=new_points.begin(); it!=new_points.end(); ++it){
				delete *it;
			}
		}
};

class bob_x3uv
{
	public:
		int idx; // ??
		float values[6];
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_otextfilestream& os, int matIdx);
		bool toFile(bob_dom_obinaryfilestream& os);
};

class bob_x3uv_list : public ext::array<bob_x3uv*>
{
	public:
		~bob_x3uv_list() 
		{
			for(iterator &it=begin(); it!=end(); ++it) {
				delete *it;
			}
		}
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_otextfilestream& os, int matIdx);
		bool toFile(bob_dom_obinaryfilestream& os);
		
		iterator search(int faceIdx)
		{
			int min=0, mid, res, max=(int)size() - 1;
		
			if(max==-1) return end();
			do {
				mid=((int)max + min) / 2;
				res=faceIdx - at(mid)->idx;
				if(res <= 0) max=mid - 1;
				if(res >= 0) min=mid + 1;
			}
			while(min <= (int)max);
			return faceIdx==at(mid)->idx ? (m_first + mid) : end();
		}
};

class bob_dom_face
{
	public:
		int values[4];
		bob_error_codes errorCode;
	
		bob_dom_face() { errorCode=e_noError; }
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
};

class bob_face_list : public ext::list<bob_dom_face*>, public bob_with_errors
{
	public:
		int materialIndex;
		bob_x3uv_list x3uvlist;

		bob_face_list() { materialIndex=0; }
		~bob_face_list() 
		{ 
			for(iterator &it=begin(); it!=end(); ++it) {
				delete *it;
			}
		}
		
		bool load(bob_dom_ibinaryfilestream& is, bool x3data);
		bool toFile(bob_dom_obinaryfilestream& os, bool x3data);
};

class bob_dom_part_x3vals
{
	private:
		int m_values[10];
		
	public:
		bob_dom_part_x3vals() { memset(m_values, 0, sizeof(m_values)); }
		
		bool load(bob_dom_ibinaryfilestream& is);
		bool toFile(bob_dom_otextfilestream& os, bool bCommentItself);
		bool toFile(bob_dom_obinaryfilestream& os);
		
		int& operator [] (int idx) { return m_values[idx]; }
};

class bob_dom_part : public bob_with_errors, public ext::list<bob_face_list*>
{
	public:
		static const double multiplier;
		static const int x3flag=0x10000000;
		
		int flags;
		
		bob_dom_part_x3vals values;

		bob_dom_part()	{ flags=0; }
		
		~bob_dom_part()
		{
			for(iterator &it=begin(); it!=end(); ++it){
				delete *it;
			}
		}
		
		// find the appropriate face list based on material index
		bob_face_list * operator[] (int matIdx)
		{
			for(reverse_iterator &it=rbegin(); it!=rend(); ++it){
				if(it->materialIndex==matIdx)
					return *it;
			}
			return NULL;
		}
		
		// find or create an appropriate face list based on material index
		bob_face_list * facelist(int matIdx)
		{
			bob_face_list *p=(*this)[matIdx];
			if(p==NULL){
				p=new bob_face_list();
				push_back(p);
				p->materialIndex=matIdx;
			}
			return p;
		}
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os, const Settings *settings, const bob_dom_materials *materials, const bob_point_map *pointMap, int idx);
		
};

class bob_dom_parts : public bob_dom_section, public ext::list<bob_dom_part*>
{
	public:
		static const hdr_begin=BOB_SECTION_NAME_PART_BEGIN;
		static const hdr_end=BOB_SECTION_NAME_PART_END;
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os, const Settings *settings, const bob_dom_materials *materials, const bob_point_map *pointMap);
		
		~bob_dom_parts()
		{
			for(iterator &it=begin(); it!=end(); ++it){
				delete *it;
			}
		}
};

class bob_dom_body : public bob_with_errors
{
	public:
		bob_dom_bones bones;
		bob_dom_points points;
		bob_dom_parts parts;
		bob_dom_weights weights;
		
		int bodySize;
		int bodyFlags;
		
		bob_dom_body() { bodySize=0; bodyFlags=0; }
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os, const Settings *settings, const bob_dom_materials *materials, int idx);
};

class bob_dom_bodies : public bob_dom_section, public bob_dom_cantainer<bob_dom_body>
{
	public:
		static const hdr_begin=BOB_SECTION_NAME_BODY_BEGIN;
		static const hdr_end=BOB_SECTION_NAME_BODY_END;
		
		bool load(bob_dom_ibinaryfilestream& is);
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os, const Settings *settings, const bob_dom_materials *materials);
};

class bob_dom_bob : public bob_dom_section
{
	private:
		const Settings *m_settings;
		
	public:
		typedef bob_dom_bodies::iterator BodyIterator;
		
		bob_dom_info info;
		bob_dom_materials materials;
		bob_dom_bodies bodies;
		
		static const hdr_begin=BOB_SECTION_NAME_BOB_BEGIN;
		static const hdr_end=BOB_SECTION_NAME_BOB_END;
		
		bob_dom_bob(const Settings *settings) { m_settings=settings; }
		
		bool toFile(bob_dom_obinaryfilestream& os);
		bool toFile(bob_dom_otextfilestream& os);
		
		bool load(bob_dom_ibinaryfilestream& is);
		
};

inline
bob_dom_otextfilestream& operator <<(bob_dom_otextfilestream& os, bob_dom_bob& right)
{
	right.toFile(os);
	return os;
}

#endif // !defined(BOB_DOM_BOB_INCLUDED)