/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
/********************************************************************/
/**
 * @file SurfSmooth.cpp
 * @brief MGSurfSmoothTool クラスのインプリメンテーション
 */
#include "stdafx.h"
#include "mg/SBRep.h"
#include "fugen.h"
#include "fugenDoc.h"
#include "fugenView.h"
#include "SurfCmd/SurfSmooth.h"
#include "GLInputRealDlg.h"
#include "Calc/surface.h"

#include "Calc/mgcalc.h"
#include "Misc/UserPreference.h"
#include "QS/QSEval.h"

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

namespace{
	const double dSmooth = 1.0;
}

// MGSurfSmoothTool

MGSurfSmoothTool::MGSurfSmoothTool(fugenDoc* pDoc)
: MGSelectState(pDoc, ID_SURFACE_SMOOTH, SINGLE_SELECT, // single
		 mgAll_SBRep),
	   m_dSmooth(dSmooth){
}

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

bool MGSurfSmoothTool::initiate_tool(){
	MGSelectState::initiate_tool();
	const UserPreference& pref = UserPreference::getInstance();
	m_dSmooth = pref.GetDoubleValue(upv_Surface_Smooth_Distance);

	if(resetCurrentObjects(mgAll_SBRep)){
		if(current_objects().size() == 1){
			m_surf = current_objects().front();
			return OnCommandEnd(1);
		}
	}
	clear_pick_object();
	// 最初のメッセージ
	SetStatusMessage(IDS_PROMPT_SURFACE);
	return false;
}

bool MGSurfSmoothTool::terminate_tool(bool cancel){
	UserPreference& pref = UserPreference::getInstance();
	pref.SetDoubleValue(upv_Surface_Smooth_Distance, m_dSmooth);

	return MGSelectState::terminate_tool(cancel);
}

bool MGSurfSmoothTool::input_param(){
	SetStatusMessage(IDS_PROMPT_PARAMETER);
	CString str;
	VERIFY(str.LoadString(IDS_CAPTION_DEVIATION));

	CGLInputRealDlg dlg(str);
	dlg.SetValue(m_dSmooth);
	bool bOK = (IDOK == dlg.DoModal());
	if(bOK){
		m_dSmooth = dlg.GetValue();
	}
	return bOK;
}

void smtsrf(MGSBRep& srf, double avmd){
	const MGKnotVector& tu = srf.knot_vector_u();
	const MGKnotVector& tv = srf.knot_vector_v();
	MGNDDArray utau, vtau;
	utau.buildByKnotVector(tu);
	vtau.buildByKnotVector(tv);
	MGSPointSeq sp(utau.length(), vtau.length(), srf.sdim());

	std::vector<double> weight(utau.length(), 1.);
	weight.front() = weight.back() = 0.1;
	if(weight.size() > 2){
		weight[1] = weight[weight.size()-2] = 0.2;
	}

	const double maxdev_u = avmd * utau.length();
	MGBPointSeq bpu(utau.length(), srf.sdim());
	for(int j = 0; j < vtau.length(); j++){
		for(int i = 0; i < utau.length(); i++){
			bpu.store_at(i, srf.eval(utau[i], vtau[j])); // position
		}

		MGLBRep ucrv;
		ucrv.buildSRSmoothedLB_of_FreeEnd(utau, bpu, &weight[0], maxdev_u);

		for(int i = 0; i < utau.length(); i++){
			sp.store_at(i, j, ucrv.eval(utau(i)));
		}
	}

	weight.resize(vtau.length());
	weight.assign(vtau.length(), 1.);
	weight.front() = weight.back() = 0.1;
	if(weight.size() > 2){
		weight[1] = weight[weight.size()-2] = 0.2;
	}

	const double maxdev_v = avmd * vtau.length();
	MGBPointSeq bpv(vtau.length(), srf.sdim());
	for(int i = 0; i < sp.length_u(); i++){
		for(int j = 0; j < vtau.length(); j++){
			bpv.store_at(j, sp(i, j));
		}

		MGLBRep vcrv;
		vcrv.buildSRSmoothedLB_of_FreeEnd(vtau, bpv, &weight[0], maxdev_v);

		for(int j = 0; j < vtau.length(); j++){
			sp.store_at(i, j, vcrv.eval(vtau(j)));
		}
	}
	srf.buildByInterpolationWithKTV(utau, vtau, sp);
}

bool smooth_surface(
	MGSBRep& surf,
	double distance_tol
){
	if(distance_tol < 0) distance_tol = -distance_tol;

	const double inf_smtcoef = 1e-8; // e-5 is dangerous.
	const double first_ratio = .7;
	const double ratio = .333333;

	const MGSBRep original(surf);
	double smtcoef = distance_tol * first_ratio;
	if(smtcoef < inf_smtcoef){
		smtcoef = inf_smtcoef * 10;
	}

	smtsrf(surf, smtcoef);
	smtcoef *= ratio;
	MGSBRep desired(surf);
	MGPosition uv(2);
	UniqueCurve p[4];
	p[0].reset(surf.perimeter_curve(0));
	p[1].reset(surf.perimeter_curve(1));
	p[2].reset(surf.perimeter_curve(2));
	p[3].reset(surf.perimeter_curve(3));

	const MGCurve* perim[4] = {p[0].get(), p[1].get(), p[2].get(), p[3].get()};
	double min_gap = QS::eval_gap(surf, perim, uv);
	ASSERT(smtcoef > inf_smtcoef);
	while(smtcoef > inf_smtcoef){
		surf = original;
		smtsrf(surf, smtcoef);
		double cur_gap = QS::eval_gap(surf, perim, uv);
		if(cur_gap < min_gap){
			min_gap = cur_gap;
			desired = surf;
		}
		smtcoef *= ratio;
	}
	surf = desired;

	return (min_gap < distance_tol);
}

bool MGSurfSmoothTool::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_surf.is_null()){
		// キャンセル
		return MGSelectState::OnCommandEnd(3);
	}

	// パラメータを入力
	if(!input_param()){
		// キャンセル
		return MGSelectState::OnCommandEnd(3);
	}

	SetStatusMessage(IDS_PROMPT_COMPUTE);
	CWaitCursor sandglass;

	const MGSBRep* orgsurf = dynamic_cast<const MGSBRep*>(m_surf.top_object());
	ASSERT(orgsurf); std::cout<<*orgsurf<<std::endl;

	std::unique_ptr<MGSBRep> result(new MGSBRep(*orgsurf));
	double devi = m_dSmooth;
	if(!smooth_surface(*result, devi)){
		// failed
		return MGSelectState::OnCommandEnd(3);
	}
	 //std::cout<<*result<<std::endl;

	// ここはreplaceのほうが望ましい
	if(!replace_object(const_cast<MGSBRep*>(orgsurf), result.release())){
		// failed
		return MGSelectState::OnCommandEnd(3);
	}

	return MGSelectState::OnCommandEnd(1);
}

bool MGSurfSmoothTool::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).
){
	ASSERT(objs.size() == 1);
	ASSERT(dynamic_cast<const MGSBRep*>(objs.front().top_object()));
	m_surf = objs.front();

	// 計算開始
	return OnCommandEnd(1);
}
