#ifndef ATS_VP_H
#define ATS_VP_H

#include <GL/glut.h>
#include <string.h>
#include <iostream.h>
#include <fstream.h>
#include <vector>
#include <list>
#include <deque>
#include <algorithm>
#include <math.h>
#include "ats_point.h"
#include "ats_component.h"
#include "ats_composite.h"
#include "ats_leaf.h"
#include "ats_exception.h"

/********************************************************
* class Vp
* ポイント、ポリゴンのインデックス、変換行列がメンバ。
* コンストラクタでのファイルの読み込みが失敗した場合や、
* Compositeで定義される関数が呼ばれた場合は例外
* bad_operationをthrowする。
********************************************************/

namespace ats
{
	template<class T>
	class Vp : public Leaf3D<T>
	{
	protected:
		std::vector<T> m_nrm_array;
		std::vector<T> m_pnt_array;
		std::vector<T> m_nrm_array2;
		std::vector< std::vector<int> > m_idx_array;
		// box == x.max x.min y.max y.min z.max z.min
		Point<T> m_box[6];
		// scale x, y, z
		T m_scale;
		Vp(){}

		void set_scale();
		void read_pnt(::ifstream& vp);
		void read_idx(::ifstream& vp);
		void cul_normal();

	public:
		Vp(const char* filename);
		virtual ~Vp(){};
		virtual void Draw();
		virtual void UpdateTime(double) {};
	};
	
	// implementation ---------------------------------------------------------------------

	// オブジェクトが画面内に表示されるよう調整 ----------------------------
	// (最大値がsizeで表示されるように)
	template<class T>
	void Vp<T>::set_scale(){
		for(int i=0; i < m_pnt_array.size()/3; i++){
			// jが2つ進むとkは1つ進む。
			for(int j = 0, k = 0; j < 6; j++, k++){
				if(m_box[j].X() < m_pnt_array[i*3 + k]){
					m_box[j].SetXYZ(m_pnt_array[i*3],
									m_pnt_array[i*3+1],
									m_pnt_array[i*3+2]);
				}
				++j;
				if(m_box[j].X() > m_pnt_array[i*3 + k]){
					m_box[j].SetXYZ(m_pnt_array[i*3],
									m_pnt_array[i*3+1],
									m_pnt_array[i*3+2]);
				}
			}
		}
		T obj_size, scale_size = 1.5;
		obj_size = fabs(m_box[0].X() - m_box[1].X());
		obj_size = (obj_size == 0) ? 0.000000000001 : obj_size;
		T sx = scale_size / obj_size;

		obj_size = fabs(m_box[2].Y() - m_box[3].Y());
		obj_size = (obj_size == 0) ? 0.000000000001 : obj_size;
		T sy = scale_size / obj_size;

		obj_size = fabs(m_box[4].Z() - m_box[5].Z());
		obj_size = (obj_size == 0) ? 0.000000000001 : obj_size;
		T sz = scale_size / obj_size;

		m_scale = (sx < sy) ? sx : sy;
		m_scale = (m_scale < sz) ? m_scale : sz;
		::cout << "scale : " << m_scale << endl;

	}


	// vpファイルの頂点の読み込み ----------------------------------------
	template<class T>
	void Vp<T>::read_pnt(::ifstream& vp)
	{
		// 頂点の総数の読み込み
		int pnt_num;
		vp >> pnt_num;
		m_pnt_array.resize(pnt_num*3);

		// vpファイルの頂点の読み込み
		for(int i=0; i < pnt_num*3; i++){
			vp >> m_pnt_array[i];
		}

		// スケールの設定
		set_scale();
	}

	// vpファイルのポリゴンのインデックスの読み込み ----------------------
	template<class T>
	void Vp<T>::read_idx(::ifstream& vp)
	{
		// vpファイルのポリゴンのインデックスの読み込み
		int index_num, poly_num;
		vp >> index_num;
		m_idx_array.resize(index_num);
		for(int i=0; i < index_num; i++){
			vp >> poly_num;
			m_idx_array[i].resize(poly_num);
			for(int j=0; j < poly_num; j++){
				// デクリメントはインデックスの変換作業
				// vpファイルのインデックスは０でなく１から始まる
				vp >> m_idx_array[i][j];
				--(m_idx_array[i][j]);
			}
		}
	}

	// 面の法線計算 --------------------------------------------------------
	template<class T>
	void Vp<T>::cul_normal()
	{
		using namespace std;
		/*******************************************************
		* 注意!!：
		* pnt nrm が付く変数は
		* 1. 同じpnt nrm 系からの変換
		* 2. 多次元配列からの変換(m_idx_array[sface][0])
		* 3. for(;;)の増加分が３ずつ
		* 4. 保持する型がxyz成分でなくインデックスの場合(pnt_cnt)
		* のいずれかでない限り[]の引数に*3が必要!!
		* これはvector<Point3d>のように型保証で解決すべきだが
		* OpenGLとの互換性ために伴う変換処理を回避するため
		* の処置である。が...
		* ps : 結局複雑で管理しきれなくなったので関数の中で前後を
		* 囲むコピー処理を置くことで対処。これはコードの簡略化と
		* 同時にポインタの排除に効果アリ。
		*******************************************************/
		/*******************************************************
		* データ構造
		* sface_nrm : 面の法線ベクトル
		* pnt_cnt : 頂点の面に対する共有カウンタ
		* pnt_array : ポイントをPoint<T>形式で一時保持
		* nrm_array : 法線ベクトルをPoint<T>形式で一時保持
		*******************************************************/
		int i;
		vector<Point<T> > sface_nrm(m_idx_array.size());
		vector<list<int> > pnt_cnt;
		pnt_cnt.resize(m_pnt_array.size());
		vector<Point<T> > pnt_array(m_pnt_array.size()/3);
		vector<Point<T> > nrm_array(m_pnt_array.size()/3);
		m_nrm_array.resize(m_pnt_array.size());

		// T(doubleであることが多い) [3] から Point<T>へ変換
		for(i=0; i < m_pnt_array.size() / 3; i++){
			pnt_array[i].SetXYZ(m_pnt_array[i*3], m_pnt_array[i*3+1], m_pnt_array[i*3+2]);
		}
		
		// 共有カウンターセットポイントにどの面であるかを追加
		// していく
		int sface, pnt;
		for(sface = 0; sface < m_idx_array.size(); sface++){
			for(pnt = 0; pnt < m_idx_array[sface].size(); pnt++){
				pnt_cnt[m_idx_array[sface][pnt]].push_back(sface);
			}
		}

		// 面の法線ベクトルを計算
		// 注：法線ベクトルは頂点の順でなく面の順番でセットされる
		for(sface = 0; sface < m_idx_array.size(); sface++){
			normal(pnt_array[m_idx_array[sface][0]],
				   pnt_array[m_idx_array[sface][1]],
				   pnt_array[m_idx_array[sface][2]],
				   &sface_nrm[sface]
			);
		}

		typedef std::list<int>::iterator list_itr;

		// 法線ベクトルを足し合わせた後、平均化。
		for(pnt = 0; pnt < nrm_array.size(); pnt++){
			for(list_itr sface_itr = pnt_cnt[pnt].begin(); sface_itr != pnt_cnt[pnt].end(); sface_itr++)
			{
				nrm_array[pnt] += sface_nrm[(*sface_itr)];
			}
			// 平均化
			nrm_array[pnt] /= pnt_cnt[pnt].size();
			normalize(nrm_array[pnt]);
		}

		// DrawElementsで描画するために一次元配列へコピー
		for(i = 0; i < m_pnt_array.size()/3; i++){
			m_nrm_array[i*3] = nrm_array[i].X();
			m_nrm_array[i*3+1] = nrm_array[i].Y();
			m_nrm_array[i*3+2] = nrm_array[i].Z();
		}
	}

	// コンストラクタ ------------------------------------------------------
	template<class T>
	Vp<T>::Vp(const char* filename)
	: m_scale(0)
	{
		::ifstream vp(filename, ios::nocreate);
		if(!vp.is_open()){
			throw cant_open_file("can't open file!");
		}

		read_pnt(vp);
		read_idx(vp);
		cul_normal();

		vp.close();
	}
	// 描画関数 -----------------------------------------------------------
	template<class T>
	void Vp<T>::Draw()
	{
		static GLfloat spec[] = { 0.6, 0.6, 0.6, 1 };
		static GLfloat shin[] = { 1.0 };
		static GLfloat diff[] = { 0.8, 0.8, 0.8, 0 };
		static GLfloat ambi[] = { 0.5, 0.5, 0.5, 1 };
		static GLfloat l_pos[] = { 20, 20, 20, 0 };

		glPushMatrix();
		glMaterialfv(GL_FRONT, GL_SPECULAR, spec);
		glMaterialfv(GL_FRONT, GL_SHININESS, shin);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, diff);
		glColor3f(1.0, 1.0, 1.0);
		glVertexPointer(3, GL_DOUBLE, 0, reinterpret_cast<void*>(m_pnt_array.begin()) );
		glNormalPointer(GL_DOUBLE, 0, reinterpret_cast<void*>(m_nrm_array.begin()) );
		glScaled(m_scale, m_scale, m_scale);
		for(std::vector<std::vector<int> >::iterator itr = m_idx_array.begin(); itr != m_idx_array.end(); itr++){
			glDrawElements(GL_POLYGON, itr->size(), GL_UNSIGNED_INT, reinterpret_cast<void*>(itr->begin()));
		}
		glDisable(GL_LIGHTING);
		m_nrm_array2.resize(m_nrm_array.size());
		for(int i=0; i < m_nrm_array.size(); i+=3){
			glBegin(GL_LINE_STRIP);
				//glColor3f(0,0,0);
				//glVertex3d(0,0,0);
				glColor3f(1,1,0);
				m_nrm_array2[i] = m_nrm_array[i] + m_pnt_array[i];
				m_nrm_array2[i+1] = m_nrm_array[i+1] + m_pnt_array[i+1];
				m_nrm_array2[i+2] = m_nrm_array[i+2] + m_pnt_array[i+2];
				glVertex3dv(&m_pnt_array[i]);
				glVertex3dv(&m_nrm_array2[i]);
			glEnd();
		}
		
		glPopMatrix();
	}


}

#endif // #ifndef ATS_VP_H
