/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/

/**
 * @file fugenView2.cpp
 * @brief fugenView.h の実装。通常メソッドが主。
 */
#include "stdafx.h"
#include "mg/Straight.h"
#include "mgGL/glslProgram.h"
#include "fugen.h"
#include "MainFrm.h"
#include "Undo/CameraAction.h"
#include "Misc/UserPreference.h"
#include "Common/LocateState.h"
#include "fugenView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// Local function

/////////////////////////////////////////////////////////////////////////////
// fugenView

///Load a glslprogram fram a file.
bool loadGLSLprogram(
	const char* file,
	mgGLSLProgram::GLSLShaderType shaderType, 
	mgGLSLProgram& glslProgram,
	bool link=false
){
	if(!glslProgram.compileShaderFromFile(file,shaderType)){
		std::string&  errorLog=glslProgram.log();
		COUT<<"loadGLSLprogram error:"<<errorLog.c_str()<<std::endl;
		return false;
	}
	if(link){
		if(!glslProgram.link()){
			std::string&  errorLog=glslProgram.log();
			COUT<<"loadGLSLprogram link error:"<<errorLog.c_str()<<std::endl;
			putInOutputWindow("loadGLSLprogram link error:");
			return false;
		}
		glslProgram.printActiveAttribs();
		glslProgram.printActiveUniforms();
	}
	return true;
}

// Standard OpenGL Init Stuff
bool SetupPixelFormat(HDC hdc, DWORD dwFlags){
	PIXELFORMATDESCRIPTOR pfd = {
		sizeof(PIXELFORMATDESCRIPTOR),    // size of this pfd
		1,                                // version number
		dwFlags,
		PFD_TYPE_RGBA,                    // RGBA type
		32,                               // 32-bit color depth(RGBA)
		8,0,8,0,8,0,                      // RGB color bit
		8, 0,                             //alpha color bit
		0, 0, 0, 0, 0,                    // AC R G B A
		24,                                // 24-bit z-buffer
		8,                                // 8-bit stencil buffer
		0,                                // no auxiliary buffer
		PFD_MAIN_PLANE,                   // main layer
		0,  // reserved
		0,  // layermask
		0,  // visiblemask
		0   // damagemask
	};
	int nPixelFormat = ::ChoosePixelFormat(hdc, &pfd);
	if(nPixelFormat==0)
		return false;
	return ::SetPixelFormat(hdc,nPixelFormat,&pfd)==TRUE;
}

bool SetupGLSL(mgGLSLProgram* glslP)
{
		glslP->setOpenGLVersion();

		TCHAR tCurrentDir[256];
		if(GetModuleFileName(NULL, tCurrentDir, sizeof(tCurrentDir) / sizeof(TCHAR)) == 0) {
			return false;
		}
	    PathRemoveFileSpec(tCurrentDir);
		char currentDir[512];
		memset(currentDir, 0x00, sizeof(currentDir));
		if(WideCharToMultiByte(CP_ACP, NULL, tCurrentDir, -1, currentDir, sizeof(currentDir), NULL, NULL ) == 0) {
			return false;
		}
		currentDir[strlen(currentDir)] = '\\';
		char vshader[512];
		memset(vshader, 0x00, sizeof(vshader));
		strcpy_s(vshader, (const char*)currentDir);
		strcat_s(vshader, (const char*)"mgclShader.vert");
		if(!loadGLSLprogram(vshader,mgGLSLProgram::VERTEX,*glslP)) {
			::AfxMessageBox(_T("mgclShader.vert読み込みエラー"));
			theApp.MainFrame()->PostMessage(WM_CLOSE, 0, 0);
			return false;
		}

		char fshader[512];
		memset(fshader, 0x00, sizeof(vshader));
		strcpy_s(fshader, (const char*)currentDir);
		strcat_s(fshader, (const char*)"mgclShader.frag");
		if(!loadGLSLprogram(fshader,mgGLSLProgram::FRAGMENT,*glslP,true)) {
			::AfxMessageBox(_T("mgclShader.frag読み込みエラー"));
			theApp.MainFrame()->PostMessage(WM_CLOSE, 0, 0);
			return false;
		}
		return true;
}

// ピックイベント関連
void fugenView::BeginPick(const CPoint& point){
	m_is_LButton_down = true;
	// ピック開始ポイントを記録する。
	m_button_down_point = point; // m_button_down_point は BeginTracking と共用
}

void fugenView::EndPick(){
	m_is_LButton_down = false;
}

bool fugenView::IsPicking() const{
	return m_is_LButton_down;
}

// マウスムーヴ関連
void fugenView::BeginTracking(UINT nFlags, const CPoint& point){
	m_is_RButton_down = true;
	SetCapture();

		// ドラッギング開始ポイントを記録する。
	m_button_down_point = point; // m_button_down_point は BeginPick と共用

	// ここでビュー変換モードを決める。
	bool bCtrlDown  = (nFlags & MK_CONTROL) != 0;
	bool bShiftDown = (nFlags & MK_SHIFT) != 0;

	if(bCtrlDown){//If MK_CONTROL pressed.
		m_viewop = ViewOpZoom;
	}else if(bShiftDown){//If MK_SHIFT pressed.
		m_viewop = ViewOpPan;
	}else{//If neithre MK_CONTROL nor MK_SHIFT pressed.
		m_viewop = ViewOpRotation;
	}

	if(!m_spCamWork.get()){
		m_spCamWork.reset(new CCameraAction(this));
		m_spCamWork->SaveEnvParam(IAction::ACTION_UNDO);
	}
	OnSetCursor(this,nFlags,0);
}

void fugenView::EndTracking(){
	if(m_spCamWork.get()){
		// もしトラッキングの開始位置と現在の位置が同じ場合、
		// カメラ位置が変わっていないはずなので、
		// アクションをキャンセルする。
		if(m_button_down_point == m_mousemove_new_point){
		}else{
			// カメラ操作終了直後とみなして、最終カメラ状態を保存。
			m_spCamWork->Do(); // SetLastAction のみ
			m_spCamWork->SaveEnvParam(IAction::ACTION_REDO);
			// アクションの所有権を ActionManager に委ねる。
			m_spCamWork.release();
		}
	}

	ReleaseCapture();
	m_is_RButton_down = false;
	m_viewop = ViewOpRotation;
}

bool fugenView::IsTracking()const{
	return m_is_RButton_down;
}

//Change Window's point coordinates point to OpenGL's screen coordinate.
void fugenView::change_sc(
	const CPoint& point,	//Window's point coordinates.
	int& sx,int& sy//OpenGL's screen coordinates, whose origin is (left, bottom).
)const{
	CRect rct; GetClientRect(&rct);//Get CRect information.
	sx=point.x;
	sy=rct.bottom-point.y;
}
void fugenView::change_sc(
	int cx, int cy,	//Window's point coordinates.
	int& sx,int& sy//OpenGL's screen coordinates, whose origin is (left, bottom).
)const{
	CRect rct; GetClientRect(&rct);//Get CRect information.
	sx=cx;
	sy=rct.bottom-cy;
}

//Clear current objects(clear currents of doc and erase the display lists).
void fugenView::clearCurrentObjects(){
	fugenDoc& doc=document();
	doc.set_current_object();
}

///Clear all windows' LButton down status under the document().
void fugenView::clearAllLButtonDownStatus(){
	fugenDoc& doc=document();

	//clear all the windows LButton down status.
	POSITION pos = doc.GetFirstViewPosition();
	while(pos != NULL){
		fugenView* pSView = dynamic_cast<fugenView*>(doc.GetNextView(pos));
		pSView->EndPick();
	}
}

void fugenView::convert_to_screen(
	const MGPosition& wc,
	CPoint& sc
){
	MGPosition screen;
	int w,height;
	get_window(w,height);
	project(wc,screen);
	sc.x=int(screen[0]); sc.y=height-int(screen[1]);
}
void fugenView::convert_to_screen(
	const MGPosition& wc1,
	const MGPosition& wc2,
	CPoint& sc1,
	CPoint& sc2
){
	int w,height;
	get_window(w,height);
	MGPosition screen;
	project(wc1,screen);
	sc1.x=int(screen[0]); sc1.y=height-int(screen[1]);
	project(wc2,screen);
	sc2.x=int(screen[0]); sc2.y=height-int(screen[1]);
}
void fugenView::convert_to_screen(
	const std::vector<MGPosition>& wc,
	std::vector<CPoint>& sc
){
	glm::mat4 modelM,projM;//	GLdouble modelM[16], projM[16];
	int vp[4];
	getModelViewProjectionMatrices(modelM,projM,vp);
	int height=vp[3];
	MGPosition screen;

	size_t n=wc.size();
	sc.resize(n);
	for(size_t i=0; i<n; i++){
		const MGPosition& wci=wc[i];
		project(wci,screen,&modelM,&projM);
		CPoint& sci=sc[i];
		sci.x=int(screen[0]); sci.y=height-int(screen[1]);
	}
}

void fugenView::getModelViewProjectionMatrices(
	glm::mat4& modelM,//double modelM[16],
	glm::mat4& projM,// double projM[16],
	int vp[4]
){
	get_model_matrix(modelM);
	get_viewport(vp);
	get_projection_matrix(&vp[0],projM);
}

void fugenView::copy_view_env(const fugenView& other){
	if(is_standard_view() && other.is_standard_view()){
		copy(other);
	}
}

//Return the current command tool.
MGCommandBase* fugenView::current_command_tool(){
	return GetDocument()->current_command_tool();
}

//display the (x,y,z) position in status bar.
void fugenView::display_position_in_status_bar(const MGPosition& pt)const{
	fugenStatusBar& status=theStatusBar();
	status.DisplayCoord(pt[0], pt[1], pt[2]);
}

//display pos data in statue bar and set the data in doc.cursor().
void fugenView::displaySet_position(const MGPosition& pos){
	display_position_in_status_bar(pos);
	GetDocument()->set_cursor(pos);
}

//display pos data in statue bar and set the data in doc.cursor().
//Transfer the point to world coordinates, then display in the status bar.
void fugenView::displaySet_position(
	const CPoint& point
){
	MGPosition P=locate(point);
	displaySet_position(P);
}

//Get the surface parameter value uv(u,v) where screen coordinate (sx,sy) is prjected on.
//If no projection points are found, the nearest point to a perimeter of surf will
//be returned.
bool fugenView::get_surface_parameter(
	const MGFSurface& surf,
	const CPoint& point,	//client area coordinates. (left, top) is (0,0).
	MGPosition& uv	//surface parameter (u,v) where (sx,sy) is projected on will be returned.
)const{
	int sx,sy;
	change_sc(point,sx,sy);
	return get_surface_parameter_glv(surf, sx, sy, uv);
}

//hide the input gels.
void fugenView::hide_gels(const std::vector<const MGGel*>& gels_to_hide){
	fugenDoc& doc=*(GetDocument());
	doc.get_main_group().delete_displayList(gels_to_hide);//The target should be all the objects
							//that belong to this view.(get_root_group()?).
}

void fugenView::hide_gels(const MGPickObjects& picked){
	size_t n=picked.size();
	std::vector<const MGGel*> tmp(n);
	for(size_t i=0; i<n; i++){
		tmp.push_back(picked[(int)i].top_object());
	}
	hide_gels(tmp);
}

// Transform to the home position.
void fugenView::home(){
	if(is_standard_view()){
		// ドキュメントのボリュームを求め、ビューを更新
		fugenDoc& doc=*(GetDocument());
		const MGBox& bx=doc.box();
		if(bx.is_null()){
			doc.setDefaultBox();
		}
		ExecInitModel(bx);
	}else{
		// 特殊ビューなので、undo/redo 対応の対象ではない。
		MGOpenGLView::setHomeMatrix();
	}
	redrawOnlythis();
}

///Transform to the view whose origin is cplane's origin and
//whose eys is on the normal of the cplane.
void fugenView::cplaneHome(){
	CCameraAction* pAct = new CCameraAction(this);
	pAct->SaveEnvParam(IAction::ACTION_UNDO);
	const MGPlane& pl=cplane().plane();
	update_viewing_environment(pl);

	// アクションではなく自分でリフレッシュ
	redrawOnlythis();

	pAct->Do(); // SetLastAction したいがための空操作。
	pAct->SaveEnvParam(IAction::ACTION_REDO);
}

//Initialize model.
void fugenView::initializeModel(){
	fugenDoc& doc=*(GetDocument());
	MGContext* contxt=doc.context();
	MGglViewAttrib& viewA=contxt->theView();
	copy(viewA);
}

const MGBox& fugenView::defaultBoxModel()const{
	const fugenDoc& doc=document();
	return doc.box();
}

HGLRC* fugenView::getRenderingContext(){
	static HGLRC hRC = NULL;
	return &hRC;
}

mgGLSLProgram* fugenView::getDefaultGLSLProgram(){
	static mgGLSLProgram glsl;
	return& glsl;
}

CDC* fugenView::get_dc(){
	release_dc();
	m_pDC= GetDC();
	//TRACE("fugenView::get_dc,m_pDC=%x\n",m_pDC);
	return m_pDC;
}

///Test if this is the main view of the doc.
bool fugenView::is_mainView()const{
	const fugenDoc& doc=document();
	if(!(&doc))
		return false;
	return this==doc.get_main_view();
}

void fugenView::release_dc(){
	if(m_pDC){
		if(wglGetCurrentContext())
			glFlush();
		ReleaseDC(m_pDC);
	}
	m_pDC=0;
}

/// OpenGL init stuff
/// The processes are:
/// (1) Set up OpenGL Rendering context(HGLRC) and GLSL program.
/// (2) make MGOpenGLView for this window.
///Function's return value is:
///=True : successfulley initialized.
///=False : Error detected.
bool fugenView::InitOpenGL(){
///1. Set up MGOpenGLView.
	get_dc();
	HDC hdc =m_pDC->GetSafeHdc();
	if( hdc == NULL )
		return false;//Failure to Get DC

	HGLRC* phRC = getRenderingContext();
	mgGLSLProgram* glslP = getDefaultGLSLProgram();

	DWORD dwFlags=(
		PFD_SUPPORT_OPENGL
		|PFD_DRAW_TO_WINDOW
		|PFD_DRAW_TO_BITMAP
		|PFD_DOUBLEBUFFER
		);// support window & support OpenGL & double buffered

	if( !SetupPixelFormat(hdc,dwFlags) )
		return false;

	//<-- ここから、プロセスを通して一度だけよばれるところ。
	if(*phRC==NULL){

		//Create Rendering Context
		HGLRC hRC = ::wglCreateContext(hdc);
		TRACE("wglCreateContext %d\n", ::GetLastError());

		if( hRC == 0 )
			return false;//Failure to Create Rendering Context

		::wglMakeCurrent(hdc,hRC );

///2. OpenGL4' GLEW initialize.
		GLenum err=glewInit();
		if(err!=GLEW_OK){
			const GLubyte* errorMsg=glewGetErrorString(err);
			COUT<<"GLEW initialization error:"<<errorMsg<<std::endl;
			return false;
		}

	// 使用する OpenGL のバージョンとプロファイルの指定
	static const int  att[]= {
		WGL_CONTEXT_MAJOR_VERSION_ARB,   4,
		WGL_CONTEXT_MINOR_VERSION_ARB,   2,
		//WGL_CONTEXT_FLAGS_ARB,           WGL_CONTEXT_DEBUG_BIT_ARB,
		WGL_CONTEXT_PROFILE_MASK_ARB,    WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
		0,0
	};	

		HGLRC hglrc2=wglCreateContextAttribsARB(hdc,0,att);
		if(hglrc2){
			*phRC = hglrc2;
			::wglMakeCurrent(hdc,*phRC );
			::wglDeleteContext(hRC);
		} else {
			return false;
		}


		/// 2'. ERROR callback setting.
		if(GLEW_ARB_debug_output){
			glDebugMessageCallbackARB(&mgGLSL::debugCallback, &std::cerr);
			glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
		}

		///3. OpenGL4' GLSL Program initialize.
		mgGLSL::dumpGLInfo();

		SetupGLSL(glslP);
		mgGLSL::initializeStaticGLAttribStack();

	} 
	//ここまで、プロセスを通して一度だけよばれるところ。 -->

	glslP->use();
	setDCRC(hdc,*phRC);

	RECT win;
	GetWindowRect(&win);
	int cx=win.right-win.left, cy=win.bottom-win.top;
	set_window(cx,cy);
	attach_drawer(&m_coordinateDrawer,false);

	return true;
}

/// Each viewport uses its own context, so we need to make sure the correct
/// context is set whenever we make an OpenGL command.
void fugenView::make_RC_current(CDC* pDC){
	if(!pDC){
		get_dc();
	}else{
		release_dc();
		m_pDC=pDC;
		//TRACE("fugenView::make_RC_current,m_pDC=%x\n",m_pDC);
	}
	if(m_pDC){
		// m_pDCはget_dc()でNULLの場合がある。
		setDCRC(m_pDC->GetSafeHdc(), *getRenderingContext());
	}
}

//locate the window's screen coordinate to the 3D world coordinate.
//In uv, the construction plane's parameter(u,v) coordinate will be returned.
MGPosition fugenView::locate(
	const CPoint& point,
	MGPosition* uv
)const{
	int sx,sy;
	change_sc(point,sx,sy);
	return locate_glv(sx,sy,uv);
}

//Lock the windows update.
void fugenView::lockWindowUpdate(){
	if(!m_locked) CWnd::LockWindowUpdate();
	m_locked++;
}

//Make openGL display list in glview.
void fugenView::makeDisplayList(){
	if(has_parent_OpenGLView())
		return;

	CWaitCursor wait;
	ASSERT_VALID(GetDocument());
//Generate OpenGL display list.
	fugenDoc& doc=*(GetDocument());
	MGOpenGLView::make_display_list(*(doc.context()),doc.get_main_group());

}

void fugenView::mouse_move_transform(const CPoint& point){
	if(is_perspective()){
		CRect rc;
		GetClientRect(rc);
		int sx = rc.Width();
		int sy = rc.Height();
		ASSERT(sx != 0 && sy != 0);

		int dx=m_mousemove_old_point.x-point.x;
		int dy=m_mousemove_old_point.y-point.y;
		//std::cout<<"***TR ("<<dx<<","<<dy<<")"<<std::endl;
		if(dx==0 && dy==0)
			return;

		float delta[2] ={360.f*float(dx)/float(sx), 360.f*float(dy)/float(sy)};
		rotate(delta);
	}else{
		pan(mousemove_old_point(), point);
	}
}

//Perform the panning view transformation from the
//Windows point0 to point1.
void fugenView::pan(
	const CPoint& point0,
	const CPoint& point1
){
	CRect rct; GetClientRect(&rct);//Get CRect information.
	int wh[2]={rct.Width(), rct.Height()};
	double sdx0=double(point0.x), sdy0=double(wh[1]-point0.y);
	double sdx1=double(point1.x), sdy1=double(wh[1]-point1.y);
	double wdx0,wdy0, wdx1,wdy1;
	screen_to_world(wh,sdx0,sdy0,wdx0,wdy0);
	screen_to_world(wh,sdx1,sdy1,wdx1,wdy1);
	translate_without_scale(wdx1-wdx0, wdy1-wdy0);
}

//Redraw the view. If this is a standarad view all of the 4 views will be drawn.
//redraw will invoke make_RC_current();
void fugenView::redraw(){
	if(m_locked)
		return;

	if(is_standard_view())
		redrawAllStdViews();
	else
		redrawOnlythis();
}

//Redraw all the standard views.
//redraw will invoke make_RC_current();
void fugenView::redrawAllStdViews(){
	fugenDoc& doc=document();

	//redraw all the standard windows.
	POSITION pos = doc.GetFirstViewPosition();
	while(pos != NULL){
		fugenView* pSView = dynamic_cast<fugenView*>(doc.GetNextView(pos));
		if(pSView && pSView->is_standard_view())
			pSView->redrawOnlythis();
	}
}

//Redraw only this view. Even if this is a standard view, only this is redrawn.
//redraw will invoke make_RC_current();
void fugenView::redrawOnlythis(CDC* pDC){
	make_RC_current(pDC);
	updateAxisPictures();
	if(is_standard_view()){
		fugenDoc& doc=document();
		setViewMode(doc.GetViewMode());
		drawScene(&(current_objects()));
	}else
		drawScene();

	if(!pDC)
		release_dc();//pDC==0はfugenViewの管理
	else
		m_pDC=0;//pDC!=0はCViewのOnDraw()管理のpDCであるのでfugenViewの管理外
}

//Save the viewing context to the document.
void fugenView::save_view_context(MGContext& ctx){
	if(is_mainView())
		ctx.set_view_context(*this);
}

///Apply drawing attributes and colors of ctx to this.
void fugenView::apply_view_context_colors(MGContext & ctx){
	importDrawAttribFromContext(ctx);

	// Construction planeの定義の再設定
	apply_grid_colors(ctx);
	importGridAttrib(ctx);
}

void fugenView::apply_grid_colors(MGContext & ctx){
	cplane().set_colorsByViewID(1, ctx.gridColors());
}

//Show all objects
void fugenView::show_all_objects(){
	fugenDoc& doc=*(GetDocument());
	doc.get_main_group().make_display_list();
}

//Terminate the doc's current command tool.
void fugenView::terminate_current_command_tool(){
	GetDocument()->terminate_current_command_tool();
}

// Transform the world coordinates to the construction plane coordinates.
void fugenView::transform_cplane_coord(MGPosition& pos) const{
	// for example, (0,0,0) is mapped to cplane().plane().center().
	ASSERT(pos.sdim() == 3);

	const MGConstructionPlane& cpl = cplane();
	const MGPlane& pl = cpl.plane();

	MGVector x = pl.u_deriv().normalize();
	x *= pos[0];
	x *= cpl.uspan();

	MGVector y = pl.v_deriv().normalize();
	y *= pos[1];
	y *= cpl.vspan();

	MGVector z = pl.normal(); // unitary
	z *= pos[2];

	pos = pl.center();
	pos += x;
	pos += y;
	pos += z;
}

// Unlock the window's update.
void fugenView::unlockWindowUpdate(){
	if(m_locked) m_locked--;
	if(!m_locked) CWnd::UnlockWindowUpdate();
}

//Convert the windows screen coordinate point to MGCL's straight line.
void fugenView::unproject_to_sl(const CPoint& point, MGStraight& sl) const{
	int sx, sy;
	change_sc(point,sx,sy);
	unproject_to_sl_glv(sx,sy,sl);
}

//Convert the windows world coordinate point to MGCL's straight line.
void fugenView::unproject_to_sl(const MGPosition& point, MGStraight& sl)const{
	CPoint cp;
	fugenView* win2=const_cast<fugenView*>(this);
	win2->convert_to_screen(point,cp);
	unproject_to_sl(cp,sl);
	if(is_perspective())return;

	const MGUnit_vector viewvec=view_direction_original();
	MGUnit_vector sl_direction=sl.direction();
	double angle=(viewvec*sl_direction).len();
	if(MGZero_angle(angle))
		sl=MGStraight(MGSTRAIGHT_UNLIMIT,viewvec,point);
}
