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

#include "stdafx.h"
#include <set>

#include "mg/DNameControl.h"
#include "mg/Straight.h"
#include "mg/CSisect.h"
#include "mg/Point.h"
#include "topo/Shell.h"
#include "MainFrm.h"
#include "fugen.h"
#include "fugenDoc.h"
#include "fugenView.h"
#include "Common/LocateState.h"

//
//Implement MGLocateState Class.
//MGLocateState is a class to get point data(locate).
//

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

//////////Constructor//////////

// Standard constructor. This state belongs to owner.
MGLocateState::MGLocateState(
	MGCommandStateOwner* owner,
	SNAP_ATTRIB_LOCKKIND lock_snap_attrib,//indicates if lock the snap attrib.
	RUBBER_KIND rubberband,//Specify what type of rubberband is necessary.
					//0: no rubber band, 1:line rubber band, 2:rect rubber band.
	IPOINT_DRAWER_KIND input_drawer,//specify what type of input points drawer be used.
					//0:no drawer, 1:points only, 2:polyline only, 3:points and polyline.
	bool docSnapObjectives///<Indicates if this doc's MGObjects be
		///set as the snap objecties, true:set, false:not set.
):MGCommandBase(owner),
m_prohibitSamePointInput(true),
m_lock_snap_attrib(lock_snap_attrib),
m_CursorRestriction(theApp.MainFrame()->status_bar().restrictionMode()),
m_snap_attrib(owner->document()->getSnapAttr()),
m_end_data(MGSnapPositions::endpos),m_knot_data(MGSnapPositions::knotpos),
m_vertex_data(MGSnapPositions::vertexpos),m_center_data(MGSnapPositions::centerpos),
m_located_object(nullptr){
	document()->enableGridSnap(m_snap_attrib.getGrid());

	set_drawer(input_drawer,rubberband);
	if(docSnapObjectives){
		MGGroup* cur = current_group();
		if(cur)
			set_snap_objectives(*cur);
	}
}

// constructor that does not have an owner.
MGLocateState::MGLocateState(
	fugenDoc*  pDoc,		//document.
	UINT	command_id,	//command id
	RUBBER_KIND rubberband,
		//Specify what type of rubberband is necessary.
	IPOINT_DRAWER_KIND input_drawer,
		//specify what type of input points drawer be used.
	bool docSnapObjectives///<Indicates if this doc's MGObjects be
		///set as the snap objecties, true:set, false:not set.
):MGCommandBase(pDoc,command_id),
m_prohibitSamePointInput(true),
m_lock_snap_attrib(UNLOCK_SNAP_ATTRIB),
m_snap_attrib(pDoc->getSnapAttr()),
m_CursorRestriction(theApp.MainFrame()->status_bar().restrictionMode()),
m_end_data(MGSnapPositions::endpos),m_knot_data(MGSnapPositions::knotpos),
m_vertex_data(MGSnapPositions::vertexpos),m_center_data(MGSnapPositions::centerpos),
m_located_object(nullptr){
	document()->enableGridSnap(m_snap_attrib.getGrid());

	set_drawer(input_drawer,rubberband);
	if(docSnapObjectives){
		MGGroup* cur = current_group();
		if(cur)
			set_snap_objectives(*cur);
	}
}

//Virtual Destructor
MGLocateState::~MGLocateState(){
}

//////////Member Function//////////

//set command drawer.
void MGLocateState::set_drawer(
	IPOINT_DRAWER_KIND input_drawer,//specify what type of input points drawer be used.
		//0:no drawer, 1:points only, 2:polyline only, 3:points and polyline.
	RUBBER_KIND rubberband//Specify what type of rubberband is necessary.
		//0: no rubber band, 1:line rubber band, 2:rect rubber band.
){
	m_drawer.m_cursor=&(cursor());
	m_drawer.m_gridCursorTexture=getCursorTexture();;

	m_drawer.m_rubberKind=rubberband;
	m_drawer.m_pointDrawKind=input_drawer;
	m_drawer.m_lpoints=&(locates());;
	m_drawer.m_pointColor=MGColor::get_instance(MGColor::White);
	m_drawer.m_lineColor=MGColor::get_instance(MGColor::White);
	m_drawer.m_lineToCursorColor=MGColor::get_instance(MGColor::White);
	m_drawer.m_LWidthPLine=m_drawer.m_LWidthRubber=1.f;
}
		
//set command drawer.
void MGLocateState::setDrawerPointLine(
	IPOINT_DRAWER_KIND input_drawer//specify what type of input points drawer be used.
		//0:no drawer, 1:points only, 2:polyline only, 3:points and polyline.
){
	m_drawer.m_pointDrawKind=input_drawer;
}
		
//set command drawer.
void MGLocateState::setDrawerRubber(
	RUBBER_KIND rubberband//Specify what type of rubberband is necessary.
		//0: no rubber band, 1:line rubber band, 2:rect rubber band.
){
	m_drawer.m_rubberKind=rubberband;
}

void MGLocateState::setDrawerColorPoint(const MGColor& PColor){
	m_drawer.m_pointColor=PColor;
}
void MGLocateState::setDrawerColorLine(const MGColor& LColor){
	m_drawer.m_lineColor=LColor;
}
void MGLocateState::setDrawerColorRubber(const MGColor& RColor){
	m_drawer.m_lineToCursorColor=RColor;
}
void MGLocateState::setDrawerLWidthPLine(float width){
	m_drawer.m_LWidthPLine=width;
}
void MGLocateState::setDrawerLWidthRubber(float width){
	m_drawer.m_LWidthRubber=width;
}

//Append the locate info to the back of locates().
//MGObject* data and the parameter are refered from
//this m_located_object and m_located_param.
MGLocateInfo& MGLocateState::append_locate_info(
	const MGPosition& pointW,	//position data in world coordinates.
	fugenView* window,		//Window the point is input.
	const CPoint* pointS		//postion data in screen coordinates.
){
	set_current_position(pointW);
	CPoint Ps(0,0);
	if(!pointS)
		pointS=&Ps;
	MGLocateInfo* linfo=new MGLocateInfo(
		pointW,m_snapKind,m_located_object,m_located_param,*pointS,window);
	locates().emplace_back(linfo);
	return *linfo;
}

void MGLocateState::extract_near_locate_points(
	const std::list<const MGGel*>& objectives
){
	std::list<const MGGel*>::const_iterator i=objectives.begin(), ie=objectives.end();
	for(; i!=ie; i++){
		const MGCurve* crv=dynamic_cast<const MGCurve*>(*i);
		if(crv){
			m_near_data.push_back(crv);
			continue;
		}
		const MGGroup* groupi = dynamic_cast<const MGGroup*>(*i);
		if(groupi){
			std::list<const MGGel*> objectives2;
			MGGroup::const_iterator j=groupi->begin(), jend=groupi->end();
			for(; j!=jend; j++)
				objectives2.push_back(j->get());
			extract_near_locate_points(objectives2);
		}
	}
}

void MGLocateState::extract_locate_points(
){
	if(snap_end())
		m_end_data.extract(m_objectives);
	if(snap_near())
		extract_near_locate_points(m_objectives);
	if(snap_knot())
		m_knot_data.extract(m_objectives);
	if(snap_center())
		m_center_data.extract(m_objectives);
	if(snap_vertex())
		m_vertex_data.extract(m_objectives);
}

void generate_near_display_list(
	const std::vector<const MGCurve*>& curves,
	mgVBO& nearDlistName
){
	std::vector<const MGCurve*>::const_iterator
	i=curves.begin(), ie=curves.end();
	for(; i!=ie; i++){
		nearDlistName.drawGel(**i);
	}
	nearDlistName.setDirty(false);
}

//initiate the snap data.
void MGLocateState::initiate_snap_data(){
	m_end_data.clear();
	m_knot_data.clear();
	m_near_data.clear();
	m_vertex_data.clear();
	m_center_data.clear();
	extract_locate_points();

	if(snap_end())
		m_end_data.make_display_list();
	if(snap_knot())
		m_knot_data.make_display_list();
	if(snap_vertex())
		m_vertex_data.make_display_list();
	if(snap_near())
		generate_near_display_list(m_near_data,m_nearDlistName);
	if(snap_center())
		m_center_data.make_display_list();
}

//Initiate the class.
//Overrided initiate must invoke MGLocateState::initiate_tool first.
bool MGLocateState::initiate_tool(){
	MGCommandBase::initiate_tool();
	clear_input_positions();
	initiate_snap_data();
	attach_drawer(&m_drawer);
	VERIFY(m_hCursor = ::AfxGetApp()->LoadCursor(IDC_CURSOR_SNAP));
	prompt_message();
	return false;
}

MGCurve* MGLocateState::snappedCurve(){
	return dynamic_cast<MGCurve*>(m_located_object);
}
const MGCurve* MGLocateState::snappedCurve() const{
	return dynamic_cast<const MGCurve*>(m_located_object);
}
double MGLocateState::snappedCurveParam()const{
	return m_located_param[0];
}

//Pick the position data from MGSnapPositions data points.
//Function's return value is 0 if no objects are picked, >0 if an object is picked.
int MGLocateState::pick_position(
	fugenView* window,//The fugenView pointer where (sx,sy) belongs to.
	const float center[2],	//screen coordinates whose origin is (left, bottom).
	const MGSnapPositions& points,
	MGPosition& point,	//point data will be output when function's return value !=0.
	const MGObject*& obj,//Picked object will be returned.
	MGPosition& param	//When points's get_snap_kind() value is end, knot(Curve),
				//or vertex(FSurface), the point's parameter value of the object
				//be returned in param.
)const{
	std::set<unsigned> selected;
	MGSnapPositions* spos=const_cast<MGSnapPositions*>(&points);
	const float* apertr=pick_aperture();
	float centrApertr[4] = {center[0],center[1],apertr[0],apertr[1]};
	int objnum=window->pick_to_select_buf(centrApertr,spos,selected);
	if(objnum)
		points.get_pick_data(selected,point,obj,param);
	return objnum;
}
int MGLocateState::pick_position(
	fugenView* window,//The fugenView pointer where point_in belongs to.
	const CPoint& point_in,	//Window's point coordinates.
	const MGSnapPositions& points,
	MGPosition& point,	//point data will be output when function's return value !=0.
	const MGObject*& obj,//Picked object will be returned.
	MGPosition& param	//When points's get_snap_kind() value is end, knot(Curve),
				//or vertex(FSurface), the point's parameter value of the object
				//be returned in param.
)const{
	int sx,sy;
	window->change_sc(point_in,sx,sy);
	float center[2]={(float)sx,(float)sy};
	return pick_position(window,center,points,point,obj,param);
}

//pick a curve and locate the near point data.
//pick_near_position workd only when m_near is turned on.
//function's return value is 0 if no positions are located.
//>0 if a position is located, in this case, crv and t will have output.
int MGLocateState::pick_near_position(
	fugenView* window,//The fugenView pointer where (sx,sy) belongs to.
	const float center[2],///<screen coordinates whose origin is (left, bottom).
	const MGCurve*& crv,//the curve and the point's parameter value of the curve
						//will be returned.
	double& t			
){
	std::set<unsigned> selected;
	MGOpenGLView& glv=*window;
	window->make_RC_current();
	const float* apertr=pick_aperture();
	float centrApertr[4] = {center[0],center[1],apertr[0],apertr[1]};
	int objnum=glv.pick_to_select_buf(centrApertr,&m_nearDlistName,selected);
	if(objnum){
		MGDNameControl& dnc=getDNameControlInstance();
		unsigned dlnameObj=*(selected.begin());
		MGAttribedGel* objA=dnc.Gelpointer_from_dlistName(dlnameObj);//Lowest name is MGObject.
		if(objA){
			crv=reinterpret_cast<const MGCurve*>(objA);
			glv.get_near_position(crv,center,t);
				//parameter value of the curve crv near to (sx,sy) will be returned in t.
		}else
			objnum=0;
	}
	window->release_dc();
	return objnum;
}
int MGLocateState::pick_near_position(
	fugenView* window,//The fugenView pointer where point_in belongs to.
	CPoint point_in,	//Window's point coordinates.
	const MGCurve*& crvo,//the curve and the (sx,sy)'s parameter value of the curve
						//will be returned.
	double& t			
){
	int sx,sy;
	window->change_sc(point_in,sx,sy);
	float center[2]={(float)sx,(float)sy};///<screen coordinates whose origin is (left, bottom).
	return pick_near_position(window,center,crvo,t);
}

//Locate a point from the screen coordinate point_in to world coordinate point.
//Function's return value is snap_kind of the point located.
MGSnapPositions::snap_kind MGLocateState::locate_object_snap_point(
	fugenView* window,//The fugenView pointer where point_in  belongs to.
	const CPoint& point_in,	//Window's point coordinates.
	MGPosition& point,	//located point will be output
	const MGObject*& obj,//When function's return value is nearpos, end, knot(Curve),
				//vertex(FSurface), or center(Curve&FSurface),
				//the point's parameter value of the object be returned.
	MGPosition& param
				
){
	ASSERT_VALID(window);
	int sx,sy;
	window->change_sc(point_in,sx,sy);
	float center[2]={(float)sx,(float)sy};

	obj=0;
	window->make_RC_current();
	
	param.resize(1);
	if(snap_end()){
		if(pick_position(window,center,m_end_data,point,obj,param))
			return MGSnapPositions::endpos;
	}
	if(snap_vertex()){
		if(pick_position(window,center,m_vertex_data,point,obj,param))
			return MGSnapPositions::vertexpos;
	}
	if(snap_knot()){
		if(pick_position(window,center,m_knot_data,point,obj,param))
			return MGSnapPositions::knotpos;
	}
	if(snap_center()){
		if(pick_position(window,center,m_center_data,point,obj,param))
			return MGSnapPositions::centerpos;
	}
	if(snap_near()){
		const MGCurve* crv;
		if(pick_near_position(window,center,crv,param(0))){
			point=crv->eval(param(0));
			obj=crv;
			return MGSnapPositions::nearpos;
		}
	}
	param.resize(0);
	point = window->locate(point_in);
	return MGSnapPositions::nopos;
}

//Delete snap objective display lists.
void MGLocateState::delete_snap_display_list(){
	m_end_data.clear();
	m_knot_data.clear();
	m_vertex_data.clear();
	m_center_data.clear();
	m_nearDlistName.clearElements();
}

//Terminate the class.
bool MGLocateState::terminate_tool(bool cancel){
	detach_drawer(&m_drawer);
	clear_input_positions();
	delete_snap_display_list();
	disable_AETPmode();//Good or bad?????
	return MGCommandBase::terminate_tool(cancel);
}

void MGLocateState::turn_on_extract_end(){
	if(snap_end())
		return;

	turn_on_end();
	m_end_data.clear();
	m_end_data.extract(m_objectives);
	m_end_data.make_display_list();
}

void MGLocateState::turn_on_extract_knot(){
	if(snap_knot())
		return;

	turn_on_knot();
	m_knot_data.clear();
	m_knot_data.extract(m_objectives);
	m_knot_data.make_display_list();
}

void MGLocateState::turn_on_extract_center(){
	if(snap_center())
		return;

	turn_on_center();
	m_center_data.clear();
	m_center_data.extract(m_objectives);
	m_center_data.make_display_list();
}

void MGLocateState::turn_on_extract_near(){
	if(snap_near())
		return;

	turn_on_near();
	m_near_data.clear();
	extract_near_locate_points(m_objectives);
	generate_near_display_list(m_near_data,m_nearDlistName);
}

void MGLocateState::turn_on_extract_vertex(){
	if(snap_vertex())
		return;

	turn_on_vertex();
	m_vertex_data.clear();
	m_vertex_data.extract(m_objectives);
	m_vertex_data.make_display_list();
}

///update snap mode by input sattrib.
///update this locate's snap modes, which do no include grid snap.
void MGLocateState::updateSnapMode (
	const MGSnapAttrib& sattrib
){
	if(m_lock_snap_attrib==LOCK_SNAP_ATTRIB)
		return;

	if(sattrib.getEnd())
		turn_on_extract_end();
	else
		turn_off_end();

	if(sattrib.getKnot())
		turn_on_extract_knot();
	else
		turn_off_knot();

	if(sattrib.getNear())
		turn_on_extract_near();
	else
		turn_off_near();

	if(sattrib.getVertex())
		turn_on_extract_vertex();
	else
		turn_off_vertex();

	if(sattrib.getCenter())
		turn_on_extract_center();
	else
		turn_off_center();
}

void MGLocateState::set_snap_objectives(const std::list<const MGGel*>* objectives){
	if(!objectives)
		m_objectives.clear();
	else
		m_objectives=*objectives;
}

//update snap objectives.
void MGLocateState::update_snap_objectives(
	const MGGroup& objectives
){
	delete_snap_display_list();
	set_snap_objectives(objectives);
	initiate_snap_data();
}
void MGLocateState::update_snap_objectives(
	const std::list<const MGGel*>* objectives
){
	delete_snap_display_list();
	set_snap_objectives(objectives);
	initiate_snap_data();
}
void MGLocateState::update_snap_objectives(
	const MGGel* objective
){
	if(!objective)
		update_snap_objectives();
	else{
		std::list<const MGGel*> objectives(1,objective);
		update_snap_objectives(&objectives);
	}
}

///Extracts point_world() data from linfos.
void extract_points_world(
	const LInfoVec& linfos, 
	std::vector<MGPosition>& ipos ///Extracted data will be output.
){
	size_t n=linfos.size();
	ipos.resize(n);
	for(size_t i=0; i<n; i++){
		ipos[i]=linfos[i]->point_world();
	}
}
