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

/**
 * @file CurveSmoothingState.cpp
 * @brief MGCurveSmoothingState クラスのインプリメンテーション
 */
#include "stdafx.h"
#include "mg/LBRepEndC.h"
#include "fugenDoc.h"
#include "fugenView.h"
#include "SmoothingDlg.h"
#include "CurveCmd/CurveSmoothingState.h"

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

MGCurveSmoothingState::MGCurveSmoothingState(fugenDoc* pDoc)
: MGCommandStateOwner(pDoc, ID_CURVE_SMOOTH),m_original(0){
	// ダイアログを準備する
	attachModelessDialogue<CSmoothingDlg>(this);
}

namespace
{
	const double MIN_WEIGHT = 1.e-2;
}

void MGCurveSmoothingState::set_weight(
	bool add,				//true if add weight, false if subtract weight.
	double t				//curve's parameter value.
){
	int i = m_tau.locate(t), ntau=m_tau.length();
	if(i>=(ntau-1)) i--;
	double range = m_tau[i+1]-m_tau[i];
	double a = t - m_tau[i], b = m_tau[i+1] - t;

	CSmoothingDlg& dialog = *(static_cast<CSmoothingDlg*>(getModelessDialogPointer()));
	double min_weight2=MIN_WEIGHT;
	if(dialog.m_bFixStart || dialog.m_bFixEnd) min_weight2*=10.;
	double weight_change = dialog.m_dWeight;
	if(!add) weight_change*=-1.;

	double nweightS=m_weight[i]+b*weight_change/range;
	if(nweightS<=min_weight2) nweightS=min_weight2;
	if(!dialog.m_bFixStart || i) m_weight[i] = nweightS;

	double nweightE=m_weight[i+1]+a*weight_change/range;
	if(nweightE<=min_weight2) nweightE=min_weight2;
	if(!dialog.m_bFixEnd || i!=m_weight.size()-2) m_weight[i+1] = nweightE;
}

//set weights for all the input parameter values.
void MGCurveSmoothingState::set_weights(){
	size_t np=m_bp.length();
	m_weight.assign(np, 1.0);

	CSmoothingDlg& dialog = *(static_cast<CSmoothingDlg*>(getModelessDialogPointer()));
	if(dialog.m_bFixStart) m_weight[0]=MIN_WEIGHT;
	if(dialog.m_bFixEnd) m_weight[np-1]=MIN_WEIGHT;

	const LInfoVec& locs=locates();
	size_t ninp=locs.size();
	assert(ninp==m_adds.size());
	for(size_t i=0; i<ninp; i++){
		double t=locs[i]->curve_parameter();
		set_weight(m_adds[i],t);
	}
}

//set fix weight 
void MGCurveSmoothingState::set_fix_weight(
	bool start	//true if start point is set to be fixed.
){
	size_t id=0, n=m_weight.size();
	if(!start) id=n-1;
	m_weight[id]=MIN_WEIGHT;

	double min_weight2=MIN_WEIGHT*10.;
	for(size_t i=1; i<n-1; i++){
		if(m_weight[i]<min_weight2) m_weight[i]=min_weight2;
	}
}

//set the smoothing curve data into the member variables.
void MGCurveSmoothingState::set_member_data(MGPickObjects& pobjs){
	MGObject* co = pobjs.front().top_object();
	m_original = dynamic_cast<MGCurve*>(co);
	if(!m_original) return;

	const MGKnotVector& t=m_original->knot_vector();
	int is=t.order()-1;
	m_tau.copy_removing_multi(is,t.bdim()-is+1,t);
	m_original->eval_line(m_tau, m_bp);

	set_weights();

	CSmoothingDlg& dialog = *(static_cast<CSmoothingDlg*>(getModelessDialogPointer()));
	if(!dialog.IsWindowVisible()){
		CPoint pt;
		::GetCursorPos(&pt);
		pt.Offset(-4, -4);
		dialog.SetWindowPos(&CWnd::wndTop, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW);
	}
}

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

bool MGCurveSmoothingState::initiate_tool(){
	MGCommandStateOwner::initiate_tool();
	if(resetCurrentObjects(mgAll_LBRep)){
		//Input weight.
		set_member_data(MGPickObjects(current_objects()));
		set_child_current_command(new MGCurveSmoothingIPoint(this));
	}else{
		//select a curve to smooth.
		set_child_current_command(new MGCurveSmoothingScurve(this));
	}
	return false;
}

/////////////////////////////////////////////////////////////////////////
// EVENT

//Invoked when command is to terminate as a nomal end.
bool MGCurveSmoothingState::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_original){
		m_tmp->copy_appearance(*m_original);
		replace_object(m_original, m_tmp.release());
	}
	return MGCommandStateOwner::OnCommandEnd(nIDS);
}

void MGCurveSmoothingState::do_make_temporary_display(mgSysGL& sgl,fugenView* pSView){
	if(m_state)
		m_state->draw_temporary();
}

///////////////Smoothing curve selection class/////////////
MGCurveSmoothingScurve::MGCurveSmoothingScurve(
	MGCommandStateOwner* owner
):MGSelectState(owner,MGSelectState::SINGLE_SELECT,mgAll_LBRep){;}

bool MGCurveSmoothingScurve::initiate_tool(){
	MGSelectState::initiate_tool();
	SetStatusMessage(IDS_PROMPT_CURVE);
	return false;
}

bool MGCurveSmoothingScurve::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).
){
	MGCurveSmoothingState* owner=state_owner();
	owner->set_member_data(MGPickObjects(current_objects()));
	set_sibling_next_command(new MGCurveSmoothingIPoint(owner));
	return false;
}

/////////////Weight data for the smoothing input class//////////////
MGCurveSmoothingIPoint::MGCurveSmoothingIPoint(
	MGCommandStateOwner* owner
):MGLocateOnObjects(owner,
LOCK_SNAP_ATTRIB/*Prohibit to update snap attrib*/,NO_RUBBER,POINT_IPDRAW){
	turn_on_near();
	turn_on_end();
}

bool MGCurveSmoothingIPoint::initiate_tool(){
	MGCurveSmoothingState* owner=state_owner();
	set_locate_objectives(owner->m_original);
	MGLocateOnObjects::initiate_tool();
	prohibitSamePointInput(false);//Allow inputs at the same points.
	draw_temporary();
	SetStatusMessage(IDS_PROMPT_SMOOTHING_POINT);
	return false;
}

bool MGCurveSmoothingIPoint::OnLocated(
	const MGLocateInfo& linfo
){
	MGCurveSmoothingState* owner=state_owner();
	if(linfo.is_deleted()){
		if(owner->m_adds.size()){
			owner->m_adds.pop_back();
			owner->set_weights();
		}
		return false;
	}

	const MGCurve* curve=static_cast<const MGCurve*>(linfo.object());
	if(curve!=owner->m_original){
		cancel_last_locate();
		return false;
	}

	owner->m_adds.push_back(GetKeyState(VK_SHIFT)>=0);
	owner->set_weights();
	return false;
}

void MGCurveSmoothingIPoint::do_make_temporary_display(mgSysGL& sgl,fugenView* pSView
){
	MGCurveSmoothingState& owner=*state_owner();
	if(!owner.m_original)
		return;

	CSmoothingDlg& dialog = *(static_cast<CSmoothingDlg*>(getModelessDialogPointer()));
	double maxdev = dialog.m_dMaxDev;
	bool meanDev= dialog.m_nDevKind==0;
	if(meanDev){
		maxdev*=double(owner.m_tau.length());
	}
	owner.m_tmp.reset(new MGLBRep);

	if(dialog.m_nEndKind == 0){ // free-end
		owner.m_tmp->buildSRSmoothedLB_of_FreeEnd(
			owner.m_tau,owner.m_bp,&owner.m_weight[0],maxdev,meanDev);
	}else{
		MGLBRepEndC ec0=MGLBRepEndC(1,MGENDC_1D,*(owner.m_original));
		MGLBRepEndC ec1=MGLBRepEndC(0,MGENDC_1D,*(owner.m_original));
		owner.m_tmp->buildSRSmoothedLB_of_1stDeriv(
			ec0,ec1,owner.m_tau, owner.m_bp, &owner.m_weight[0],maxdev,meanDev);
	}

	MGColor::get_instance(MGColor::SpringGreen).exec(sgl);
	sgl.LineWidth(2.f);
	owner.m_tmp->drawWire(sgl);
	if(::IsWindow(dialog.GetSafeHwnd()) && dialog.IsWindowVisible()){
		MGColor::get_instance(MGColor::White).exec(sgl);
		sgl.LineWidth(1.f);
		sgl.drawCurvaGraph(*owner.m_tmp, dialog.m_nDensity, dialog.m_iType==1, dialog.GetScale());
	}
}
