/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
/**
 * @file fugenDoc1.cpp
 * @brief fugenDoc.h の実装。
 *
 * fugenDoc1.cpp contains common function programs,
 * does not include command programs.
 */

#include "stdafx.h"
#include "fugen.h"
#include "fugenDoc.h"
#include "MainFrm.h"
#include "fugenView.h"
#include "IO/GLFileManager.h"
#include "IO/GLFileImporter.h"
#include "Common/LocateInfo.h"
#include "Common/IdleCommand.h"
#include "Misc/UserPreference.h"
#include "Undo/UndoManager.h"
#include "Undo/GelAddAction.h"
#include "Common/LocateState.h"
#include "Common/CommandStateOwner.h"
#include "Common/CursorRestriction.h"

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

/////////////////////////////////////////////////////////////////////////////
// fugenDoc クラスの構築/消滅

fugenDoc::fugenDoc()
	 : m_main_view(0),
	   m_default_tool(new MGIdleCommandTool),
	   m_undoManager(new CUndoManager),
	   m_viewMode(MGCL::WIREVIEW)
{
	UserPreference& pref = UserPreference::getInstance();

	m_undoManager->RegisterTarget(this, pref.GetIntValue(upv_Undo_Size));
	m_default_tool->set_document(this);
	m_default_tool->lock_object(false);
	m_command_tool_stack.push(m_default_tool.get());
	m_current_group=&m_group;
}

fugenDoc::~fugenDoc(){
	// m_default_tool has been left.
	ASSERT(m_command_tool_stack.size() == 1);
}

/////////////////////////////////////////////////////////////////////////////
// fugenDoc クラスの診断

#ifdef _DEBUG
void fugenDoc::AssertValid() const{
	CDocument::AssertValid();
}

void fugenDoc::Dump(CDumpContext& dc) const{
	CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// fugenDoc コマンド

/// Return the viewing box for class fugenView.
const MGBox& fugenDoc::box()const{
	if(m_box.is_null()){
		m_box=m_group.box();
	}
	return m_box;
}

///Get the main view's context. When this doc does not hold the context, the context
///is generated from the box data.
MGContext* fugenDoc::context(){
	MGContext* ctx=m_group.context();
	if(!ctx){
		ctx=new MGContext(box());
		m_group.push_context(ctx);
		UserPreference& pref = UserPreference::getInstance();
		pref.exportToContext(*ctx);//initialize ctx.
	}
	return ctx;
}

const MGContext* fugenDoc::context()const{
	fugenDoc* dc=const_cast<fugenDoc*>(this);
	MGContext* ctx=dc->context();
	return ctx;
}

///Clear the undo/redo actions.
void fugenDoc::clearUndoRedo(){
	m_undoManager->DeleteActions();
}

void fugenDoc::DeleteContents(){
	terminate_current_command_tool();
	m_current_objects.clear();
	m_undoManager->DeleteActions();
	m_group.clear();
	setDefaultBox();
}

//Update all the selection hilighting of all the standard views.
void fugenDoc::UpdateSelectionViews(){
	m_main_view->redraw();
}

///Replace the input group data.
///When this is invoked,
///(1) current objects are cleared.
///(2) All of the data of the group docGroup are replaced with the data of newGroup.
///(3) The docGroup's current displays are erased and new data of newGroup will be showed.
void fugenDoc::replace(
	MGGroup& docGroup,	///A group of this document
	MGGroup&& newGroup
){
	set_current_object();//Clear current objects.
	docGroup = std::move(newGroup);
	InvalidateAllStdViews();
}

void fugenDoc::UpdateAllStdViews(const MGBox* box){
	POSITION pos = GetFirstViewPosition();
	while(pos){
		fugenView* pView = dynamic_cast<fugenView*>(GetNextView(pos));
		if(!pView) continue;

		if(pView->is_standard_view() && box){
			pView->ExecInitModel(*box);
		}
		// リフレッシュ
		pView->redrawOnlythis();
	}
}

//コマンド終了処理
void fugenDoc::clearCommand(){
	// Clean the command objects except for m_default_tool here.
	while(m_command_tool_stack.size()>1){
		//We do not pop if the terminate request is to pop up the default command tool.
		//(This default command tool was set by fugenDoc.)
		MGCommandBase* command=current_command_tool();
		command->terminate_tool(true);
		delete command;
		m_command_tool_stack.pop();
	}
}

//Add an object newobj to the document root group.
//newobj must be a newed object and the ownership is transfered
//to the document.
void fugenDoc::add_object_to_root(MGObject* newobj){
	if(!newobj) return;

	MGGelPosition newgel(&m_group,newobj);
	(new CGelAddAction(this, newgel))->Do();
}

//append the current objects(MGGelPositions).
void fugenDoc::append_current_object(const MGGelPositions& gelps, bool redraw){
	MGPickObjects cobjs=current_objects();
	cobjs.append_object(gelps);
	set_current_object(cobjs,redraw);
}

//Test if this doc's main view includes a system display list of
//the function code function_code.
bool fugenDoc::includes_sysgl(size_t function_code)const{
	return m_main_view->m_sysgllist.includes((int)function_code);
}

// Note that this function returns null position if there is no input
// position when it is called.
const MGPosition& fugenDoc::recent_input_position()const{
	return m_posRecent;
}

// The most recent input position is usually set by this class.
// However, the position may be modified in OnLocate() of derived class.
// This function is called in such case.
void fugenDoc::set_recent_input_position(const MGPosition& pos){
	m_posRecent=pos;
}

int fugenDoc::load(LPCTSTR file){
	CWaitCursor sandglass;
	CGLFileManager& factory = CGLFileManager::GetInstance();
	CGLFileImporter* handler = factory.GetImporter(file);
	if(!handler){
		// 呼び出し側でメッセージを表示
		return -1;
	}

	SetStatusMessage(IDS_PROMPT_NOW_LOADING);

	MGGroup data;
	if(!handler->Load(data) || data.empty()){
		// 呼び出し側でメッセージを表示
		return -1;
	}
	// 成功が確定したこの時点で初めてデータを完全に差し替える
	m_group = std::move(data);

	if(!m_group.empty())
		m_box=m_group.box();

	const MGContext* ctx=m_group.context();
	if(ctx){
		const MGglViewAttrib& vatr=ctx->theView();//MGglViewAttrib of 3D.
		ChangeViewMode(vatr.viewMode(),false);
	}
	SetStatusMessage(_T(""));
	return 0;
}

void fugenDoc::SetModifiedFlag(BOOL bModified){
	CDocument::SetModifiedFlag(bModified);

	if(m_strTitle.IsEmpty()) return;

	if(bModified){
		if(m_strTitle.GetAt(m_strTitle.GetLength() - 1) != '*'){
			m_strTitle += _T(" *");
			UpdateFrameCounts(); // will cause name change in views
		}
		m_box.set_null();
	}else{
		if(m_strTitle.GetAt(m_strTitle.GetLength() - 1) == '*'){
			m_strTitle.TrimRight(_T(" *"));
			UpdateFrameCounts();
		}
	}
}

//Save all the standard views' viewing context in context.
void fugenDoc::save_view_context(){
	ASSERT_VALID(this);
	MGContext* ct = context();

	POSITION pos = GetFirstViewPosition();
	while(pos){
		fugenView* pView = dynamic_cast<fugenView*>(GetNextView(pos));
		if(!pView) continue;
		pView->save_view_context(*ct);
	}
}

///Apply drawing attributes and colors of this context to all views.
void fugenDoc::apply_view_context_colors(){
	ASSERT_VALID(this);
	MGContext* ct = context();

	POSITION pos = GetFirstViewPosition();
	while(pos){
		fugenView* pView = dynamic_cast<fugenView*>(GetNextView(pos));
		if(!pView) continue;
		pView->apply_view_context_colors(*ct);
	}
	InvalidateAllStdViews();
}

//Set the current command tool.
//If ntool's can_break_into() return true, the current command tool will be pushed
//onto the doc's command stack, then ntool will be set current.
//If command pusing was done, the pushed command will resume the command currency
//on termination of ntool, 
//If can_break_into() return false, command manager of the doc will terminate
//the current command(invoke terminate_tool()) and not push to command stack),
//then ntool will be set current.
//The ntool's initiate_tool() will be invoked.
void fugenDoc::set_current_command_tool(MGCommandBase* ntool){
	ASSERT(ntool);
	int ncommand=(int)m_command_tool_stack.size();
	MGCommandBase* ctool=current_command_tool();
	if(ncommand>=2){
		if(ctool->command_id()==ntool->command_id()){
			//cancel new command.
			delete ntool;
			return;//When current and new command are the same, the new will not be invoked.
		}

		if(!ntool->can_break_into()){
			int yesno=AfxMessageBox(IDS_PROMPT_CANCEL_OK, MB_YESNO);
			CString ckind;
			if(yesno==IDYES){
				//cancel current command.
				cancel_command_tool(ctool);
			}else{
				//cancel new command.
				delete ntool;
				return;
			}
		}
	}

	fugenStatusBar& sbar = theStatusBar();
	sbar.SetDocument(this);
	push_command(ntool);
	if(ntool->initiate_tool()){
		terminate_current_command_tool();
		return;
	}
	m_main_view->redraw();
}

//Set the main view of this fugenDoc.
void fugenDoc::set_main_view(fugenView* mainView){
	m_main_view=mainView;
}

//Push and pop a command and the current_objects.
void fugenDoc::push_command(MGCommandBase* ntool){
	if(m_command_tool_stack.size()>=2)
		m_cobjects_stack.push(m_current_objects);
	m_command_tool_stack.push(ntool);
}
void fugenDoc::pop_command(){
	m_command_tool_stack.pop();
	if(m_command_tool_stack.size()>=2){
		//Restore saved command state.
		set_current_object(m_cobjects_stack.top());
		m_cobjects_stack.pop();
		MGCommandBase* savedCommand=current_command_tool();
		SetStatusMessage(savedCommand->message());
	}
}

//The function's return value is true, if the tool termination is accepted.
//False, if the termination is refused.
void fugenDoc::terminate_current_command(){
	MGCommandBase* command=current_command_tool();
	pop_command();
	if(m_command_tool_stack.size()==1){
		m_default_tool->set_last_command(command);
	}else{
		delete command;
	}
	InvalidateAllStdViews();
}

//False, if the termination is refused.
bool fugenDoc::terminate_current_command_tool(
){
	if(m_command_tool_stack.size()>1){
		//We do not pop if the terminate request is to pop up the default command tool.
		//(This default command tool was set by fugenDoc.)
		MGCommandBase* command=current_command_tool();
		if(!command->terminate_tool(false))
			return false;

		terminate_current_command();
	}
	return true;
}

//cancel the current command tool.
//cancel_command_tool invokes ctool's OnCommandEnd and
//terminate_tool(true) and delete ctool.
void fugenDoc::cancel_command_tool(
	MGCommandBase* ctool,	//command tool to cancel.
		//This ctool will be removed from the command stack.
	UINT nIDS
){
	if(m_command_tool_stack.size()<=1) return;
		//We do not pop if the terminate request is to pop up the default command tool.
		//(This default command tool was set by fugenDoc.)

	//指定したコマンドのcancel.
	MGCommandBase* command = current_command_tool();
	if(ctool!=command)	return;
	ctool->OnCommandCanceled(nIDS);
	ctool->terminate_tool(true);
	terminate_current_command();
}

///全てのコマンドを終了する
void fugenDoc::cancel_command_tool_all(){
	//stackに積まれているidleコマンド以外を全て終了する
	while(m_command_tool_stack.size() > 1)
		cancel_command_tool(m_command_tool_stack.top());
}

//Test if the command tool is current and breaking into the other command.
bool fugenDoc::is_breaking_command(const MGCommandBase* tool)const{
	const MGCommandBase* command=current_command_tool();
	if(tool!=command)
		return false;

	return m_command_tool_stack.size()>=3;
}

// Create a new frame window from pTemplate.
// pTemplate must be one of the document template members of class fugen.
// See the header file fugen.h.
void fugenDoc::InitialUpdateFrame(
	CDocTemplate* pTemplate,
	bool inherit_view_env
		//True if necessary to inherit the viewing environment from the current view.
){
	ASSERT_VALID(this);
    ASSERT_VALID(pTemplate);

	fugenMainFrame& mf = *theApp.MainFrame();
	CMDIChildWnd* pActiveChild = mf.MDIGetActive();
	ASSERT_VALID(pActiveChild);

	CFrameWnd* pFrame = pTemplate->CreateNewFrame(this, pActiveChild);
	if (pFrame == NULL) {
		AfxMessageBox(AFX_IDP_COMMAND_FAILURE);
		return; // Command failed
	}

	pTemplate->InitialUpdateFrame(pFrame, this);
	if(!inherit_view_env) return;
	fugenView* pOldView = dynamic_cast<fugenView*>(pActiveChild->GetActiveView());
	if(!pOldView) return;
	if(fugenView* pNewView = dynamic_cast<fugenView*>(pFrame->GetActiveView())){
		pNewView->copy_view_env(*pOldView);
	}
}

//Invalidate all the standard views.
void fugenDoc::InvalidateAllStdViews(){
	POSITION pos = GetFirstViewPosition();
	while(pos != NULL){
		fugenView* pSView = dynamic_cast<fugenView*>(GetNextView(pos));
		if(pSView && pSView->is_standard_view())
			pSView->Invalidate(FALSE);
	}
}

//terminate the command ctool as normal end.
//normalEnd_command_tool will invoke ctool's OnCommandEnd and
//terminate_tool(false). Then delete ctool.
void fugenDoc::normalEnd_command_tool(
	MGCommandBase* ctool,	//command tool to cancel.
		//This ctool will be removed from the command stack.
	UINT nIDS,	//=0: erase the current message, and display no messages.
				//=1: display "xxxx" normally end.
				//=2: display "xxxx" failed.
				//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.
){
	if(m_command_tool_stack.size()<=1) return;
		//We do not pop if the terminate request is to pop up the default command tool.
		//(This default command tool was set by fugenDoc.)

	//指定したコマンドのnomal end.
	MGCommandBase* command=current_command_tool();
	if(ctool!=command) return;
	ctool->OnCommandEnd(nIDS,erase_temporary_display);
	ctool->terminate_tool(false);
	terminate_current_command();
}

//Delete all the temporary display lists of all the views of this doc.
//Temporary means pictures not included in the document.
void fugenDoc::DeleteDisplayList(){
	fugenView* glwind=get_main_view();
	glwind->m_sysgllist.clear();
	InvalidateAllStdViews();
}

//Delete the display list of all the views of this doc, which was displayed
//by the command id(Function id of system display list of fugenView::push_back_to_sysgl).
void fugenDoc::DeleteDisplayList(size_t command_id, bool redraw){
	get_main_view()->DeleteDisplayList_by_function((int)command_id);
	if(redraw)
		InvalidateAllStdViews();
}

//Delete the display list of all the views of this doc, which was displayed
//by the command id(Function id of system display list of fugenView::push_back_to_sysgl) and
//the object id gel.
void fugenDoc::DeleteDisplayList(size_t command_id, const MGGel* gel, bool redraw){
	get_main_view()->DeleteDisplayList_by_function_object_code((int)command_id,gel);
	if(redraw)
		InvalidateAllStdViews();
}

//Lock or unlock all the windows update of this doc.
void fugenDoc::lockAllWindowsUpdate(){
	POSITION pos = GetFirstViewPosition();
	while(pos != NULL){
		fugenView* pSView = dynamic_cast<fugenView*>(GetNextView(pos));
		if(pSView) pSView->lockWindowUpdate();
	}
}
void fugenDoc::unlockAllWindowsUpdate(){
	POSITION pos = GetFirstViewPosition();
	while(pos != NULL){
		fugenView* pSView = dynamic_cast<fugenView*>(GetNextView(pos));
		if(pSView) pSView->unlockWindowUpdate();
	}
}

//Remove gelps from the current objects.
void fugenDoc::remove_current(const MGGelPositions& gelps){
	m_current_objects.remove(gelps);
}

//Set current objects of this doc's views.
//These functions do update views also.
void fugenDoc::set_current_object(bool redraw){
	m_current_objects.clear();
	if(redraw)
		UpdateSelectionViews();
};

//Set current objects of this document and update all views.
void fugenDoc::set_current_object(const MGGelPositions& gelpsIn,bool redraw){
	MGGelPositions gelps;
	for(size_t i=0; i<gelpsIn.size(); i++)
		if(gelpsIn[i].leaf_object())
			gelps.push_back(gelpsIn[i]);

	MGGelPositions to_remove(m_current_objects);
	to_remove-=gelps;

	MGGelPositions to_add(gelps);
	to_add-=m_current_objects;
	m_current_objects.remove(to_remove);
	m_current_objects.append_object(to_add);
	if(redraw) m_main_view->redraw();
}

///From this document's snap attrib,
///Update snap modes of the current tool and displays of the status bar.
void fugenDoc::updateSnapMode(){
	MGSnapAttrib& snapAtrib=getSnapAttr();//This doc's attrib.

	//(1) Export the attrib to the current command.
	MGCommandBase* tool=current_command_tool();
	tool->updateSnapMode(snapAtrib);

	//(2) Set up grid snap mode.
	enableGridSnap(snapAtrib.getGrid());
}

void fugenDoc::enableGridSnap(bool enable){

	//(1)Update the doc's context.
	MGSnapAttrib& sa = getSnapAttr();
	sa.setGrid(enable);

	//(2)Update UserPreference.
	UserPreference& pref = UserPreference::getInstance();
	pref.SetBoolValue(upv_ModelingOp_GridSnap_Enabled, enable);

	//(3)Update all the view's grid snap mode.
	POSITION pos = GetFirstViewPosition();
	while(pos != NULL){
		fugenView* pSView = dynamic_cast<fugenView*>(GetNextView(pos));
		if(pSView){
			pSView->enable_grid_snap(enable);
		}
	}
}

//Set the box data of this document.
void fugenDoc::setBox(const MGBox& box){
	m_box = box;
}

//Set the defalut box(origin+ length 10 for each axis).
void fugenDoc::setDefaultBox(){
	m_box = MGBox(MGPosition(0.,0.,0.),10.);
}

// 割り込みコマンド終了
void fugenDoc::cancel_breaking_command_tool()
{
	if(current_command_tool() && current_command_tool()->can_break_into()) {
		terminate_current_command_tool();
	}
}

const fugenView* fugenDoc::get_main_view()const{
	fugenDoc* doc=const_cast<fugenDoc*>(this);
	return doc->get_main_view();
}
