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

/**
 * @file SurfFromEdgesTool.cpp
 * @brief MGSurfFromEdgesTool クラスのインプリメンテーション
 */
#include "stdafx.h"
#include "mg/LBRep.h"
#include "mg/SBRep.h"
#include "mg/SBRepTP.h"
#include "Calc/mgcalc.h"
#include "fugenDoc.h"
#include "fugenView.h"
#include "SurfCmd/SurfFromEdgesTool.h"

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

// MGSurfFromEdgesTool

MGSurfFromEdgesTool::MGSurfFromEdgesTool(fugenDoc* pDoc, size_t nEdge)
:MGSelectState(pDoc,ID_SURFACE_FROM_EDGE,SINGLE_SELECT,mgAll_Curve),
m_nEdge(nEdge), m_nCount(0){
	ASSERT(m_nEdge >= 2 && m_nEdge <= 4);

	switch(m_nEdge){
	case 2:	set_command_id(ID_SURFACE_EDGES_2);		break;
	case 3:	set_command_id(ID_SURFACE_EDGES_3);		break;
	case 4:	set_command_id(ID_SURFACE_EDGES_4);		break;
	}
}

MGCommandBase* MGSurfFromEdgesTool::initial_clone(fugenDoc* pDoc) const{
	// 他のクラスと少々異なるので注意
	return new MGSurfFromEdgesTool(pDoc, m_nEdge);
}

bool MGSurfFromEdgesTool::initiate_tool(){
	MGSelectState::initiate_tool();

	// 強制的にクリア
	clear_pick_object();
	set_add_mode();
	prohibit_unselect();

	// 最初のメッセージ
	SetStatusMessage(IDS_PROMPT_CURVE);
	return false;
}

bool MGSurfFromEdgesTool::OnCommandCanceled(UINT nIDS){
	clear_pick_object();
	return MGSelectState::OnCommandCanceled(nIDS);
}

bool MGSurfFromEdgesTool::OnCommandEnd(
	UINT nIDS,	//=0: erase the current message, and display no messages.
				//=1: display "xxxx" normally end.
				//otherwise: nIDS is a string id, and load the message from string table to display.
	bool erase_temporary_display
){
	// 指定された曲線の個数と
	// コマンドの指定する個数が一致しているかどうか
	if(m_nCount != m_nEdge){
		// みなしキャンセル
		return OnCommandCanceled(1);
	}
	
	// 計算開始
	SetStatusMessage(IDS_PROMPT_COMPUTE);
	CWaitCursor sandglass;

	std::unique_ptr<MGLBRep> tmp2[4];
	//Construct 4 perimeters, given at least two of the four.
	//Input perimeters may have different knot configuration. In this case they will be updated
	//so as to have the same configuration.
	//Function's return value indicates which perimeter(s) was missing:
	// 10: all of the 4 were input(and knot configurations were updated to have the same).
	//  0: only perimeter 0 was missing.
	//  1: only perimeter 1 was missing.
	//  2: only perimeter 2 was missing.
	//  3: only perimeter 3 was missing.
	//  4: perimeter 2 and 3 were missing.
	//  5: perimeter 1 and 3 were missing.
	//  6: perimeter 1 and 2 were missing.
	//  7: perimeter 0 and 3 were missing.
	//  8: perimeter 0 and 2 were missing.
	//  9: perimeter 0 and 1 were missing.
	// -2: less than 2 perimeters were provided.
	int ret = construct_perimeters(m_curve,m_curve+4, tmp2);
	ASSERT(tmp2[0]->knot_vector() == tmp2[2]->knot_vector());
	ASSERT(tmp2[1]->knot_vector() == tmp2[3]->knot_vector());
	MGSBRep* srf = new MGSBRep;
	srf->buildGeneralizedRuledSurface(tmp2);
	add_object_to_current_group(srf);
	return MGSelectState::OnCommandEnd(1);
}

bool MGSurfFromEdgesTool::OnSelected(
	fugenView* window,//The fugenView pointer where point input event took place.
	MGPickObjects&	objs,	//selected objects at this selection operation.
	MGPickObjects&	unselected_objects	//unselected objects at this selection operation.
		//unselected_objects.size()>=1 only when the already selected objects are selected
		//when add mode is set(or when operation is done with a crtl key pressed).
){
	if(!objs.size())
		return false;
	const MGCurve& curve =*dynamic_cast<const MGCurve*>(objs.front().top_object());
	MGPosition& crvNewS = curve.start_point();
	MGPosition& crvNewE = curve.end_point();

	int msgID=0;//0 means success to continue the command.
	int idCurve=0;//m_curve[]'s id to set curve in.
	int needToNegate=0;//0: no need to negate, 1: need to negate, -1:unknown.
	if(m_nCount){// 1本目のカーブは無条件で[0]にセットする, makes v=min curve.
		const MGCurve& c0 = *m_curve[0];
		MGPosition c0s = c0.start_point();
		MGPosition c0e = c0.end_point();
		double errorLoose=MGTolerance::wc_zero()*5.;
		mgTolSetWCZero wcz(errorLoose);//Loosen error for point equal judgement.
		switch(m_nCount){
		case 1:// 2本目のカーブは収納先を計算する
			if(c0s == crvNewS){//case1
				idCurve=3;
			}else if(c0s == crvNewE){//case2
				needToNegate=1;
				idCurve=3;
			}else if(c0e == crvNewS){//case3
				idCurve=1;
			}else if(c0e == crvNewE){//case4
				needToNegate=1;
				idCurve=1;
			}else{//case5
				needToNegate = -1;
				idCurve=2;
			}
			break;
		case 2:// 3本目のカーブを収納する: 失敗する場合もある
			if(m_curve[3].get()){//0,3
				const MGPosition& c3e = m_curve[3]->end_point();
				if(c0e == crvNewS){//case6
					idCurve=1;
				}else if(c3e == crvNewS){//case7
					idCurve=2;
				}else if(c0e == crvNewE){//case8
					needToNegate=1;
					idCurve=1;
				}else if(c3e == crvNewE){//case9
					needToNegate=1;
					idCurve=2;
				}else{// 曲線が変な場所にあるため失敗とする
					msgID = IDS_FAIL_SELECT_PERIM;
				}
			}else if(m_curve[1].get()){//0,1
				const MGPosition& c1e = m_curve[1]->end_point();
				if(crvNewS == c0s){
					idCurve=3;
				}else if(crvNewE == c1e){
					idCurve=2;
				}else if(crvNewE == c0s){
					needToNegate=1;
					idCurve=3;
				}else if(crvNewS == c1e){
					needToNegate=1;
					idCurve=2;
				}else{// 曲線が変な場所にあるため失敗とする
					msgID = IDS_FAIL_SELECT_PERIM;
				}
			}else{//0,2
				MGPosition c2s = m_curve[2]->start_point();
				MGPosition c2e = m_curve[2]->end_point();

				if(crvNewS == c0e && crvNewE == c2e){
					idCurve=1;
				}else if(crvNewS == c0s && crvNewE == c2s){
					idCurve=3;
				}else if(crvNewS == c2e && crvNewE == c0e){
					idCurve=1;
					needToNegate=1;
				}else if(crvNewS == c2s && crvNewE == c0s){
					idCurve=3;
					needToNegate=1;
				}else if(crvNewS == c0e && crvNewE == c2s){
					idCurve=1;
					needToNegate=1;
				}else if(crvNewS == c0s && crvNewE == c2e){
					idCurve=3;
					needToNegate=1;
				}else if(crvNewS == c2s && crvNewE == c0e){
					idCurve=1;
					needToNegate=1;
				}else if(crvNewS == c2e && crvNewE == c0s){
					idCurve=3;
					needToNegate=1;
				}else{// 曲線が変な場所にあるため失敗とする
					msgID = IDS_FAIL_SELECT_PERIM;
				}
			}
			break;
		case 3:{// 4本目のカーブを決定する: 失敗する場合もある				
			// Now m_curve accommodates just one curve.
			if(!m_curve[1].get()){
				idCurve=1;
				MGPosition c2e = m_curve[2]->end_point();
				if(crvNewS == c2e && crvNewE == c0e)
					needToNegate=1;
			}else if(!m_curve[2].get()){
				idCurve=2;
				MGPosition c1e = m_curve[1]->end_point();
				MGPosition c3e = m_curve[3]->end_point();
				if(crvNewS == c1e && crvNewE == c3e)
					needToNegate=1;
			}else{
				idCurve=3;
				MGPosition c2s = m_curve[2]->start_point();
				if(crvNewS == c2s && crvNewE == c0s)
					needToNegate=1;
			}
		} break;
		}
	}

	if(msgID)
		return OnCommandCanceled(msgID);// failed

	m_curve[idCurve].reset(curve.clone());
	MGCurve& crvNew = *m_curve[idCurve];
	if (needToNegate == -1) {
		const MGCurve& crv0 = *m_curve[0];
		double tmid0 = (crv0.param_s() + crv0.param_e())*.5;
		double tmid1 = (crvNew.param_s() + crvNew.param_e())*.5;
		if (crv0.eval(tmid0, 1) % crvNew.eval(tmid1, 1) < 0.)
			needToNegate = 1;
	}
	if(needToNegate==1)
		crvNew.negate();
	m_nCount++;

	if(m_nCount != m_nEdge)
		return false;
	return OnCommandEnd(1);// 計算開始
}

// ファクトリー用
MGCommandBase* CreateSurfFromEdgesTool(fugenDoc* pDoc, UINT nCmdID)
{
	switch(nCmdID){
	case ID_SURFACE_EDGES_2:
		return new MGSurfFromEdgesTool(pDoc, 2);
	case ID_SURFACE_EDGES_3:
		return new MGSurfFromEdgesTool(pDoc, 3);
	default:
		return 0;
	}
}

// MGSurfFrom4Edges
MGSurfFrom4Edges::MGSurfFrom4Edges(fugenDoc* pDoc)
	 : MGSelectState(
		 pDoc,
		 ID_SURFACE_EDGES_4,
		 SINGLE_SELECT,
		 mgAll_Curve),
	   m_nIDS(1)
{
}

MGCommandBase* MGSurfFrom4Edges::initial_clone(fugenDoc* pDoc) const
{
	return new MGSurfFrom4Edges(pDoc);
}

bool MGSurfFrom4Edges::initiate_tool(){
	MGSelectState::initiate_tool();
	clear_pick_object();
	set_add_mode();
	prompt_message();
	return false;
}

bool MGSurfFrom4Edges::OnCommandCanceled(UINT nIDS){
	clear_pick_object();
	return MGSelectState::OnCommandCanceled(nIDS);
}

bool MGSurfFrom4Edges::OnCommandEnd(
	UINT nIDS,	//=0: erase the current message, and display no messages.
				//=1: display "xxxx" normally end.
				//otherwise: nIDS is a string id, and load the message from string table to display.
	bool erase_temporary_display
){
	return MGSelectState::OnCommandEnd(1);
}

bool MGSurfFrom4Edges::calculate(){
	SetStatusMessage(IDS_PROMPT_COMPUTE);
	CWaitCursor sandglass;

	const MGCurve* perim_org[4] = {
		dynamic_cast<const MGCurve*>(m_vmin_c.top_object()),
		dynamic_cast<const MGCurve*>(m_umax_c.top_object()),
		dynamic_cast<const MGCurve*>(m_vmax_c.top_object()),
		dynamic_cast<const MGCurve*>(m_umin_c.top_object()),
	};

	// 1 - check all corners of four perimeter curves
	std::unique_ptr<MGLBRep> perim_tmp[4]; ///< 一時描画用辺曲線
	if(rebuildCurveTrimDirectionUpdate(perim_org, perim_tmp)){
		m_nIDS = IDS_FAIL_MAKE_VALID_PERIM;
		return false;
	}

	std::unique_ptr<MGLBRep> tmp2[4];
	int ret = construct_perimeters(perim_tmp, perim_tmp+4, tmp2);
	if(ret != 10){
		// failed
		m_nIDS = IDS_FAIL_MAKE_VALID_PERIM;
		return false;
	}
	ASSERT(tmp2[0]->knot_vector() == tmp2[2]->knot_vector());
	ASSERT(tmp2[1]->knot_vector() == tmp2[3]->knot_vector());
	MGSBRep* srf = new MGSBRep; srf->buildGeneralizedRuledSurface(tmp2);
	add_object_to_current_group(srf);
	return true;
}

void MGSurfFrom4Edges::prompt_message() const{
	if(m_vmin_c.is_null()){
		SetStatusMessage(IDS_PROMPT_CURVE_VMIN);
		return;
	}
	if(m_umax_c.is_null()){
		SetStatusMessage(IDS_PROMPT_CURVE_UMAX);
		return;
	}
	if(m_vmax_c.is_null()){
		SetStatusMessage(IDS_PROMPT_CURVE_VMAX);
		return;
	}
	if(m_umin_c.is_null()){
		SetStatusMessage(IDS_PROMPT_CURVE_UMIN);
		return;
	}
}

bool MGSurfFrom4Edges::OnSelected(
	fugenView* window,//The fugenView pointer where point input event took place.
	MGPickObjects&	objs,	//selected objects at this selection operation.
	MGPickObjects&	unselected_objects	//unselected objects at this selection operation.
		//unselected_objects.size()>=1 only when the already selected objects are selected
		//when add mode is set(or when operation is done with a crtl key pressed).
){
	if(m_vmin_c.is_null()){
		m_vmin_c = objs.back();
		prompt_message();
		return false;
	}
	if(m_vmin_c.top_object() == objs.back().top_object()){
		objs.pop_back();
		prompt_message();
		return false;
	}
	
	if(m_umax_c.is_null()){
		m_umax_c = objs.back();
		prompt_message();
		return false;
	}
	if(m_umax_c.top_object() == objs.back().top_object()){
		objs.pop_back();
		prompt_message();
		return false;
	}
	
	if(m_vmax_c.is_null()){
		m_vmax_c = objs.back();
		prompt_message();
		return false;
	}
	if(m_vmax_c.top_object() == objs.back().top_object()){
		objs.pop_back();
		prompt_message();
		return false;
	}

	if(m_umin_c.is_null()){
		m_umin_c = objs.back();
	}

	// 計算開始
	if(!calculate()){
		// failed
		return OnCommandCanceled(m_nIDS);
	}
	// コマンド終了
	return OnCommandEnd(1);
}
