#pragma once

#include <iostream>
#include <vector>
#include <string>

#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

#include <cpp_common/mathlib.h>


namespace my_dxf
{

class DxfFace
{
public:
	DxfFace()
	{
		m_idx[0] = m_idx[1] = m_idx[2] = m_idx[3] = 0;
	}

	size_t m_idx[4]; // offset = 1
};

class DxfPolyline
{
public:
	std::string m_Layer;
	vector<lm::vector3f> m_Vertices;
	vector<DxfFace> m_Faces;
};
typedef std::vector<DxfPolyline> DxfPolylines;

class Dxf3DFace
{
public:
	std::string m_Layer;
	vector<lm::vector3f> m_Vertices;
	DxfFace m_Face;
};	
typedef std::vector<Dxf3DFace> Dxf3DFaces;


class DxfDataSet
{
public:
	DxfDataSet(std::istream& ist) : m_ist(ist)
	{
	}

	bool LoadNext(void)
	{
		if( m_ist.eof() ) return false;

		std::string strCode;
		std::getline( m_ist , strCode );
		if( m_ist.eof() ) return false;
		std::getline( m_ist , m_data );

		boost::trim( strCode );
		m_code = boost::lexical_cast<unsigned int>(strCode);

		return true;
	}

	template<typename T>
	T get_value(void){ return boost::lexical_cast<T>(m_data); }

public:
	unsigned int m_code;
	std::string m_data;

private:
	std::istream& m_ist;
};


class DxfObject
{
public:
	void Load(std::istream& ist);

private:
	void Load_Entities(DxfDataSet& dataset);

	void Load_Polyline(DxfDataSet& dataset, DxfPolyline& polyline);
	void Load_3DFace(DxfDataSet& dataset, Dxf3DFace& face);

public:
	DxfPolylines m_Polylines;
	Dxf3DFaces   m_3DFaces;
};


void DxfObject::Load(std::istream& ist)
{
	DxfDataSet dataset(ist);

	if( dataset.LoadNext() == false ) return;

	for(;;)
	{
		if( dataset.m_data == "EOF" )
			break;
		else if( dataset.m_data == "ENTITIES" )
			Load_Entities( dataset );

		if( dataset.LoadNext() == false ) return;
	}
}

void DxfObject::Load_Entities(DxfDataSet& dataset)
{
	dataset.LoadNext();

	for(;;)
	{
		if( dataset.m_data == "ENDSEC" ) 
		{
			break;
		}
		else if( dataset.m_data == "POLYLINE" )
		{
			m_Polylines.push_back( DxfPolyline() );
			Load_Polyline( dataset , m_Polylines.back() );
		}
		else if( dataset.m_data == "3DFACE" )
		{
			m_3DFaces.push_back( Dxf3DFace() );
			Load_3DFace( dataset , m_3DFaces.back() );
		}
		else
		{
			if( dataset.LoadNext() == false ) return;
		}
	}
}

void DxfObject::Load_Polyline(DxfDataSet& dataset, DxfPolyline& polyline)
{
	dataset.LoadNext();

	for(;;)
	{
		if( dataset.m_data == "SEQEND" )
		{
			return;
		}
		else if( dataset.m_data == "VERTEX" )
		{
			dataset.LoadNext();

			vector3f v;
			DxfFace f;
			int loaded_type = 0;
			for(;;)
			{
				if( dataset.m_code == 0 ) break;

				switch( dataset.m_code )
				{
				case  8 : polyline.m_Layer = dataset.m_data ; break;

				case 10 : v.x        = dataset.get_value<float>()  ;  break;
				case 20 : v.y        = dataset.get_value<float>()  ;  break;
				case 30 : v.z        = dataset.get_value<float>()  ;  break;

				case 71 : f.m_idx[0] = dataset.get_value<size_t>() ;  break;
				case 72 : f.m_idx[1] = dataset.get_value<size_t>() ;  break;
				case 73 : f.m_idx[2] = dataset.get_value<size_t>() ;  break;
				case 74 : f.m_idx[3] = dataset.get_value<size_t>() ;  break;

				default : break;
				};

				switch( dataset.m_code )
				{
				case 10 : loaded_type = 1 ; break;
				case 71 : loaded_type = 2 ; break;
				default : break;
				}

				if( dataset.LoadNext() == false ) return;
			}

			switch( loaded_type )
			{
			case 1 : polyline.m_Vertices.push_back( v ); break;
			case 2 : polyline.m_Faces.push_back( f );    break;
			default: break;
			}
		}
		else
		{
			if( dataset.LoadNext() == false ) return;
		}
	}
}

void DxfObject::Load_3DFace(DxfDataSet& dataset, Dxf3DFace& face)
{
	dataset.LoadNext();
	
	vector3f vary[4];
	for(;;)
	{
		if( dataset.m_code == 0 ) break;
		switch( dataset.m_code )
		{
		case 8 : face.m_Layer = dataset.m_data ; break;

		case 10 : vary[0].x = dataset.get_value<float>() ; break;
		case 20 : vary[0].y = dataset.get_value<float>() ; break;
		case 30 : vary[0].z = dataset.get_value<float>() ; break;
		case 11 : vary[1].x = dataset.get_value<float>() ; break;
		case 21 : vary[1].y = dataset.get_value<float>() ; break;
		case 31 : vary[1].z = dataset.get_value<float>() ; break;
		case 12 : vary[2].x = dataset.get_value<float>() ; break;
		case 22 : vary[2].y = dataset.get_value<float>() ; break;
		case 32 : vary[2].z = dataset.get_value<float>() ; break;
		case 13 : vary[3].x = dataset.get_value<float>() ; break;
		case 23 : vary[3].y = dataset.get_value<float>() ; break;
		case 33 : vary[3].z = dataset.get_value<float>() ; break;

		default : break;
		}

		switch( dataset.m_code )
		{
		case 10 : face.m_Face.m_idx[0] = 1 ; break;
		case 11 : face.m_Face.m_idx[1] = 2 ; break;
		case 12 : face.m_Face.m_idx[2] = 3 ; break;
		case 13 : face.m_Face.m_idx[3] = 4 ; break;
		}

		if( dataset.LoadNext() == false ) return;
	}

	face.m_Vertices.clear();
	if( face.m_Face.m_idx[3] != 0 )
	{
		face.m_Vertices.push_back(vary[0]);
		face.m_Vertices.push_back(vary[1]);
		face.m_Vertices.push_back(vary[2]);
		face.m_Vertices.push_back(vary[3]);
	}
	else
	{
		if( face.m_Face.m_idx[2] != 0 )
		{
			face.m_Vertices.push_back(vary[0]);
			face.m_Vertices.push_back(vary[1]);
			face.m_Vertices.push_back(vary[2]);
		}
	}
}











class GlDxfObjectDrawer
{
public:
	void Draw(DxfObject& i_dxf)
	{
		foreach( DxfPolylines , i_dxf.m_Polylines , iter )
		{
			const DxfPolyline& p = *iter;

			for( size_t i = 0 ; i < p.m_Faces.size() ; ++i )
			{
				size_t idx0 = p.m_Faces[i].m_idx[0];
				size_t idx1 = p.m_Faces[i].m_idx[1];
				size_t idx2 = p.m_Faces[i].m_idx[2];
				size_t idx3 = p.m_Faces[i].m_idx[3];
				if( idx3 != 0 )
					draw_quad( p.m_Vertices[idx0-1] , p.m_Vertices[idx1-1] , p.m_Vertices[idx2-1] , p.m_Vertices[idx3-1] );
				else if( idx2 != 0 )
					draw_triangle( p.m_Vertices[idx0-1] , p.m_Vertices[idx1-1] , p.m_Vertices[idx2-1] );
			}
		}

		foreach( Dxf3DFaces , i_dxf.m_3DFaces , iter )
		{
			Dxf3DFace& face = *iter;

			size_t idx0 = face.m_Face.m_idx[0];
			size_t idx1 = face.m_Face.m_idx[1];
			size_t idx2 = face.m_Face.m_idx[2];
			size_t idx3 = face.m_Face.m_idx[3];
			if( idx3 != 0 )
				draw_quad( face.m_Vertices[idx0-1] , face.m_Vertices[idx1-1] , face.m_Vertices[idx2-1] , face.m_Vertices[idx3-1] );
			else if( idx2 != 0 )
				draw_triangle( face.m_Vertices[idx0-1] , face.m_Vertices[idx1-1] , face.m_Vertices[idx2-1] );
		}
	}

private:
	void draw_triangle(const vector3f& v0, const vector3f& v1, const vector3f& v2)
	{
		vector3f n = cross( v1 - v0 , v2 - v0 );
		n.normalize();
		glBegin( GL_TRIANGLES );
		glNormal3f( n.x , n.y , n.z );
		glVertex3fv( v0.v() );
		glVertex3fv( v1.v() );
		glVertex3fv( v2.v() );
		glEnd();
	}

	void draw_quad(const vector3f& v0, const vector3f& v1, const vector3f& v2, const vector3f& v3)
	{
		vector3f c = ( v0 + v1 + v2 + v3 ) / 4.0f;
		vector3f n( 0.0f , 0.0f , 0.0f );
		n += cross( v0 - c , v1 - c );
		n += cross( v1 - c , v2 - c );
		n += cross( v2 - c , v3 - c );
		n += cross( v3 - c , v0 - c );
		n.normalize();
		glBegin( GL_QUADS );
		glNormal3fv( n.v() );
		glVertex3fv( v0.v() );
		glVertex3fv( v1.v() );
		glVertex3fv( v2.v() );
		glVertex3fv( v3.v() );
		glEnd();
	}

};


} // namespace my_dxf
