/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/* ***************************************************** */
/**
 * @file LocateState3.cpp
 * @brief LocateState.h の実装。キーボードとマウスとカーソルイベント中心。
 */
#include "stdafx.h"
#include "IO/util.h"
#include "GLCoordDlg.h"
#include "fugenStatusBar.h"
#include "fugenView.h"
#include "Common/GridCursor.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

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

//Process F8 KB input data.
//Function's return value is 
//0 if some data is input.
//!=0 otherwise.
//		return 2;	//OK was not input(canceled or dialogue was closed)
//		return 3;	//No strings were input.
//		return 5;	//distance limitation was processed.
//		return 6;	//angle limitation was processed.
//		return 7;	//This is assumed not to happen. Maybe some bugs are included.
int MGLocateState::process_F8_input(
	fugenView* window,//The fugenView pointer where this event took place.
	MGPosition& cursr	//Input point will be output when same data is input.
){
	fugenDoc& doc=*(document());

	// 入力用のダイアログボックスの準備
	CGLCoordDlg dlg;
	const MGPosition& posRecent = doc.recent_input_position();
	if(posRecent.is_null()){
		dlg.DisableRelativeOptions();
	}else{
		dlg.DisplayPrevInputCoord(posRecent);
	}

	// ダイアログボックスを表示
	if(IDOK != dlg.DoModal())
		return 2;	//OK was not input.

	MGVector data;
	StringVectorConvert(dlg.m_str,data);
	if(data.is_null())
		return 3;
	cursr=data;
	CGLCoordDlg::Mode input_mode = dlg.GetMode();
	switch(input_mode){
	case CGLCoordDlg::WORLD_ABS_3D:
	case CGLCoordDlg::WORLD_REL_3D:
		// ワールド座標での位置入力
		if(input_mode == CGLCoordDlg::WORLD_REL_3D)
			cursr += posRecent;
		return 0;
	case CGLCoordDlg::CPLANE_ABS_3D:
	case CGLCoordDlg::CPLANE_REL_3D:{
		// CPlane座標での位置入力
		const MGConstructionPlane& cplane = window->cplane();
		cursr = cplane.convert_to_world(cursr);
		if(input_mode == CGLCoordDlg::CPLANE_REL_3D){
			const MGPlane& plane = cplane.plane();
			cursr -= plane.center();
			cursr += posRecent;
		}
		return 0;
	}
	case CGLCoordDlg::CPLANE_POLAR:{
		// CPlane 極座標
		//Here cursr[0]=length from the origin of the cplane, 
		//     cursr[1]=degree from cplane's uderiv().
		const MGConstructionPlane& cplane = window->cplane();
		const MGPlane& plane = cplane.plane();
		const MGPosition& origin = plane.root_point();
		MGUnit_vector dirx = plane.u_deriv();
		MGUnit_vector diry = plane.v_deriv();
	
		double theta=MGCL::degree_to_radian(cursr[1]);
		double radius=cursr[0];
		cursr=origin+radius*(cos(theta)*dirx+sin(theta)*diry);
		return 0;
	}
	default:
		return 7;	//This is assumed not to happen. Maybe some bugs are included.

	}//End of switch.
}

/// @return true when position data is input, false when some restriction data is input and
/// position data is not input.
bool MGLocateState::F8Locate(fugenView* window,const MGObject* snapObj){
	if(!window){
		window = document()->get_main_view();
	}
	VERIFY(window!=NULL);

	MGPosition cursr;
	if(process_F8_input(window, cursr))
		return false;

	//Data is input by F8 input process and cursr is effective.
	CPoint point;
	window->convert_to_screen(cursr,point);
	MGSnapPositions::snap_kind sk=MGSnapPositions::nopos;
	if(snapObj){
		MGPosition cursr2;
		sk=locate_object_snap_point(window, point, cursr2, snapObj, m_located_param);
		m_located_object = const_cast<MGObject*>(snapObj);
	}else{
		m_located_object=0;
		m_located_param=MGPosition(0.,0.);
	}

	m_snapKind=sk;
	LInfoVec& linfos=locates();
	m_CursorRestriction.adjust_cursor(window, 0, point,linfos,cursr);
	std::unique_ptr<MGLocateInfo> linfo(
		new MGLocateInfo(cursr,sk,m_located_object,m_located_param,point,window));

	if(linfos.empty() || !m_prohibitSamePointInput || linfos.back()->point_world()!=cursr){
		linfos.push_back(std::move(linfo));
		set_current_position(cursr);
		return true;
	}
	return false;
}

bool MGLocateState::UndoLocate(fugenView* window){
	LInfoVec& linfos=locates();
	if(linfos.empty()){
		return false;
	}
	cancel_last_locate();
	if(!window){
		window = document()->get_main_view();
	}

	MGLocateInfo linfo(cursor(),MGSnapPositions::DELETE_POINT,0,0.,CPoint(),window);
	bool terminate=OnLocated(linfo);
	document()->InvalidateAllStdViews();
	return terminate;
}

bool MGLocateState::OnKeyDown(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nChar,	UINT nRepCnt, UINT nFlags
		//These parameters are of CWnd::OnKeyDown. See the document.
){
	bool terminate=false;
	switch(nChar){
	case VK_F8:
		if(F8Locate(window)){
			terminate=OnLocated(*(locates().back()));
		}
		break;

	case VK_BACK:
		terminate=UndoLocate(window);
		break;

	case VK_TAB:
		if(locates().empty() || 
			m_CursorRestriction.isAETUpdateProhibitted() ||
			m_CursorRestriction.isPUpdateProhibitted()
			)
			break;

		if(m_CursorRestriction.mode()==MGCursorRestriction::TABMODE)
			disable_AETPmode();
		else
			setTabRestriction(locates().back()->point_world(),cursor());
		break;

	case VK_ESCAPE: 
	case VK_RETURN:
		return MGCommandBase::OnKeyDown(window,nChar,nRepCnt,nFlags);

	default:;
	}
	if(!terminate){
		prompt_message();
		draw_temporary();
	}
	return terminate;
}

bool MGLocateState::processLButtonDown(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags,CPoint point,//These parameters are of CWnd::OnLButtonDown. See the document.
	bool dontCareOnlyOnObject//true if do not care if only on object by MGLocateOnObjects
){
	fugenDoc& doc=window->document();
	LInfoVec& linfos=locates();
	MGPosition cursr;

	const MGObject* pObj = m_located_object;
	m_snapKind=locate_object_snap_point(window, point, cursr, pObj, m_located_param);
	m_located_object = const_cast<MGObject*>(pObj);
	m_CursorRestriction.adjust_cursor(window, nFlags, point,linfos,cursr);

	bool terminate=false;
	if(MK_CONTROL&nFlags){//If control key is pressed.
		if(!m_CursorRestriction.isAETPUpdateProhibitted()){
			if(m_CursorRestriction.mode() == MGCursorRestriction::ELEVATION){
				m_CursorRestriction.SetRestrictionMode(MGCursorRestriction::FREE);
			}else{
				m_CursorRestriction.SetRestrictionMode(MGCursorRestriction::ELEVATION);
				const MGVector& N=window->cplane().plane().normal();
				m_CursorRestriction.set_straight_to_map(cursr,cursr+N);
			}
		}
		fugenStatusBar& sbar = theStatusBar();
		sbar.updateDisplay();
	}else if(dontCareOnlyOnObject){
		if(linfos.empty() || !m_prohibitSamePointInput || linfos.back()->point_world()!=cursr){
			set_current_position(cursr);
			terminate=OnLocated(append_locate_info(cursr,window,&point));
			if(!terminate){
				prompt_message();
				draw_temporary();
			}
		}
	}else
		SetStatusMessage(IDS_LOCATE_ON_OBJECT);

	doc.InvalidateAllStdViews();
	return terminate;
}

//When a point is located, the data will be output to documnet's cursor().
bool MGLocateState::OnLButtonDown(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnLButtonDown. See the document.
){
	return processLButtonDown(window,nFlags,point,true);
}

bool MGLocateState::OnMouseMove(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnMouseMove. See the document.
){
	snap(window, nFlags, point);
	LInfoVec& linfos=locates();

	// マウスカーソル位置を補正、決定する。
	// 補正後の座標でドキュメントとステータスバーの座標情報も更新する。
	MGPosition crsr(cursor());
	m_CursorRestriction.adjust_cursor(window, nFlags, point, linfos, crsr);

	// ステータスバーの座標情報とドキュメントの持つカーソル位置を同時更新する。
	window->displaySet_position(crsr);

	// 最後に再描画
	draw_temporary(true, false);//true:erase the old pictures and false:do not redraw
	window->redraw();
	return false;
}

bool MGLocateState::OnSetCursor(
	fugenView* pView,
	CWnd* pWnd,
	UINT nHitTest,
	UINT message)
{
	if(m_hCursor){
		// snap メンバー関数を参照。
		::SetCursor(m_hCursor);
		return true;
	}

	// 基底クラスの OnSetCursor に任せる。
	return MGCommandBase::OnSetCursor(pView, pWnd, nHitTest, message);
}

void MGLocateState::ModifyContextMenu(CMenu& popup)
{
	// 規定の Cancel を加える。
	MGCommandBase::ModifyContextMenu(popup);

	// LocateState 共通のメニューアイテムを加える。
	InsertMenu(IDR_MENU_LOCATE_STATE, popup);
}

bool MGLocateState::CanHandle(UINT nID) const
{
	if(MGCommandBase::CanHandle(nID)){
		return true;
	}

	return (nID ==ID_COMMAND_LOCATE_STATE_OK
		||	nID == ID_COMMAND_LOCATE_STATE_UNDO
		||	nID == ID_COMMAND_LOCATE_STATE_DIRECT
		);
}

bool MGLocateState::HandleMessage(UINT nID)
{
	if(MGCommandBase::HandleMessage(nID)){
		return true;
	}

	switch(nID){
	case ID_COMMAND_LOCATE_STATE_OK:
		{
			//get_main_viewにVK_ESCAPEイベントをポスト
			fugenView* pView = document()->get_main_view();
			if(pView){
				pView->PostMessage(WM_KEYDOWN, static_cast<WPARAM>(VK_RETURN));
			}
		}
		return true;
	case ID_COMMAND_LOCATE_STATE_UNDO:
		UndoLocate();
		return true;
	case ID_COMMAND_LOCATE_STATE_DIRECT:
		F8Locate();
		return true;
	}

	return false;
}

bool MGLocateState::HandleUpdateUI(UINT nID, CCmdUI* pCmdUI)
{
	if(MGCommandBase::HandleUpdateUI(nID, pCmdUI)){
		return true;
	}

	switch(nID){
	case ID_COMMAND_LOCATE_STATE_OK:
		pCmdUI->Enable(!locates().empty());
		return true;
	case ID_COMMAND_LOCATE_STATE_UNDO:
		pCmdUI->Enable(!locates().empty());
		return true;
	case ID_COMMAND_LOCATE_STATE_DIRECT:
		pCmdUI->Enable();
		return true;
	}

	return false;
}
