/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
#include "stdafx.h"
#include "mg/Straight.h"
#include "fugenDoc.h"
#include "fugenView.h"
#include "Common/CursorRestriction.h"

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

MGCursorRestriction::MGCursorRestriction()
:m_distance(false,-1.),
m_prohibit_AETmodeUpdate(false),
m_prohibit_PmodeUpdate(false),
m_restriction_mode(FREE),m_angle(90.){
}

//This method is invoked in moving the cursor(update_state=false),
//or to input adjusted data(update_state=ture).
//Adjust input cursr point data by snap attributes.
//Function's return value is true when a point is input, false if not.
//False will be returned when mode change is done and cursr data is not valid.
void MGCursorRestriction::adjust_cursor(
	fugenView* window,  //active view
	UINT           nFlags,  //whether shift, tab, control key be pressed or not
	const CPoint&  point,   //screen position of the cursor
	const LInfoVec&    ipos,	//located position.
	MGPosition&    cursr	//non-adjusted world position data of the cursor is input,
							//and the adjusted data will be output.
){
	if(isPlanarMode()){//Planar mode process.
		restrict_planar(point,*window,ipos,cursr);
	}else if(isMapToStraightMode()){//Elevate or tab mode process.
		restrict_to_straight(cursr);
	}
	if(!ipos.empty()){
		const MGPosition& posRef = ipos.back()->point_world();
		if(isAngleMode()){//Angle mode process.
			const MGPosition& posRef2= (isPlanarMode() && !m_planarOnPrevious) ?
				 m_straight_to_map.root_point():posRef;
			restrict_angle(cursr, posRef2,window->cplane());
		}
		if(m_distance.first){//Distance mode process.
			restrict_distance(cursr, posRef);
		}
	}
}

// Prohibit ELEVATION mode.
void MGCursorRestriction::prohibitAETPmodeUpdate(){
	m_prohibit_AETmodeUpdate=true;
	m_prohibit_PmodeUpdate=true;
}

void MGCursorRestriction::allowAETPmodeUpdate(){
	m_prohibit_AETmodeUpdate=false;
	m_prohibit_PmodeUpdate=false;
}

// Restrict track of the cursor on the lines which pass through pivot.
// The first radial line lies the X-axis of cplane, which is at m_angle.second
// degree angles the second, and which is at the same angle the third...
void MGCursorRestriction::restrict_angle(
	MGPosition& cursor,
	const MGPosition& pivot,
	const MGConstructionPlane& cplane
){
	if(cursor==pivot)
		return;

	MGVector radial(cursor - pivot);
	const MGVector& normal = cplane.plane().normal();
	const MGVector& uDeri=cplane.plane().u_deriv();//This is a unit vector.
	
	MGVector radial2=radial.orthogonize(normal);
	double polang = uDeri.angle2pai(radial2,normal);//in radian.
	double polangDegree = polang * 180. / mgPAI;
	double rounded = MGCL::round_angle(polangDegree, m_angle);
	MGMatrix rot;
	rot.set_rotate_3D(normal, rounded*mgPAI / 180.);
	MGVector rho=uDeri*rot;//rho is a unit vector since uDeri is unit.
	MGVector R2unit=radial2.normalize();
	double R2projectedLength=R2unit%radial;
	rho*=(rho%R2unit)*R2projectedLength;
	cursor =pivot+rho;
}

// Restrict track of the cursor on radial lines which pass through pivot.
// The first radial line lies the X-axis of cplane, which is at m_angle.second
// degree angles the second, and which is at the same angle the third...
void MGCursorRestriction::restrict_to_straight(
	MGPosition& cursor
){
	double t=m_straight_to_map.closest(cursor);
	cursor=m_straight_to_map.eval(t);
}

// Restrict tranck of the cursor as planar to previous point.
void MGCursorRestriction::restrict_planar(
	const CPoint& point,
	const fugenView& view,
	const LInfoVec& ipos,	//located position.
	MGPosition& cursor///Non restricted cursor point is input, and
		///adjusted cursor is output.
){
	if(m_planarOnPrevious && ipos.size()==0)
		return;

	MGStraight straightEye;
	view.unproject_to_sl(point,straightEye);
	const MGVector& normal=m_planarOnPrevious ?
			view.cplane().plane().normal() : m_straight_to_map.direction();

	const MGPosition& planePoint=m_planarOnPrevious ?
			ipos.back()->point_world() : m_straight_to_map.root_point();
	MGPlane pl(normal.normalize(),planePoint);
	MGCSisects is=pl.isect(straightEye);
	if(is.size())
		cursor=is.last().point();
	else
		cursor=planePoint;
}

// Restrict track of the cursor within the sphere 
// whose radius is m_distance.second and the center is pivot.
void MGCursorRestriction::restrict_distance(
	MGPosition& cursor, const MGPosition& pivot
){
	double rest = m_distance.second;
	double len = cursor.distance(pivot);
	if(len > rest){
		cursor = (rest*cursor + (len-rest)*pivot)/len;
	}
}

void MGCursorRestriction::freeAngleMode(){
	if(isPlanarMode()){
		SetRestrictionMode(MGCursorRestriction::PLANAR);
	}else
		//else means the mode is ANGLE, ELEVATE, or TAB is enabled.
		SetRestrictionMode(MGCursorRestriction::FREE);
}

void MGCursorRestriction::addAngleMode(){
	if(isPlanarMode()){
		SetRestrictionMode(MGCursorRestriction::ANGLE_PLANAR);
	}else
		//else means the mode is ANGLE, ELEVATE, or TAB is enabled.
		SetRestrictionMode(MGCursorRestriction::ANGLE);
}

/// Set the angle data of angle restriction, does not update the mode.
void MGCursorRestriction::set_angle(double degree){
	if(degree<=1. || degree >=360.)
		degree=90.;//This is the default value.
	m_angle = degree;
}

/// Set the maximum distance from a position to the cursor.
/// This turns on the distance restriction on, but does not change
/// the other mode.
/// When dist<=0., the distance restriction is disabled.
void MGCursorRestriction::set_distance(double dist){
	if(dist <= 0.){
		m_distance.first = false;
	}else{
		m_distance.first = true;
		m_distance.second = dist;
	}
}
void MGCursorRestriction::set_distanceMode_ON(bool onoff){
	m_distance.first = onoff;
}

///Set restriction mode as input.
void MGCursorRestriction::SetRestrictionMode(
	CURSOR_RESTRICTION_MODE mode
){
	m_restriction_mode=mode;
}

///Set straight to map on in ELEVATION or TAB mode.
void MGCursorRestriction::set_straight_to_map(
	const MGStraight& sl
){
	m_straight_to_map.set_straight(MGSTRAIGHT_UNLIMIT,sl.direction(), sl.root_point());
}
	
///Set straight to map on in ELEVATION or TAB mode.
void MGCursorRestriction::set_straight_to_map(
		const MGPosition& P0,
		const MGPosition& P1
){
	m_straight_to_map.set_straight(MGSTRAIGHT_UNLIMIT,P1-P0, P1);
}

void MGCursorRestriction::freePlanarMode(){
	if(isAngleMode()){
		SetRestrictionMode(MGCursorRestriction::ANGLE);
	}else
		//else means the mode is ANGLE, ELEVATE, or TAB is enabled.
		SetRestrictionMode(MGCursorRestriction::FREE);
}

///Set the mode as PLANAR on the current view cplane.
///Restriction plane is on the cplane of the located view
/// and previous is the last point of locates().
void MGCursorRestriction::addPlanarToPreviousMode(){
	m_planarOnPrevious=true;

	if(isAngleMode()){
		SetRestrictionMode(MGCursorRestriction::ANGLE_PLANAR);
	}else
		//else means the mode is ANGLE, ELEVATE, or TAB is enabled.
		SetRestrictionMode(MGCursorRestriction::PLANAR);

}

///Set the mode as PLANAR.
///normal is the normal of the plane to restrict,
///and basePoint is the origin of the plane.
void MGCursorRestriction::set_planarToPoint(
	const MGVector& normal,
	const MGPosition& basePoint
){
	m_planarOnPrevious=false;
	if(isAngleMode())
		m_restriction_mode=ANGLE_PLANAR;
	else
		m_restriction_mode=PLANAR;
	m_straight_to_map.set_straight(MGSTRAIGHT_UNLIMIT,normal,basePoint);
}

const MGCursorRestriction mgNoCursorRestriction=MGCursorRestriction();
