// Copyright (C) 2013 mocchi

#include "CONObjSceneNode.h"

#include <vector>

#include "gl_mangle.h"
#include <GL/gl.h>
#include <GL/glu.h>

#include "opennurbs_gl.h"

using namespace irr;
using namespace irr::core;
using namespace irr::scene;
using namespace irr::video;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

static ON_Color GetColorFromAttr(const ON_3dmObjectAttributes &attr, const ON_ObjectArray<ON_Layer> &layers){
	ON_Color col;
	switch(attr.ColorSource()){
		case ON::color_from_layer:
			col = layers[attr.m_layer_index].Color();
			break;
		case ON::color_from_object:
		case ON::color_from_material: // 良くわからない
			col = attr.m_color;
			break;
	}
	return col;
}


//	CONObjSceneNode(ONX_Model &model, scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id)
//		: scene::ISceneNode(parent, mgr, id)
//	{
CONObjSceneNode::CONObjSceneNode(const ONX_Model *model, const ON_Mesh *mesh, ON_3dmObjectAttributes &attr, ISceneNode* parent, ISceneManager* mgr, s32 id)
		: ISceneNode(parent, mgr, id) {

	ONModel = model;
	ONAttribute = attr;
	IsSelectable = true;
	Selected = false;
	setAutomaticCulling(EAC_OFF);

	ON_Color col = GetColorFromAttr(attr, model->m_layer_table);

	Material.Wireframe = false;
	Material.Lighting = true;
	Material.AmbientColor = irr::video::SColor(255,col.Red()/4,col.Green()/4,col.Blue()/4);
	Material.DiffuseColor = irr::video::SColor(255,col.Red()/4,col.Green()/4,col.Blue()/4);
	Material.SpecularColor = irr::video::SColor(255,255,255,255);
	Material.EmissiveColor = irr::video::SColor(255,col.Red()/4,col.Green()/4,col.Blue()/4);
	Material.Shininess = 0.5f;
	Material.FrontfaceCulling = false;
	Material.BackfaceCulling = false;
	Material.ZBuffer = true;
	Material.ZWriteEnable = true;

	WMaterial.Wireframe = true;
	WMaterial.Lighting = false;
	WMaterial.Thickness = 4.0f;

	ON_Mesh cmesh;
	if (!mesh->HasVertexNormals()){
		cmesh.CopyFrom(mesh);
		cmesh.ComputeVertexNormals();
	}

	{
		const ON_3fPointArray &pts = mesh->m_V;
		const ON_3fVectorArray &nrms = (mesh->HasVertexNormals()) ? mesh->m_N : cmesh.m_N;
		for (s32 i = 0; i < pts.Count(); ++i){
			ON_Color col = (mesh->m_C.Count() > i) ? mesh->m_C[i] : ON_Color(0,0,0,0);
			SColor irrcol(255-col.Alpha(), col.Red(), col.Green(), col.Blue());

			Vertices.push_back(video::S3DVertex(
				pts[i].x, pts[i].y, pts[i].z,
				nrms[i].x, nrms[i].y, nrms[i].z,
				irrcol, 0, 0
			));
		}
		u32 prevIndices = Indices.size();
		for (s32 i = 0; i < mesh->m_F.Count(); ++i){
			const ON_MeshFace &face = mesh->m_F[i];
			Indices.push_back(prevIndices + face.vi[0]);
			Indices.push_back(prevIndices + face.vi[1]);
			Indices.push_back(prevIndices + face.vi[2]);
			if (face.vi[2] != face.vi[3]){
				Indices.push_back(prevIndices + face.vi[2]);
				Indices.push_back(prevIndices + face.vi[3]);
				Indices.push_back(prevIndices + face.vi[0]);
			}
		}
	}

	if (Vertices.size() == 0){
		Vertices.push_back(video::S3DVertex(0,0,10, 1,1,0,
				video::SColor(255,0,255,255), 0, 1));
		Vertices.push_back(video::S3DVertex(10,0,-10, 1,0,0,
				video::SColor(255,255,0,255), 1, 1));
		Vertices.push_back(video::S3DVertex(0,20,0, 0,1,1,
				video::SColor(255,255,255,0), 1, 0));
		Vertices.push_back(video::S3DVertex(-10,0,-10, 0,0,1,
				video::SColor(255,0,255,0), 0, 0));

		u32 indices[] = {	0,2,3, 2,1,3, 1,0,3, 2,0,1	};
		for (s32 i = 0; i < 12; ++i) Indices.push_back(indices[i]);
	}

	/*
	The Irrlicht Engine needs to know the bounding box of a scene node.
	It will use it for automatic culling and other things. Hence, we
	need to create a bounding box from the 4 vertices we use.
	If you do not want the engine to use the box for automatic culling,
	and/or don't want to create the box, you could also call
	irr::scene::ISceneNode::setAutomaticCulling() with irr::scene::EAC_OFF.
	*/
	Box.reset(Vertices[0].Pos);
	for (u32 i=1; i<Vertices.size(); ++i)
		Box.addInternalPoint(Vertices[i].Pos);
}

CONObjSceneNode::CONObjSceneNode(const ONX_Model *model, const ON_Brep *brep, ON_3dmObjectAttributes &attr, ISceneNode* parent, ISceneManager* mgr, s32 id)
		: ISceneNode(parent, mgr, id) {

	ONModel = model;
	ONAttribute = attr;
	IsSelectable = true;
	Selected = false;
	setAutomaticCulling(EAC_OFF);

	ON_Color col = GetColorFromAttr(attr, model->m_layer_table);

	Material.Wireframe = false;
	Material.Lighting = true;
	Material.AmbientColor = irr::video::SColor(255-col.Alpha(),col.Red()*3/4,col.Green()*3/4,col.Blue()*3/4);
	Material.DiffuseColor = irr::video::SColor(255-col.Alpha(),col.Red()*3/4,col.Green()*3/4,col.Blue()*3/4);
	Material.SpecularColor = irr::video::SColor(255-col.Alpha(),255,255,255);
	Material.EmissiveColor = irr::video::SColor(255-col.Alpha(),col.Red()/2,col.Green()/2,col.Blue()/2);
	Material.Shininess = 10.0f;
	Material.FrontfaceCulling = false;
	Material.BackfaceCulling = false;
	Material.GouraudShading = true;
	Material.ZBuffer = true;

//	Material.ZBuffer = false;
//	Material.MaterialType = EMT_TRANSPARENT_ADD_COLOR;

	WMaterial.Wireframe = true;
	WMaterial.Lighting = false;
	WMaterial.Thickness = 0.2f;

	// GLU_NURBS_TESSELLATORの場合でも、GLUからglGetIntegerv(GL_VIEWPORT, ...)
	// が呼び出される。テセレーションの細かさがビューポートの大きさに依存する？
	::mglViewport(0, 0, 640, 480);
	GLUnurbs *nurbs = ::gluNewNurbsRenderer();
	::gluNurbsProperty( nurbs, GLU_NURBS_MODE, GLU_NURBS_TESSELLATOR);
	::gluNurbsProperty( nurbs, GLU_SAMPLING_TOLERANCE,   20.0f );
	::gluNurbsProperty( nurbs, GLU_PARAMETRIC_TOLERANCE, 1.0f );
	::gluNurbsProperty( nurbs, GLU_DISPLAY_MODE, static_cast<GLfloat>(GLU_FILL) );
	//::gluNurbsProperty( nurbs, GLU_AUTO_LOAD_MATRIX, 0 );
	// GLU_AUTO_LOAD_MATRIXを0にするとテセレーションが汚くなる
	//::gluNurbsProperty( nurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_POLYGON );
	//::gluNurbsProperty( nurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_PATCH );
	//::gluNurbsProperty( nurbs, GLU_SAMPLING_METHOD, static_cast<GLfloat>(GLU_PATH_LENGTH) );
	::gluNurbsProperty( nurbs, GLU_SAMPLING_METHOD, static_cast<GLfloat>(GLU_PARAMETRIC_ERROR) );
	//::gluNurbsProperty( nurbs, GLU_SAMPLING_METHOD, GLU_DOMAIN_DISTANCE );
	::gluNurbsProperty( nurbs, GLU_CULLING, static_cast<GLfloat>(GL_FALSE) );

	struct Callback{
		CONObjSceneNode *this_;
		GLenum cur_type;
		map<vector3df, int> v2ind;
		array<int> indices_b2e;
		array<vector3df> norms;
		bool rev_nrm;

		static void GLAPIENTRY Begin(GLenum type, void *obj){
			Callback *this_ = static_cast<Callback *>(obj);
			this_->cur_type = type;
			this_->indices_b2e.clear();
			this_->norms.clear();
		}
		static void GLAPIENTRY Vertex(GLfloat *v, void *obj){
			Callback *this_ = static_cast<Callback *>(obj);
			vector3df vertex(v[0], v[1], v[2]);
			map<vector3df, int>::Node *node = this_->v2ind.find(vertex);
			int curind;
			array<video::S3DVertex> &Vertices = this_->this_->Vertices;
			if (!node){
				curind = static_cast<int>(Vertices.size());
				Vertices.push_back(video::S3DVertex(
					v[0], v[1], v[2], 0, 0, 0, video::SColor(255,0,0,0), v[3], v[4]
				));
				this_->v2ind.insert(vertex, curind);
			}else curind = node->getValue();
			this_->indices_b2e.push_back(curind);
		}
		static void GLAPIENTRY Normal(GLfloat *v, void *obj){
			Callback *this_ = static_cast<Callback *>(obj);
			if (this_->rev_nrm){
				this_->norms.push_back(vector3df(-v[0], -v[1], -v[2]));
			}else{
				this_->norms.push_back(vector3df(v[0], v[1], v[2]));
			}
		}
		static void GLAPIENTRY End(void *obj){
			Callback *this_ = static_cast<Callback *>(obj);

			array<video::S3DVertex> &Vertices = this_->this_->Vertices;
			array<u32> &Indices = this_->this_->Indices;
			array<int> &indices_b2e = this_->indices_b2e;
			for (u32 i = 0; i < indices_b2e.size(); ++i){
				video::S3DVertex &vertex = this_->this_->Vertices[indices_b2e[i]];
				if (this_->norms.size() > i) vertex.Normal = this_->norms[i];
			}

			GLenum type = this_->cur_type;
			if (type == GL_TRIANGLES){
				for (u32 i = 0, i3 = 0; i3 < indices_b2e.size() - 2; ++i, i3 += 3){
					for (u32 h = 0; h < 3; ++h) Indices.push_back(indices_b2e[i3+h]);
				}
			}else if (type == GL_TRIANGLE_STRIP || type == GL_QUAD_STRIP){
				for (u32 i = 0; i < indices_b2e.size() - 2; ++i){
					for (u32 h = 0; h < 3; ++h) Indices.push_back(indices_b2e[i+h]);
				}
			}else if (type == GL_TRIANGLE_FAN){
				for (u32 i = 1; i < indices_b2e.size() - 1; ++i){
					Indices.push_back(indices_b2e[0]);
					for (u32 h = 0; h < 2; ++h) Indices.push_back(indices_b2e[i+h]);
				}
			}else if (type == GL_QUADS){
				static const int IND_ORDER[] = {0, 1, 2, 2, 3, 0};
				for (u32 i = 0, i4 = 0; i4 < indices_b2e.size() - 3; ++i, i4 += 4){
					for (u32 h = 0; h < 6; ++h) Indices.push_back(indices_b2e[i4+IND_ORDER[h]]);
				}
			}
		}
	}cbobj;

	cbobj.this_ = this;
	::gluNurbsCallbackData( nurbs, &cbobj );
	::gluNurbsCallback( nurbs, GLU_NURBS_BEGIN_DATA, reinterpret_cast<_GLUfuncptr>(Callback::Begin));
	::gluNurbsCallback( nurbs, GLU_NURBS_VERTEX_DATA, reinterpret_cast<_GLUfuncptr>(Callback::Vertex));
	::gluNurbsCallback( nurbs, GLU_NURBS_NORMAL_DATA, reinterpret_cast<_GLUfuncptr>(Callback::Normal));
	::gluNurbsCallback( nurbs, GLU_NURBS_END_DATA, reinterpret_cast<_GLUfuncptr>(Callback::End));

	for (int i = 0; i < brep->m_F.Count(); ++i){
		const ON_BrepFace &face = brep->m_F[i];
		cbobj.rev_nrm = face.m_bRev;
		ON_GL(face, nurbs);
	}

	::gluDeleteNurbsRenderer( nurbs );

	if (Vertices.size()){
		Box.reset(Vertices[0].Pos);
		for (u32 i=1; i<Vertices.size(); ++i)
			Box.addInternalPoint(Vertices[i].Pos);
	}
}

/*
Before it is drawn, the irr::scene::ISceneNode::OnRegisterSceneNode()
method of every scene node in the scene is called by the scene manager.
If the scene node wishes to draw itself, it may register itself in the
scene manager to be drawn. This is necessary to tell the scene manager
when it should call irr::scene::ISceneNode::render(). For
example, normal scene nodes render their content one after another,
while stencil buffer shadows would like to be drawn after all other
scene nodes. And camera or light scene nodes need to be rendered before
all other scene nodes (if at all). So here we simply register the
scene node to render normally. If we would like to let it be rendered
like cameras or light, we would have to call
SceneManager->registerNodeForRendering(this, SNRT_LIGHT_AND_CAMERA);
After this, we call the actual
irr::scene::ISceneNode::OnRegisterSceneNode() method of the base class,
which simply lets also all the child scene nodes of this node register
themselves.
*/
void CONObjSceneNode::OnRegisterSceneNode() {
	if (IsVisible) SceneManager->registerNodeForRendering(this);

	ISceneNode::OnRegisterSceneNode();
}

/*
In the render() method most of the interesting stuff happens: The
Scene node renders itself. We override this method and draw the
tetraeder.
*/
void CONObjSceneNode::render() {
	if (Indices.size() == 0 || Vertices.size() == 0) return;

	ON_Color col = Selected ? ON_Color(255, 128, 0, 255) : GetColorFromAttr(ONAttribute, ONModel->m_layer_table);

	Material.SpecularColor = irr::video::SColor(255-col.Alpha(),255,255,255);
	Material.AmbientColor = irr::video::SColor(255-col.Alpha(),col.Red()*3/4,col.Green()*3/4,col.Blue()*3/4);
	Material.DiffuseColor = irr::video::SColor(255-col.Alpha(),col.Red()*3/4,col.Green()*3/4,col.Blue()*3/4);
	Material.EmissiveColor = irr::video::SColor(255-col.Alpha(),col.Red()/2,col.Green()/2,col.Blue()/2);

	using namespace irr::core;
	video::IVideoDriver* driver = SceneManager->getVideoDriver();

	matrix4 trans = AbsoluteTransformation;

	driver->setTransform(video::ETS_WORLD, trans);
	driver->setMaterial(Material);
	driver->drawVertexPrimitiveList(&Vertices[0], Vertices.size(), &Indices[0], Indices.size() / 3, video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_32BIT);
#if 0
	driver->setMaterial(WMaterial);
	driver->drawVertexPrimitiveList(&Vertices[0], Vertices.size(), &Indices[0], Indices.size() / 3, video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_32BIT);
#endif
}

/*
And finally we create three small additional methods.
irr::scene::ISceneNode::getBoundingBox() returns the bounding box of
this scene node, irr::scene::ISceneNode::getMaterialCount() returns the
amount of materials in this scene node (our tetraeder only has one
material), and irr::scene::ISceneNode::getMaterial() returns the
material at an index. Because we have only one material here, we can
return the only one material, assuming that no one ever calls
getMaterial() with an index greater than 0.
*/
const aabbox3d<f32>& CONObjSceneNode::getBoundingBox() const {
	return Box;
}

u32 CONObjSceneNode::getMaterialCount() const {
	return 1;
}

SMaterial& CONObjSceneNode::getMaterial(u32 i) {
	return Material;
}

//void Grid(){
//	}

