/*******************************************************
      Lightwave Object Loader for OSG

  Copyright (C) 2004 Marco Jez <marco.jez@poste.it>
  OpenSceneGraph is (C) 2004 Robert Osfield
********************************************************/

#include "Object.h"

#include <osg/Notify>

#include <vector>
#include <string>
#include <sstream>

using namespace lwosg;

namespace
{

	bool triangle_is_clockwise(const osg::Vec3Array *points, int a, int b, int c)
	{
		const osg::Vec3 &A = (*points)[a];
		const osg::Vec3 &B = (*points)[b];
		const osg::Vec3 &C = (*points)[c];
		float area2 = 0;
		area2 += A.x() * B.y() - B.x() * A.y();
		area2 += B.x() * C.y() - C.x() * B.y();
		area2 += C.x() * A.y() - A.x() * C.y();
		return area2 < 0;
	}

	float cylindrical_angle(float x, float y)
	{
		float r = sqrtf(x*x+y*y);
		if (r == 0) return 0;
		x /= r;
		float a;
		if (x < 0 && y >= 0) a = osg::PI_2 - acosf(-x);
		else if (x < 0 && y < 0) a = acosf(-x) + osg::PI_2;
		else if (x >= 0 && y >= 0) a = acosf(x) + 3 * osg::PI_2;
		else if (x >= 0 && y < 0) a = 3 * osg::PI_2 - acosf(x);
                else a = 0.0f;
		return a/osg::PI/2;
	}

}

Object::Object()
:	csf_(new LwoCoordFixer)
{
}

Object::Object(const iff::Chunk_list &data)
:	csf_(new LwoCoordFixer)
{
	build(data);
}

void Object::build(const iff::Chunk_list &data)
{
	clips_.clear();
	surfaces_.clear();
	layers_.clear();
	comment_ = "";
	description_ = "";

	osg::notify(osg::INFO) << "INFO: lwosg::Object: scanning clips\n";
	scan_clips(data);
	osg::notify(osg::INFO) << "INFO: lwosg::Object: scanning surfaces\n";
	scan_surfaces(data);
	osg::notify(osg::INFO) << "INFO: lwosg::Object: parsing LWO2 chunks and building object\n";
	parse(data);
	osg::notify(osg::INFO) << "INFO: lwosg::Object: generating normals\n";
	generate_normals();
	osg::notify(osg::INFO) << "INFO: lwosg::Object: generating automatic texture maps\n";
	generate_auto_texture_maps();
}

void Object::scan_clips(const iff::Chunk_list &data)
{
	for (iff::Chunk_list::const_iterator i=data.begin(); i!=data.end(); ++i) {
		const lwo2::FORM::CLIP *clip = dynamic_cast<const lwo2::FORM::CLIP *>(*i);
		if (clip) {
			clips_[clip->index] = Clip(clip);
		}
	}
}

void Object::scan_surfaces(const iff::Chunk_list &data)
{
	for (iff::Chunk_list::const_iterator i=data.begin(); i!=data.end(); ++i) {
		const lwo2::FORM::SURF *surf = dynamic_cast<const lwo2::FORM::SURF *>(*i);
		if (surf) {
			surfaces_[surf->name] = Surface(surf, clips_);
		}
	}
}

void Object::parse(const iff::Chunk_list &data)
{
	typedef std::vector<std::string> String_list;
	String_list tag_strings;

	Layer current_layer;

	for (iff::Chunk_list::const_iterator i=data.begin(); i!=data.end(); ++i) {

		const lwo2::FORM::LAYR *layr = dynamic_cast<const lwo2::FORM::LAYR *>(*i);
		if (layr) {
			if (!current_layer.units().empty() || current_layer.get_layer_chunk()) {
				layers_[current_layer.number()] = current_layer;
			}
			current_layer.set_layer_chunk(layr);
			current_layer.units().clear();
		}

		const lwo2::FORM::PNTS *pnts = dynamic_cast<const lwo2::FORM::PNTS *>(*i);
		if (pnts) {
			Unit new_unit;
			for (lwo2::FORM::PNTS::Point_list::const_iterator i=pnts->point_location.begin(); i!=pnts->point_location.end(); ++i) {
				new_unit.points()->push_back(csf_->fix_point(osg::Vec3(i->X, i->Y, i->Z) /*+ current_layer.pivot()*/));				
			}
			new_unit.shares().assign(new_unit.points()->size(), Unit::Index_list());
			current_layer.units().push_back(new_unit);
		}

		const lwo2::FORM::VMAP *vmap = dynamic_cast<const lwo2::FORM::VMAP *>(*i);
		if (vmap && !current_layer.units().empty()) {
			std::string type(vmap->type.id, 4);
			if (type == "WGHT") {
				if (vmap->dimension != 1) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " vertex map dimension: " << vmap->dimension << std::endl;
					continue;
				}
				VertexMap *new_map = current_layer.units().back().weight_maps()->getOrCreate(vmap->name);
				for (lwo2::FORM::VMAP::Mapping_list::const_iterator i=vmap->mapping.begin(); i!=vmap->mapping.end(); ++i) {
					(*new_map)[i->vert.index] = osg::Vec4(i->value.at(0), 0, 0, 0);
				}
			}
			if (type == "MNVW") {
				if (vmap->dimension != 1) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " vertex map dimension: " << vmap->dimension << std::endl;
					continue;
				}
				VertexMap *new_map = current_layer.units().back().subpatch_weight_maps()->getOrCreate(vmap->name);
				for (lwo2::FORM::VMAP::Mapping_list::const_iterator i=vmap->mapping.begin(); i!=vmap->mapping.end(); ++i) {
					(*new_map)[i->vert.index] = osg::Vec4(i->value.at(0), 0, 0, 0);
				}
			}
			if (type == "TXUV") {
				if (vmap->dimension != 2) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " vertex map dimension: " << vmap->dimension << std::endl;
					continue;
				}
				VertexMap *new_map = current_layer.units().back().texture_maps()->getOrCreate(vmap->name);
				for (lwo2::FORM::VMAP::Mapping_list::const_iterator i=vmap->mapping.begin(); i!=vmap->mapping.end(); ++i) {
					(*new_map)[i->vert.index] = osg::Vec4(i->value.at(0), i->value.at(1), 0, 0);
				}
			}
			if (type == "RGB ") {
				if (vmap->dimension != 3) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " vertex map dimension: " << vmap->dimension << std::endl;
					continue;
				}
				VertexMap *new_map = current_layer.units().back().rgb_maps()->getOrCreate(vmap->name);
				for (lwo2::FORM::VMAP::Mapping_list::const_iterator i=vmap->mapping.begin(); i!=vmap->mapping.end(); ++i) {
					(*new_map)[i->vert.index] = osg::Vec4(i->value.at(0), i->value.at(1), i->value.at(2), 1);
				}
			}
			if (type == "RGBA") {
				if (vmap->dimension != 4) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " vertex map dimension: " << vmap->dimension << std::endl;
					continue;
				}
				VertexMap *new_map = current_layer.units().back().rgba_maps()->getOrCreate(vmap->name);
				for (lwo2::FORM::VMAP::Mapping_list::const_iterator i=vmap->mapping.begin(); i!=vmap->mapping.end(); ++i) {
					(*new_map)[i->vert.index] = osg::Vec4(i->value.at(0), i->value.at(1), i->value.at(2), i->value.at(3));
				}
			}
			if (type == "MORF") {
				if (vmap->dimension != 3) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " vertex map dimension: " << vmap->dimension << std::endl;
					continue;
				}
				VertexMap *new_map = current_layer.units().back().displacement_maps()->getOrCreate(vmap->name);
				for (lwo2::FORM::VMAP::Mapping_list::const_iterator i=vmap->mapping.begin(); i!=vmap->mapping.end(); ++i) {
					(*new_map)[i->vert.index] = osg::Vec4(i->value.at(0), i->value.at(1), i->value.at(2), 0);
				}
			}
			if (type == "SPOT") {
				if (vmap->dimension != 3) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " vertex map dimension: " << vmap->dimension << std::endl;
					continue;
				}
				VertexMap *new_map = current_layer.units().back().spot_maps()->getOrCreate(vmap->name);
				for (lwo2::FORM::VMAP::Mapping_list::const_iterator i=vmap->mapping.begin(); i!=vmap->mapping.end(); ++i) {
					(*new_map)[i->vert.index] = osg::Vec4(csf_->fix_point(osg::Vec3(i->value.at(0), i->value.at(1), i->value.at(2))), 0);
				}
			}
		}

		const lwo2::FORM::POLS *pols = dynamic_cast<const lwo2::FORM::POLS *>(*i);
		if (pols && !current_layer.units().empty()) {
			std::string type(pols->type.id, 4);
			if (type != "FACE") {
				osg::notify(osg::INFO) << "INFO: Lwo2Object: polygon list of type " << type << " not supported, rendering may be inaccurate" << std::endl;
			}
			for (lwo2::FORM::POLS::Polygon_list::const_iterator i=pols->polygons.begin(); i!=pols->polygons.end(); ++i) {
				Polygon polygon;
				bool must_invert_winding = csf_->invert_winding();

				// FIX FOR A LIGHTWAVE BUG? MAYBE IT IS A FEATURE, I DON'T KNOW...
				// if the first vertex is at a concave corner, we must invert the winding of the polygon
				// beacuse it appears as flipped in Lighwave. Also, we tell the polygon to invert its normal.
				// (not implemented yet)
				/*if (i->vert.size() >= 4) {
					if (must_invert_winding == triangle_is_clockwise(current_layer.units().back().points(), i->vert.front().index, i->vert.back().index, i->vert[1].index)) {
						must_invert_winding = !must_invert_winding;
						polygon.set_invert_normal(true);
					}
				}*/

				if (must_invert_winding) {
					for (unsigned j=0; j<i->numvert; ++j) {
						int index = i->vert.at((i->numvert-j)%i->numvert).index;
						polygon.indices().push_back(index);
						current_layer.units().back().shares().at(index).push_back(current_layer.units().back().polygons().size());
					}
				} else {
					for (unsigned j=0; j<i->numvert; ++j) {
						int index = i->vert.at(j).index;
						polygon.indices().push_back(index);
						current_layer.units().back().shares().at(index).push_back(current_layer.units().back().polygons().size());
					}
				}
				current_layer.units().back().polygons().push_back(polygon);
			}
		}

		const lwo2::FORM::TAGS *tags = dynamic_cast<const lwo2::FORM::TAGS *>(*i);
		if (tags) {
			tag_strings = tags->tag_string;
		}

		const lwo2::FORM::PTAG *ptag = dynamic_cast<const lwo2::FORM::PTAG *>(*i);
		if (ptag && !current_layer.units().empty()) {
			std::string type(ptag->type.id, 4);
			if (type == "SURF") {
				for (lwo2::FORM::PTAG::Mapping_list::const_iterator i=ptag->mapping.begin(); i!=ptag->mapping.end(); ++i) {
					current_layer.units().back().polygons().at(i->poly.index).set_surface(&surfaces_[tag_strings.at(i->tag)]);
				}
			}
			if (type == "PART") {
				for (lwo2::FORM::PTAG::Mapping_list::const_iterator i=ptag->mapping.begin(); i!=ptag->mapping.end(); ++i) {
					current_layer.units().back().polygons().at(i->poly.index).set_part_name(tag_strings.at(i->tag));
				}
			}
			if (type == "SMGP") {
				for (lwo2::FORM::PTAG::Mapping_list::const_iterator i=ptag->mapping.begin(); i!=ptag->mapping.end(); ++i) {
					current_layer.units().back().polygons().at(i->poly.index).set_smoothing_group(tag_strings.at(i->tag));
				}
			}
		}

		const lwo2::FORM::VMAD *vmad = dynamic_cast<const lwo2::FORM::VMAD *>(*i);
		if (vmad && !current_layer.units().empty()) {
			std::string type(vmad->type.id, 4);
			if (type == "WGHT") {
				if (vmad->dimension != 1) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " discontinuous vertex map dimension: " << vmad->dimension << std::endl;
					continue;
				}
				for (lwo2::FORM::VMAD::Mapping_list::const_iterator i=vmad->mapping.begin(); i!=vmad->mapping.end(); ++i) {
					VertexMap *this_map = current_layer.units().back().polygons().at(i->poly.index).weight_maps()->getOrCreate(vmad->name);
					(*this_map)[i->vert.index] = osg::Vec4(i->value.at(0), 0, 0, 0);
				}				
			}
			if (type == "TXUV") {
				if (vmad->dimension != 2) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " discontinuous vertex map dimension: " << vmad->dimension << std::endl;
					continue;
				}
				for (lwo2::FORM::VMAD::Mapping_list::const_iterator i=vmad->mapping.begin(); i!=vmad->mapping.end(); ++i) {
					VertexMap *this_map = current_layer.units().back().polygons().at(i->poly.index).texture_maps()->getOrCreate(vmad->name);
					(*this_map)[i->vert.index] = osg::Vec4(i->value.at(0), i->value.at(1), 0, 0);
				}				
			}
			if (type == "RGB ") {
				if (vmad->dimension != 3) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " discontinuous vertex map dimension: " << vmad->dimension << std::endl;
					continue;
				}
				for (lwo2::FORM::VMAD::Mapping_list::const_iterator i=vmad->mapping.begin(); i!=vmad->mapping.end(); ++i) {
					VertexMap *this_map = current_layer.units().back().polygons().at(i->poly.index).rgb_maps()->getOrCreate(vmad->name);
					(*this_map)[i->vert.index] = osg::Vec4(i->value.at(0), i->value.at(1), i->value.at(2), 1);
				}				
			}
			if (type == "RGBA") {
				if (vmad->dimension != 4) {
					osg::notify(osg::WARN) << "Warning: Lwo2Object: invalid " << type << " discontinuous vertex map dimension: " << vmad->dimension << std::endl;
					continue;
				}
				for (lwo2::FORM::VMAD::Mapping_list::const_iterator i=vmad->mapping.begin(); i!=vmad->mapping.end(); ++i) {
					VertexMap *this_map = current_layer.units().back().polygons().at(i->poly.index).rgba_maps()->getOrCreate(vmad->name);
					(*this_map)[i->vert.index] = osg::Vec4(i->value.at(0), i->value.at(1), i->value.at(2), i->value.at(3));
				}				
			}
		}

		const lwo2::FORM::DESC *desc = dynamic_cast<const lwo2::FORM::DESC *>(*i);
		if (desc) {
			description_ = desc->description_line;
		}

		const lwo2::FORM::TEXT *text = dynamic_cast<const lwo2::FORM::TEXT *>(*i);
		if (text) {
			comment_ = text->comment;
		}

	}

	if (!current_layer.units().empty() || current_layer.get_layer_chunk()) {
		layers_[current_layer.number()] = current_layer;
	}
}

void Object::generate_normals()
{
	for (Layer_map::iterator i=layers_.begin(); i!=layers_.end(); ++i) {
		for (Layer::Unit_list::iterator j=i->second.units().begin(); j!=i->second.units().end(); ++j) {
			j->generate_normals();
		}
	}
}

void Object::generate_auto_texture_maps()
{
	for (Surface_map::iterator i=surfaces_.begin(); i!=surfaces_.end(); ++i) {
		for (Surface::Block_map::iterator j=i->second.blocks().begin(); j!=i->second.blocks().end(); ++j) {
			Block &block = j->second;
			if (block.get_type() == "IMAP") {
				if (block.get_image_map().projection == Image_map::UV) continue;

				Image_map::Axis_type axis = block.get_image_map().axis;

				std::ostringstream oss;
				oss << "Auto_map_" << &block;
				std::string map_name = oss.str();

				osg::notify(osg::DEBUG_INFO) << "DEBUG INFO: lwosg::Object: creating automatic texture map '" << map_name << "'\n";

				for (Layer_map::iterator k=layers_.begin(); k!=layers_.end(); ++k) {
					for (Layer::Unit_list::iterator h=k->second.units().begin(); h!=k->second.units().end(); ++h) {

						osg::ref_ptr<VertexMap> new_map = new VertexMap;
						(*h->texture_maps())[map_name] = new_map.get();

						if (block.get_image_map().projection == Image_map::FRONT_PROJECTION) {
							osg::notify(osg::WARN) << "Warning: lwosg::Object: front projection is not supported" << std::endl;
						}

						if (block.get_image_map().projection == Image_map::CUBIC) {

							for (Unit::Polygon_list::iterator p=h->polygons().begin(); p!=h->polygons().end(); ++p) {

								Polygon &poly = *p;
								osg::ref_ptr<VertexMap> local_uv_map = poly.texture_maps()->getOrCreate(map_name);

								osg::Vec3 N = csf_->fix_vector(poly.face_normal(h->points()));

								Image_map::Axis_type axis = Image_map::X;
								if (N.y() > N.x() && N.y() > N.z()) axis = Image_map::Y;
								if (-N.y() > N.x() && -N.y() > N.z()) axis = Image_map::Y;
								if (N.z() > N.x() && N.z() > N.y()) axis = Image_map::Z;
								if (-N.z() > N.x() && -N.z() > N.y()) axis = Image_map::Z;

								for (Polygon::Index_list::iterator i=poly.indices().begin(); i!=poly.indices().end(); ++i) {

									// fetch vertex
									osg::Vec3 P = csf_->fix_point((*h->points())[*i]);

									// setup scale/translation/rotation
									P = block.setup_texture_point(P);

									osg::Vec2 uv;
									switch (axis) {
										case Image_map::X: uv.set(P.z(), P.y()); break;
										case Image_map::Y: uv.set(P.x(), P.z()); break;
										case Image_map::Z: uv.set(P.x(), P.y()); break;
										default: ;
									}
									uv += osg::Vec2(0.5f, 0.5f);

									osg::Vec4 map_value(uv.x(), uv.y(), 0, 0);

									if ((new_map->find(*i) != new_map->end()) && (map_value != (*new_map.get())[*i])) {
										(*local_uv_map.get())[*i] = map_value;
									} else {
										(*new_map.get())[*i] = map_value;
									}
								}
							}
						} else {

							for (unsigned p=0; p<h->points()->size(); ++p) {

								// fetch vertex
								osg::Vec3 P = csf_->fix_point((*h->points())[p]);

								// setup scale/translation/rotation
								P = block.setup_texture_point(P);

								osg::Vec2 uv;

								if (block.get_image_map().projection == Image_map::PLANAR) {
									switch (axis) {
										case Image_map::X: uv.set(P.z(), P.y()); break;
										case Image_map::Y: uv.set(P.x(), P.z()); break;
										case Image_map::Z: uv.set(P.x(), P.y()); break;
										default: ;
									}
									uv += osg::Vec2(0.5f, 0.5f);
								}

								if (block.get_image_map().projection == Image_map::CYLINDRICAL) {
									switch (axis) {
										case Image_map::X: uv.set(cylindrical_angle(-P.z(), -P.y()), P.x()); break;
										case Image_map::Y: uv.set(cylindrical_angle(P.x(), P.z()), P.y()); break;
										case Image_map::Z: uv.set(cylindrical_angle(P.x(), -P.y()), P.z()); break;
										default: ;
									}								
									uv.x() *= block.get_image_map().wrap_amount_w;
									uv += osg::Vec2(0, 0.5f);
								}

								if (block.get_image_map().projection == Image_map::SPHERICAL) {
									float r = P.length();
									if (r != 0) {
										switch (axis) {
											case Image_map::X: uv.set(cylindrical_angle(-P.z(), -P.y()), (asinf(P.x()/r) + osg::PI_2) / osg::PI); break;
											case Image_map::Y: uv.set(cylindrical_angle(P.x(), P.z()), (asinf(P.y()/r) + osg::PI_2) / osg::PI); break;
											case Image_map::Z: uv.set(cylindrical_angle(P.x(), -P.y()), (asinf(P.z()/r) + osg::PI_2) / osg::PI); break;
											default: ;
										}
									}
									uv.x() *= block.get_image_map().wrap_amount_w;
									uv.y() *= block.get_image_map().wrap_amount_h;
								}

								(*new_map.get())[p] = osg::Vec4(uv.x(), uv.y(), 0, 0);
							}
						}
					}
				}

				block.get_image_map().uv_map = map_name;
				block.get_image_map().projection = Image_map::UV;
			}
		}
	}
}
