/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
/**
 * @file CommandBase.cpp
 * @brief CommandBase.h の実装
 *
 * Define MGCommandBase Class.
 * MGCommandBase is an abstract class to provide the base of all the command function.
 *
 * Subclasses of MGCommandBase are basic command state tools like
 * MGSelectState, MGLocateState.
 *
 */
#include "stdafx.h"
#include "calc/mgfunctor.h"
#include "fugen.h"
#include "Misc/UserPreference.h"
#include "fugenView.h"
#include "Undo/GelAddAction.h"
#include "Undo/GelRemoveAction.h"
#include "Undo/GelReplaceAction.h"
#include "Common/LocateInfo.h"
#include "Common/CommandStateOwner.h"
#include "mgModelessDialogue.h"

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

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

// Construct an object of MGCommandBase with the target document
// and the initial pick mode.
MGCommandBase::MGCommandBase(
	fugenDoc*  pDoc,		//document.
	UINT	command_id	//command id
):m_document(pDoc),m_eventView(nullptr),m_owner_cmd(0),
m_locked(true),m_nCmdID(command_id), m_modelesDialog(nullptr){
}

// Construct an object of MGCommandBase from the owner.
MGCommandBase::MGCommandBase(
	MGCommandStateOwner* owner
):m_document(0),m_eventView(nullptr),m_owner_cmd(owner),
m_locked(true), m_modelesDialog(nullptr){
	if(owner){
		m_document=owner->document();
		m_nCmdID=owner->command_id();
	}
}

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


///command drawer attacher. attach_drawer() sets the mgVBO reference for this command.
///The target view is the Document's mainview' common pictures if window=0.
///Common means the pictures are drawn to all the child windows.
///When window is specified, the pictures are set for the window's specific pictures.
///Specific means they are not drawn to other child or sibling windows.
///When drawer is attached, it must be detached at the command termination.
///The attached drawer will be invoked in MGOpenGLView's drawScene().
void MGCommandBase::attach_drawer(mgVBO* drawer, fugenView* window){
	fugenView* view;
	bool common=true;
	if(window){
		common=false;
		view=window;
	}else{
		view=document()->get_main_view();
	}
	if(!view)
		return;
	view->attach_drawer(drawer,common);
}

//Detach the command drawer that is attached by attach_drawer().
///The target view is the Document's mainview' common pictures if window=0.
///When window is specified, the pictures are detached from the window's specific pictures.
void MGCommandBase::detach_drawer(mgVBO* drawer, fugenView* window){
	fugenView* view;
	bool common=true;
	if(window){
		common=false;
		view=window;
	}else{
		view=document()->get_main_view();
	}
	if(!view)
		return;
	view->detach_drawer(drawer,common);
}

//Append newobj to the document and invoke CGelAddAction::add.
//The ownership of newobj is transfered to the document.
void MGCommandBase::add_object_to_current_group(MGObject* newobj){
	if(newobj){
		MGGroup* parent = current_group();
		MGGelPosition gelp(parent,newobj);
		add_object_to_document(gelp);
	}
}

// Append newobj to the document and invoke CGelAddAction::add.
//All the ownership of MGGel's in gelp are transfered to the document.
void MGCommandBase::add_object_to_document(const MGGelPosition& gelp){
	CGelAddAction* aAction = new CGelAddAction(document(), gelp);
	aAction->Do();
}
// Append newobj to the document and invoke CGelAddAction::add.
//All the ownership of MGGel's in gelp are transfered to the document.
void MGCommandBase::add_object_to_document(const MGGelPositions& gelps){
	CGelAddAction* aAction =new CGelAddAction(document(), gelps);
	aAction->Do();
}

///Append newobj in the range [first, last) to the document
///and invoke CGelAddAction::add.
void MGCommandBase::addListGelsToCurrentGroup(
	std::list<std::unique_ptr<MGGel>>&& objs
){
	addUniqueGelsToCurrentGroup(objs.begin(), objs.end());
}

///Append newobj in the range [first, last) to the document
///and invoke CGelAddAction::add.
void MGCommandBase::addVectorGelsToCurrentGroup(
	std::vector<std::unique_ptr<MGGel>>&& objs
){
	addUniqueGelsToCurrentGroup(objs.begin(), objs.end());
}

//cancel only the last locate.
void MGCommandBase::cancel_last_locate(){
	LInfoVec& linfos=locates();
	if(linfos.empty())
		return;

	linfos.pop_back();
	if(!linfos.empty()){
		set_current_position(linfos.back()->point_world());
	}
}

//clear input position data.
void MGCommandBase::clear_input_positions(){
	locates().clear();
}

//Clear this command's owner's command state.
//This will cause the termination of owner command.
void MGCommandBase::clear_owner_command_state(){
	assert(m_owner_cmd);
	m_owner_cmd->clear_state();
}

void MGCommandBase::clear_pick_object(){
	set_current_object();
}

//Exclude objs from the curennt object.
void MGCommandBase::exclude_pick_object(
	const MGPickObjects& objs	//selected objects at this selection operation.
){
	MGPickObjects cobjs=current_objects();
	cobjs.remove(objs);
	set_current_object(cobjs);
}

// Command ID for Windows.
UINT MGCommandBase::command_id() const{
	const MGCommandStateOwner* owner=get_owner_command();
	if(owner)
		return owner->command_id();
	else
		return m_nCmdID;
}

///Return the view wher an event took place.
fugenView* MGCommandBase::eventView(){
	MGCommandStateOwner* owner=get_owner_command();
	if(owner)
		return owner->eventView();
	else
		return m_eventView;
}

//Get the command name from the menu string.
void MGCommandBase::commandNameFromMenu(CString& name){
	fugenView* view=document()->get_main_view();
	CMenu* menu=view->GetParentOwner()->GetMenu();
	menu->GetMenuString(command_id(),name,MF_BYCOMMAND);

	// もしあれば邪魔な制御文字等を削除する
	// MFC には正規表現ライブラリーはない？

	// ショートカットキー部分を削除
	int iIndex = name.Find(_T('\t'));
	if(iIndex != -1){
		name.Delete(iIndex, name.GetLength() - iIndex);
	}
	// メニューキー部分を削除 (jp)
	iIndex = name.Find(_T("(&"));
	if(iIndex != -1){
		name.Delete(iIndex, name.GetLength() - iIndex);
	}
	else{
		// & 文字自体を削除 (en)
		name.Remove(_T('&'));
	}
	// ... を削除
	iIndex = name.Find(_T("..."));
	if(iIndex != -1){
		name.Delete(iIndex, name.GetLength() - iIndex);
	}
}

//Get the document's current object, which is the target group of fugenDoc that contains
//objects to add or update.
MGGroup* MGCommandBase::current_group(){
	return document()->current_group();
}

//Set the current group for the document update.
void MGCommandBase::set_current_group(MGGroup* grp){
	return document()->set_current_group(grp);
}

//Test if the current objects include the target types. When current objects included
//target types, this will return true and the objects will be extracted into targets.
//The document's curent objects and views will not be updated.
//To update them, use set_current_object.
bool MGCommandBase::current_object_is_valid(
	const MGAbstractGels& types
)const{
	MGPickObjects targets=current_objects();
	targets.reset_objects(types);
	return targets.size()>=1;
}
bool MGCommandBase::current_object_is_valid(
	const MGAbstractGels& types,
	MGPickObjects& targets	//Target object.
)const{
	targets=current_objects();
	targets.reset_objects(types);
	return targets.size()>=1;
}

//Get the reference to the doc's current object.
const MGPickObjects& MGCommandBase::current_objects()const{
	return document()->current_objects();
}
//Get the reference to the doc's current object.
MGPickObjects& MGCommandBase::current_objects(){
	return document()->current_objects();
}

const MGPosition& MGCommandBase::cursor() const{
	return document()->cursor();
}

///Set the current position for the doc, i.e.
//1. set doc.cursor()=current.
//2. set_recent_input_position(current);
void MGCommandBase::set_current_position(
	const MGPosition& current
){
	fugenDoc& doc=*document();
	doc.set_cursor(current);
	doc.set_recent_input_position(current);
}

//Select objects of input type from the document's current objects.
//The document's current object will be unchanged.
//Function's return value is pickobjects selected.
MGPickObjects MGCommandBase::select_from_current_objects(const MGAbstractGels& types){
	return current_objects().select(types);
}

//Select objects of specified type from the document's current objects,
//and reset the current objects with them.
void MGCommandBase::reset_current_objects(const MGAbstractGels& types){
	MGPickObjects newObjs=current_objects();
	newObjs.reset_objects(types);
	set_current_object(newObjs);
}

//Get the document's MGGroup pointer.
MGGroup* MGCommandBase::doc_root(){
	ASSERT_VALID(m_document);
	return &m_document->m_group;
}

//Erases images of temporary objects from views by the command id
//and the object id gel. When gel==null(0), all the images of this command id
//will be erased.
void MGCommandBase::erase_temporary(
	const MGGel* gel, bool redraw
){
	if(gel){
		m_document->DeleteDisplayList(m_nCmdID,gel,redraw);
	}else
		m_document->DeleteDisplayList(m_nCmdID,redraw);
}

// Return the GelPosition of obj_in which is currently in the document.
// If given obj_in is not in the document, the result is undefined.
MGGelPosition MGCommandBase::gelpos(const MGObject* obj_in){
	ASSERT(document());
	ASSERT(obj_in);
	MGObject* obj = const_cast<MGObject*>(obj_in);

	MGGroup& gp = *doc_root();
	MGGroup* stored = 0;
	MGGroup::iterator i = gp.find(obj, stored);
	MGGelPosition gelp;
	if(!stored)
		return gelp; // error

	gelp.set_leaf_object(obj);
	gelp.set_top_group(stored);

	return gelp;
}
MGGelPosition MGCommandBase::gelpos(MGPickObject& po){
	ASSERT(document());
	return MGGelPosition(po);
}

//Get the document's main view.
fugenView* MGCommandBase::get_main_view(){
	ASSERT(document());
	return m_document->get_main_view();
}
MGOpenGLView& MGCommandBase::get_main_glview(){
	ASSERT(get_main_view());
	return *get_main_view();
}

/// オブジェクトの「初期状態のコピー」を返す。
/// @param[in] pDoc コマンドを利用するドキュメント
/// @param[in] nCmdUI リソース定義されているコマンド ID
/// @return 初期状態のコマンドオブジェクト。またはコピーが作れない場合は
///         0 を返す。
MGCommandBase* MGCommandBase::initial_clone(fugenDoc* pDoc)const{
	return 0;
}

//Initiate the class.
//Overrided initiate must invoke MGCommandBase::initiate_tool first.
bool MGCommandBase::initiate_tool(){
	MGCommandBase* owner=get_owner_command();
	if(owner){
		m_command_name=owner->m_command_name;
	}else{
		commandNameFromMenu(m_command_name);

		// コマンドを開始する旨を出力ウィンドウに表示する。
		CString strMsg; strMsg.Format(IDS_COMMAND_ENTER, m_command_name);
		COUT << (TCAST)strMsg << std::endl;
		SetStatusMessage(IDS_PROMPT_COMPUTE);
	}
	setEventView(theApp.currentfugenView(document()));//Initial view.
	return false;
}

//Get the input positions array.
const LInfoVec& MGCommandBase::locates()const{
	const MGCommandBase* owner=get_owner_command();
	if(owner)
		return owner->locates();
	return m_locates;
}
LInfoVec& MGCommandBase::locates(){
	MGCommandBase* owner=get_owner_command();
	if(owner)
		return owner->locates();
	return m_locates;
}

//Test if the command tool is current and breaking into the other command.
bool MGCommandBase::is_breaking_command()const{
	return m_document->is_breaking_command(this);
}

//redraw all the standard views of the document.
void MGCommandBase::InvalidateAllStdViews(){
	m_document->InvalidateAllStdViews();
}

// 
MGGelPosition MGCommandBase::make_gelpos(MGObject* obj){
	ASSERT(document());
	fugenDoc* doc=document();
	MGGroup* cgrp=doc->current_group();
	return MGGelPosition(cgrp,obj);
}

// OK.
MGGelPosition MGCommandBase::make_gelpos(MGObject* obj, MGGroup* parent){
	ASSERT(parent);
	return MGGelPosition(parent,obj);
}

void MGCommandBase::redraw(){
	m_document->InvalidateAllStdViews();
}

///Draw temporary pictures, making mgSysGL only by this command id.
///Pictures are drawn invoking do_make_temporary_display(). To use this
///draw_temporary(), subclass must define its own do_make_temporary_display().
void MGCommandBase::draw_temporary(
	bool clear_so_far,//true if all of the pictures drawn so far for this 
		//command id is to clear.
	bool redraw	///<true if redraw action is necessary.
){
	// それまでで描いた一時描画を消去
	if(clear_so_far)
		erase_temporary();

	// OpenGLコンテキストに依存する描画を行う
	fugenView* view = get_main_view();
	mgSysGL* sgl = view->push_back_to_sysgl(m_nCmdID,0);
	do_make_temporary_display(*sgl,eventView());
	sgl->setDirty(false);

	// ビューを再描画
	if(redraw)
		InvalidateAllStdViews();
}

//Draw temporary pictures using mgSysGL.
//Pictures are drawn by sysgl->drawSysGL().
//Pictures drawn by draw_temporary are maintained by UndoManager, and
//pictures will be updated on the actions of add, remove, and replace.
void MGCommandBase::draw_temporary(
	mgSysGL* sysgl,	//mgSysGL to draw the pictures to keep.
					//This must be a newed object, and the ownership will be transfered
					//to MGCommandBase(actually to main view's MGOpenGLView).
	bool clear_so_far,//true if all of the pictures drawn so far for this 
					//command id is to clear.
	bool redraw
){
	// それまでで描いた一時描画を消去
	if(clear_so_far)
		erase_temporary();

	// ドキュメントに関連付けられた各ビューに対して
	sysgl->makeSysGLDisplayList(get_main_glview());

	// ビューを再描画
	if(redraw)
		InvalidateAllStdViews();
}

//remove the objects specified in gelp, or gelps from the document.
void MGCommandBase::remove_object_from_document(
	const MGGelPosition& gelp
){
	MGGelPositions gelps(gelp);
	(new CGelRemoveAction(document(),gelps))->Do();
}
void MGCommandBase::remove_object_from_document(
	const MGGelPositions& gelps
){
	(new CGelRemoveAction(document(),gelps))->Do();
}

// gel_replace.
//the ownership of new_obj will be transfered to MGCommandBase.
//(Actually, undo manager will take care.)
bool MGCommandBase::replace_object(MGObject* old_obj, MGObject* new_obj){
	if(!old_obj){
		delete new_obj;
		return false;
	}
	MGGroup* grp=current_group();
	replace(MGGelPosition(grp,old_obj), MGGelPosition(grp,new_obj));
	return true;
}

//gel_replace.
//the ownership of object pointer in new_obj will be transfered to MGCommandBase.
//new_obj is assumed to belong to the same MGGroup of old_obj.
void MGCommandBase::replace(const MGPickObject& old_obj, MGObject* new_obj){
	MGGroup* grp=const_cast<MGGroup*>(old_obj.bottom_group());
	replace(old_obj, MGGelPosition(grp,new_obj));
}

//gel_replace.
//the ownership of object pointer in new_pos will be transfered to MGCommandBase.
//(Actually, undo manager will take care.)
void MGCommandBase::replace(const MGGelPositions& old_pos, const MGGelPositions& new_pos){
	fugenDoc& doc = *document();
	CGelReplaceAction* act = new CGelReplaceAction(&doc, old_pos, new_pos);
	act->Do();
}

void MGCommandBase::set_cursor_pos(const MGPosition& pos){
	ASSERT_VALID(document());
	document()->set_cursor(pos);
}

//Set this command's owner's(parent) next command.
//this and nextState are siblings.
void MGCommandBase::set_sibling_next_command(
	MGCommandBase* nextState
){
	ASSERT(m_owner_cmd);
	ASSERT(!m_owner_cmd->m_next);
	m_owner_cmd->m_next = nextState;
}

//Terminate the class.
//Overrided terminate_tool must invoke MGCommandBase::terminate_tool last.
//The function's return value is true, if the tool termination is accepted.
//False, if the termination is refused.
bool MGCommandBase::terminate_tool(bool cancel){
	//Erase OpenGL's rendering.
	if(cancel)
		erase_temporary();

	if(m_modelesDialog)
		m_modelesDialog->DestroyWindow();
	if(!get_owner_command()){
		//The following message is shown only when this is the top command of State.
		// コマンドを終了する旨を出力ウィンドウに表示する。
		const CString& strCmdName=command_name();
		CString strMsg;
		strMsg.Format(IDS_COMMAND_LEAVE, strCmdName);
		COUT << (TCAST)strMsg << std::endl;
	}
	return true;
}

///Exclude the objects that is not of types.
///if objects of types are remaind, function returns true.
bool MGCommandBase::resetCurrentObjects(
  	const MGAbstractGels& types
){
	MGPickObjects cobjs;
	current_object_is_valid(types,cobjs);
	set_current_object(cobjs);
	return cobjs.size()>=1;
}

//Invoked when command cancel action is done.
//do all the necessary processes for cancel.
//This MGCommandBase's OnCommandCanceled() will:
//(1) update all the standard views display without display.
//Actual display refresh will take place upon return to main loop
//(in fugenDoc's set_current_command_tool)
//(2) display cancel message.
bool MGCommandBase::OnCommandCanceled(UINT nIDS){
	if(nIDS==1)
		nIDS=3;
	return MGCommandBase::OnCommandEnd(nIDS, true);
}

//Invoked when command is to terminate as a nomal end.
//display normal end message.
bool MGCommandBase::OnCommandEnd(
	UINT nIDS,	//   =0: erase the current message, and display no messages.
				//   =1: display "xxxx" normally end.
				//   =2: display "xxxx" failed.
				///< =3: display "xxxx" canceled.
				//otherwise: nIDS is a string id, and load the message from string table to display.
	bool erase_temporary_display
				//true when draw_temporary() pictures are to erase.
				//false if draw_temporary() pictures are to stay displayed.
){
	switch(nIDS){
	case 0:
		SetStatusMessage(_T(""));
		break;
	case 1:
		// 終了しました
		SetStatusMessage(IDS_PROMPT_FINISH);
		break;
	case 2:
		// 失敗しました
		SetStatusMessage(IDS_PROMPT_ERROR_OPERATION);
		break;
	case 3:
		// Cancelしました
		SetStatusMessage(IDS_PROMPT_CANCEL);
		break;
	default:{
		// 文字列リソースとみなす
			CString str;
			VERIFY(str.LoadString(nIDS));
			SetStatusMessage(str);
		}
		break;
	}
	if(erase_temporary_display)
		erase_temporary();
	return true;
}

///Provides a standard undo/redo process.
void MGCommandBase::OnEditUndo(){
	document()->OnEditUndo();
}
void MGCommandBase::OnEditRedo(){
	document()->OnEditRedo();
}

///MGCommandBase's OnKeyDown does the processes of cancel(ESCAPE Key), end of the
///command process(RETURN Key).
///Function's return value is:
///true if on return from the function, the command tool should be removed
///from the current command stack of the doc, false if not.
bool MGCommandBase::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.
){
	MGCommandBase* owner=get_owner_command();
	if(!owner)
		owner=this;

	switch(nChar){
		case VK_ESCAPE: return owner->OnCommandCanceled(1);
		case VK_RETURN: return owner->OnCommandEnd(1);
		default:;
	}
	return false;
}

bool MGCommandBase::OnKeyUp(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nChar,	UINT nRepCnt, UINT nFlags
		//These parameters are of CWnd::OnKeyUp. See the document.
){
	return false;
}

bool MGCommandBase::OnLButtonDblClk(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnLButtonDblClk. See the document.
){
	return false;
}

bool MGCommandBase::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 false;
}


bool MGCommandBase::OnLButtonUp(
	fugenView* window,//The fugenView pointer where this event took place.
	CPoint old_point,	//The old point(point when LButtonDown).
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnLButtonUp. See the document.
){
	return false;
}


bool MGCommandBase::OnMouseMove(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnMouseMove. See the document.
){
	window->displaySet_position(point);
	return false;
}

bool MGCommandBase::OnMouseWheel(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags, short zDelta, CPoint pt
		//These parameters are of CWnd::OnMouseWheel. See the document.
){
	return false;
}

bool MGCommandBase::OnRButtonDblClk(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnRButtonDblClk. See the document.
){
	return false;
}

bool MGCommandBase::OnRButtonDown(
	fugenView* window,//The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnRButtonDown. See the document.
){
	return false;
}

bool MGCommandBase::OnRButtonUp(
	fugenView* window,//The fugenView pointer where this event took place.
	CPoint old_point,	//The old point(point when RButtonDown).
	UINT nFlags, CPoint point
		//These parameters are of CWnd::OnRButtonUp. See the document.
){
	return false;
}

bool MGCommandBase::OnSetCursor(
	fugenView* pView,
	CWnd* pWnd,
	UINT nHitTest,
	UINT message
){
	// デフォルトではカーソルを変更せず、後続の処理に任せる。
	return false;
}

void MGCommandBase::ModifyContextMenu(CMenu& popup){
	InsertMenu(IDR_MENU_CANCEL, popup);

	// del, undo, redo を抜いておく。
	popup.RemoveMenu(ID_EDIT_CLEAR, MF_BYCOMMAND);
	popup.RemoveMenu(ID_EDIT_UNDO, MF_BYCOMMAND);
	popup.RemoveMenu(ID_EDIT_REDO, MF_BYCOMMAND);
}

void MGCommandBase::InsertMenu(UINT nIDR, CMenu& popup){
	CMenu menu;
	VERIFY(menu.LoadMenu(nIDR));

	CMenu* pPopup1 = menu.GetSubMenu(0);
	ASSERT(pPopup1);

	// 先頭にメニューを連結しているだけ。

	const int nItem = pPopup1->GetMenuItemCount();
	for(int i = 0; i < nItem; ++i){
		CString strItem;
		if(pPopup1->GetMenuString(i, strItem, MF_BYPOSITION)){
			popup.InsertMenu(i, MF_BYPOSITION, pPopup1->GetMenuItemID(i), strItem);
		}
		else{
			// separator
			popup.InsertMenu(i, MF_BYPOSITION);
		}
	}
	TRACE(_T("%d  Menu0:%X \n"),nItem, popup.GetMenuItemID(0));
}

bool MGCommandBase::CanHandle(UINT nID) const{
	return nID == ID_COMMAND_CANCEL;
}

bool MGCommandBase::HandleMessage(UINT nID){
	switch(nID){
	case ID_COMMAND_CANCEL:
		{
			fugenView* pView = theApp.currentfugenView(document());
			if(pView){
				pView->PostMessage(WM_KEYDOWN, static_cast<WPARAM>(VK_ESCAPE));
			}
		}
		return true;
	default:
		return false;
	}
}

bool MGCommandBase::HandleUpdateUI(UINT nID, CCmdUI* pCmdUI){
	switch(nID){
	case ID_COMMAND_CANCEL:
		pCmdUI->Enable(TRUE);
		return true;
	}
	return false;
}
