/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
/**
 * @file CommandBase.h
 * @brief クラス MGCommandBase の宣言。
 */
#ifndef _MGCommandBase_HH_
#define _MGCommandBase_HH_

#include "mg/MGCL.h"
#include "mg/GelPositions.h"
#include "mg/PickObjects.h"
#include "mg/AbstractGels.h"
#include "Common/LocateInfo.h"
#include "fugenDoc.h"

class fugenView;
class MGCommandStateOwner;
class MGSnapAttrib;
class mgModelessDialogue;

/// @class MGCommandBase CommandBase.h "Common/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.
class MGCommandBase{

public:

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

/// Construct an object of MGCommandBase with the target document
/// and the the owner.
MGCommandBase(
	fugenDoc*  pDoc=0,		///<document.
	UINT	command_id=0	///<command id
);

/// Construct an object of MGCommandBase from the owner.
MGCommandBase(
	MGCommandStateOwner* owner///<owner
);

public:

///Virtual Destructor
virtual ~MGCommandBase(){;};

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

///Append newobj to the document's current group and invoke CGelAddAction::add.
///The ownership of newobj is transfered to the document.
void add_object_to_current_group(MGObject* newobj);

///Append newobj to the document's current group and invoke CGelAddAction::add.
void add_object_to_current_group(std::unique_ptr<MGObject>&& newobj){
	add_object_to_current_group(newobj.release());
}

///Append gelp to the document and invoke CGelAddAction::add.
///The ownership of the gel in gelp is transfered to the document.
void add_object_to_document(const MGGelPosition& gelp);

///Append gelps to the document and invoke CGelAddAction::add.
///The ownership of all the gels in gelps are transfered to the document.
void add_object_to_document(const MGGelPositions& gelps);

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

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

///Append newobj in the range [first, last) to the document
///and invoke CGelAddAction::add.
///InputIterator must be MGGel*, and the ownership is transfered to the document.
template<typename InputIterator>
void addGelsToCurrentGroup(InputIterator first, InputIterator last){
	if (first != last) {
		MGGroup* parent = current_group();
		MGGelPositions gelps = make_gelpos(first, last, parent);
		add_object_to_document(gelps);
	}
}

///Append newobj in the range [first, last) to the document
///and invoke CGelAddAction::add.
///InputIterator must be UniqueGel*, and the ownership is transfered to the document.
template<typename InputIterator>
void addUniqueGelsToCurrentGroup(InputIterator first, InputIterator last){
	if(first != last) {
		MGGroup* parent = current_group();
		MGGelPositions gelps = make_gelposUniqueGel(first, last, parent);
		add_object_to_document(gelps);
	}
}

///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 attach_drawer(mgVBO* drawer, fugenView* window=0);

//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 detach_drawer(mgVBO* drawer, fugenView* window=0);

///ask if this tool can break_into other command tool.
/// @return If can_break_into() return true, the current command tool will be pushed
///onto the doc's command stack, then this command will be set current.
///On termination of this command, the pushed command will resume the command currency.
///If can_break_into() return false, command manager of the doc will terminate
///the current command(invoke terminate_tool() and not pushed to command stack),
///then this command will be set current.
///CommandTool who return true for this can_break_into() must hold all the necessary
///information for the tool in the class.
virtual bool can_break_into()const{return false;};

///cancel only the last locate.
void cancel_last_locate();

///Clear this command's owner's command state.
///This will cause the termination of owner command.
void clear_owner_command_state();

///clear input position data.
void clear_input_positions();

///clear the curennt object.
virtual void clear_pick_object();

/// Command ID for Windows.
virtual UINT command_id() const;

///Test if the current objects include the target types. When current objects included
///target types, current_object_is_valid will return true. The 2nd version will be extract
///objects into targets.
///The document's curent objects and views will not be updated.
///To update them, use set_current_object.
bool current_object_is_valid(
	const MGAbstractGels& types
)const;
bool current_object_is_valid(
	const MGAbstractGels& types,
	MGPickObjects& targets	///<Target object.
)const;

///Get the document's current group, which is the target group of fugenDoc that contains
///objects to add or update.
MGGroup* current_group();

///Set the current group for the document update.
void set_current_group(MGGroup* grp);

///Get the reference to the doc's current object.
const MGPickObjects& current_objects()const;
MGPickObjects& current_objects();

///Set the current position for the doc, i.e.
///1. set doc.cursor()=current.
///2. set_recent_input_position(current);
void set_current_position(
	const MGPosition& current
);

///Get the cursor position.
const MGPosition& cursor() const;

///Exclude objs from the curennt object.
virtual void exclude_pick_object(
	const MGPickObjects& objs	///<selected objects at this selection operation.
);

///Get input positions data array(avector of MGLocateInfo).
const LInfoVec& locates()const;
LInfoVec& locates();

///redraw all the standard views of the document.
void InvalidateAllStdViews();

/////////////////////////////////////////////////////////////////////////////////
//*********The following do_make_temporary_display()
//is the metod for draw_temporary() and erase_temporary().
//Their default actions are defined here.

///Draw temporary pictures, making mgSysGL for do_make_temporary_display() of
///this by this command id.
///This draw_temporary invokes do_make_temporary_display() to draw. To use this
///draw_temporary(), subclass must define its own do_make_temporary_display().
void draw_temporary(
	bool clear_so_far=true,///<true if all of the pictures drawn so far for this 
					///<command id is to clear.
	bool redraw=true///<true if redraw action is necessary.
);

///Draw temporary pictures, given mgSysGL, does not invoke do_make_temporary_display().
///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 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,///<true if all of the pictures drawn so far for this 
					///<command id is to clear.
	bool redraw=true///<true if redraw action is necessary.
);

/// Compiles OpenGL's display list of temporary objects for each standard views.
virtual void do_make_temporary_display(mgSysGL& sgl,fugenView* pSView){;};

///Output prompting message,
///specifically called by MGLocate::OnLocated, OnKeyDown.
virtual void prompt_message()const{;};

///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.
virtual void erase_temporary(const MGGel* gel=0, bool redraw=false);

///////////////End of the methods declarations of draw_temporary///////////////
/////////////////////////////////////////////////////////////////////////////////

///Get the document's MGGroup pointer.
MGGroup* doc_root();

///Return the target document.
fugenDoc* document(){return m_document;};
const fugenDoc* document() const{ return m_document;}

///Return the view where an event took place.
fugenView* eventView();

/// 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 gelpos(const MGObject* obj_in);
MGGelPosition gelpos(MGPickObject& po);

/// Return the GelPosition of every object in the range [first, last)
/// which is currently in the document.
/// If there exists an obj that is not in the document, then it will
/// be ignored.
///*InputIterator must be MGObject*, or MGObject&.
template<typename InputIterator>
MGGelPositions gelpos(InputIterator first, InputIterator last){
	MGGelPositions gelps;
	for(; first != last; ++first){
		gelps.push_back(gelpos(*first));
	}
	return gelps;
}

/// Make GelPosition from obj which will be into the document.
/// obj must be a newed one, and the ownership is transfered to MGGelPosition.
MGGelPosition make_gelpos(MGObject* obj); // parent == document.m_group
MGGelPosition make_gelpos(
	MGObject* obj,
	MGGroup* parent	///<parent must be the document's root group or a member
					///<of the document's root group.
);

/// Make GelPositions from every object in the range [first, last)
/// which will be into the document.
///InputIterator must be MGGel*, and the ownership is transfered to the document
///after GelPositions is transfered to Undo Manager.
template<typename InputIterator>
MGGelPositions make_gelpos(
	InputIterator first,
	InputIterator last,
	MGGroup* parent	///<parent must be the document's root group or a member
					///<of the document's root group.
){
	MGGelPositions gelps;
	gelps.reserve((int)std::distance(first, last));
	for(; first != last; ++first){
		MGObject* obji=dynamic_cast<MGObject*>(*first);
		if(obji)
			gelps.push_back(MGGelPosition(parent,obji));
	}
	return gelps;
}
/// Make GelPositions from every object in the range [first, last)
/// which will be into the document.
///InputIterator must be UniqueGel*, and the ownership is transfered to the document
///after GelPositions is transfered to Undo Manager.
template<typename InputIterator>
MGGelPositions make_gelposUniqueGel(
	InputIterator first,
	InputIterator last,
	MGGroup* parent	///<parent must be the document's root group or a member
					///<of the document's root group.
){
	MGGelPositions gelps;
	gelps.reserve((int)std::distance(first, last));
	for(; first != last; ++first){
		MGObject* obji = dynamic_cast<MGObject*>(first->get());
		if(obji){
			first->release();
			gelps.push_back(MGGelPosition(parent, obji));
		}
	}
	return gelps;
}

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

///Initiate the class.
///Overrided initiate must invoke MGCommandBase::initiate_tool first.
///Function's return value is true:if this command should be terminated on return,
///false if not.
///MGCommandBase::initiate_tool() always return false.
virtual bool initiate_tool();

///Test if the command tool is current and breaking into the other command.
bool is_breaking_command()const;

/// Return true if and only if the command protects all of the object
/// in the document against removal operation.
bool is_locked() const{ return m_locked;}
void lock_object(bool lock = true){ m_locked = lock;}

/// redraw all the standard view.
void redraw();

///remove the objects specified in gelp, or gelps from the document.
void remove_object_from_document(
	const MGGelPosition& gelp
);
void remove_object_from_document(
	const MGGelPositions& gelps
);

///gel_replace.
///the ownership of object pointer in new_pos will be transfered to MGCommandBase,
///even if function failed(Actually, undo manager will take care.)
///Function's return value is true if seccessfully replaced,
///false if replace action failed.
///Both old_obj and new_obj are assumed to belong to MGGroup of the document.
bool replace_object(MGObject* old_obj, MGObject* new_obj);

///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 replace(const MGPickObject& old_obj, MGObject* new_obj);
void replace(const MGGelPositions& old_pos,const MGGelPositions& new_pos);

///Select objects of specified type from the document's current objects,
///and reset the current objects with them.
virtual void reset_current_objects(const MGAbstractGels& type);

///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 select_from_current_objects(const MGAbstractGels& type);

/// Set the command id, which is correspond to Windows' message,
/// that is menu id.
void set_command_id(UINT nID){ m_nCmdID = nID;}

/// Append current (active) objects in the document.
void append_current_object(const MGPickObjects& pobjs, bool redraw=false){
	document()->append_current_object(pobjs,redraw);
};
void append_current_object(const MGPickObject& pobj, bool redraw=false){
	document()->append_current_object(pobj,redraw);
};

/// Set current (active) objects in the document.
//@{
void set_current_object(bool redraw=false){
	document()->set_current_object(redraw);
};
void set_current_object(const MGPickObjects& pobjs,bool redraw=false){
	document()->set_current_object(pobjs,redraw);
};
void set_current_object(const MGPickObject& pobj,bool redraw=false){
	document()->set_current_object(pobj,redraw);
};
//@}

///set the cursor position.
void set_cursor_pos(const MGPosition& pos);

///Set document.
void set_document(fugenDoc* doc){m_document=doc;};

///Set active view as view.
void setActiveView(fugenView* view);

///Register view.
void setEventView(fugenView* view){m_eventView=view;};

///Set this command's owner's(parent) next command.
///this and nextState are siblings.
void set_sibling_next_command(
	MGCommandBase* 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.
virtual bool terminate_tool(bool cancel=false);

///Exclude the objects that is not of types.
///if objects of types are remaind, function returns true.
bool resetCurrentObjects(
  	const MGAbstractGels& types
);

/// 派生クラスがMGCommandStateOwnerの状態クラスであるとき
/// その親コマンドを取得/設定する関数
/// When get_owner_command()==null, this is the owner and has no owner.
MGCommandStateOwner* get_owner_command(){ return m_owner_cmd;}
const MGCommandStateOwner* get_owner_command() const{ return m_owner_cmd;}

void set_owner_command(MGCommandStateOwner* cmd){ m_owner_cmd = cmd;}

///Get the document's main view.
fugenView* get_main_view();
MGOpenGLView& get_main_glview();

///update snap mode for locate.
virtual void updateSnapMode(const MGSnapAttrib& sattrib){;};

virtual void ModifyContextMenu(CMenu& popup);
virtual bool CanHandle(UINT nID) const;
virtual bool HandleMessage(UINT nID);
virtual bool HandleUpdateUI(UINT nID, CCmdUI* pCmdUI);
void InsertMenu(UINT nIDR, CMenu& popup);
CString& message()const;
const CString& command_name()const;

//
//*********** Window's event handling fucntions*****************
//All of the event handlings of fugenView are defined.

///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.
virtual bool OnCommandCanceled(
	UINT nIDS	///<=0: erase the current message, and display no messages.
				///<=1: display "xxxx" cancelled.
				///<otherwise: nIDS is a string id, and load the message from string table to display.
);

///Invoked when command is to terminate as a nomal end.
///display normal end message.
virtual bool 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
				///<true when draw_temporary() pictures are to erase.
				///<false if draw_temporary() pictures are to stay displayed.
);

///Provides a standard undo process.
virtual void OnEditUndo();

///Provides a standard redo process.
virtual void 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.
virtual bool 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.
);

///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.
virtual bool 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.
);

///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.
virtual bool OnLButtonDblClk(
	fugenView* window,///<The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		///<These parameters are of CWnd::OnLButtonDblClk. See the document.
);

///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.
virtual bool OnLButtonDown(
	fugenView* window,///<The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		///<These parameters are of CWnd::OnLButtonDown. See the document.
);

///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.
virtual bool 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.
);

///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.
virtual bool OnMouseMove(
	fugenView* window,///<The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		///<These parameters are of CWnd::OnMouseMove. See the document.
);

///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.
virtual bool 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.
);

///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.
virtual bool OnRButtonDblClk(
	fugenView* window,///<The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		///<These parameters are of CWnd::OnRButtonDblClk. See the document.
);

///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.
virtual bool OnRButtonDown(
	fugenView* window,///<The fugenView pointer where this event took place.
	UINT nFlags, CPoint point
		///<These parameters are of CWnd::OnRButtonDown. See the document.
);

///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.
virtual bool 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.
);

/// See MSDN CWnd::OnSetCursor.
/// @param[in] pView The fugenView pointer where this event took place.
/// @param[in] pWnd Specifies a pointer to the window that contains the cursor (see MSDN).
/// @param[in] nHitTest Specifies the hit-test area code (see MSDN).
/// @param[in] message Specifies the mouse message number (see MSDN).
/// @return true to halt further processing, or false to continue.
///
/// See MSDN CWnd::OnSetCursor.
virtual bool OnSetCursor(
	fugenView* pView,
	CWnd* pWnd,
	UINT nHitTest,
	UINT message);

///@name 個別のキーイベントの取得
///ウィンドウからフォーカスが外れていてもキーイベントがほしい場合、
///そのキーをアクセラレータキーにして個別に実装する。\n
///元からアクセラレータキーの場合も同様にする。
//@{
/// Deleteキーのイベントを取得する場合trueを返す
virtual bool canDeleteKey(){return false;};
/// Deleteキーに対する処理
///@return コマンドを終了させるならtrueを返す
virtual bool ProcessDeleteKey(){return false;};

/// F8キーのイベントを取得する場合trueを返す
virtual bool canF8Key(){return false;};
/// F8キーに対する処理
///@return コマンドを終了させるならtrueを返す
virtual bool ProcessF8Key(){return false;};
//@}

///Modeless dialogue support functions.
///The newed dialog is deleted(destroyed) in terminateTool.
template <class DIALOG, class... Args>
DIALOG* attachModelessDialogue(Args&&... args){
	DIALOG* dialog = new DIALOG(std::forward<Args>(args)...);
	setModelessDialogPointer(dialog);
	return dialog;
}
mgModelessDialogue* getModelessDialogPointer();
const mgModelessDialogue* getModelessDialogPointer()const;
void setModelessDialogPointer(mgModelessDialogue* dialog);

protected:

// StatusMessage出力関数
// Write message in the leftmost pane of status bar.
void SetStatusMessage(LPCTSTR lpszMsg)const;
void SetStatusMessage(UINT nIDS, ...)const;///<When nIDS=0, does not put messages.

//Clear the StatusMessage(including command name).
void ClearCommandMessage();


///Get the command name from the menu string.
virtual void commandNameFromMenu(CString& name);

private:
	
	fugenDoc*        m_document;//The document who uses the command.
	fugenView*		 m_eventView;//The view where a event took place(On...).
	mgModelessDialogue* m_modelesDialog;//When this command uses mgModelessDialogue class
	                 //as to input data from modeless dialog, the newed mgModelessDialogue
	                 //is stored.

	// コマンドに対応するID
	UINT    m_nCmdID;  // e.g., ID_CURVE_CIRCLE_3POINTS
	CString m_command_name;//This command name.
	mutable CString m_message;//Prompt message to displya is saved.

	// MGCommandの派生クラスが
	// 状態クラスを表現する場合に「親」のコマンドクラスを知りたいことがある
	// それを指すメンバが m_owner_cmd である
	// m_owner_cmd はMGCommandBaseの派生クラスが
	// こうした状態クラスであるときに有効なメンバである
	MGCommandStateOwner* m_owner_cmd;

	// m_lockedはオブジェクトの選択に関するフラグである
	// 主にundo/redoの禁止 マウスの使用によらないオブジェクト選択の禁止を
	// スイッチする
	bool           m_locked : 1;

	//m_locates is for MGLocateState.
	//MGLocateState will initialize them. OpenGLView will draw the polyline
	//if positions data are set in m_input_positions.
	LInfoVec m_locates;

	void SetStatusMessageSub(LPCTSTR lpszMsg)const;
};

#define DECIMAL_PLACE_NUMBER 1000
#endif
