/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/* ***************************************************** */
/********************************************************************/
// UndoManager.cpp: implementation of the CUndoManager class.

#include "stdafx.h"
#include "Undo/UndoManager.h"
#include "Undo/IAction.h"
#include "Undo/IActionTarget.h"

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

// CUndoManager

CUndoManager::CUndoManager(
):m_actionTarget(0), m_cleanMarker(0), m_capacity(UM_DEFAULT_STACK_CAPACITY){
}

CUndoManager::CUndoManager(
	IActionTarget* actionTarget,
	int capacity
):m_actionTarget(actionTarget), m_cleanMarker(0), m_capacity(capacity){
}

CUndoManager::~CUndoManager(){
	DeleteActions();
}

void CUndoManager::DeleteActions(ActionStack& aStack, int nActions){
	// delete from the bottom up; because
	// we're using a reverse storage strategy,
	// we walk from front to back
	for(int i = 0; i < nActions; ++i){
		IAction *anAction = aStack.front();
		aStack.pop_front();
		delete anAction;
	}
}

void CUndoManager::DeleteActions(){
	DeleteActions(m_undoStack, (int)m_undoStack.size());
	DeleteActions(m_redoStack, (int)m_redoStack.size());
}

IActionTarget* CUndoManager::GetTarget() const
{
	return m_actionTarget;
}

void CUndoManager::RegisterTarget(
	IActionTarget* actionTarget,
	int capacity
){
	m_actionTarget = actionTarget;
	SetStackCapacity(capacity);
}

void CUndoManager::SetLastAction(IAction *itsLastAction){
	if(!itsLastAction)
		return;

	// a new undoable action cancels any previously redoable ones 
	DeleteActions(m_redoStack, (int)m_redoStack.size());

	// remove bottom action if stack is full
	if(int(m_undoStack.size()) >= m_capacity){
		IAction *firstAction = m_undoStack.front();

		// we are removing an action which modified the
		// command target (e.g., a document); this effects
		// our ability to undo back into an unmodified state
		m_cleanMarker = -1;

		m_undoStack.pop_front();
		delete firstAction;
	}

	// we use the standard reverse vector approach for our stacks,
	// even though we can efficiently push front with a deque; this
	// way however, indexes in the deque remain constant; that makes
	// building menus and tracking the clean marker much easier
	push_back_to_undo(itsLastAction);
}

int CUndoManager::GetStackSize(StackKind stackKind)const{
	const ActionStack& theStack = (stackKind == UNDO) ? m_undoStack : m_redoStack;
	return (int)theStack.size();
}

void CUndoManager::SetStackCapacity(int nActions){
	assert(nActions > 0);

	// the new size may be smaller than the previous stack
	// size, so we may have to delete some initial actions
	if(int(m_undoStack.size()) > nActions){
		size_t overflow=m_undoStack.size() - nActions;
		DeleteActions(m_undoStack,(int)overflow);
		DeleteActions(m_redoStack,(int)overflow);
	}

	m_capacity = nActions;

	// and the new size may also be greater than our previous
	// clean state; if so, we need to mark the document so that
	// it cannot be cleaned
	if (m_cleanMarker >= m_capacity) m_cleanMarker = -1;
}

bool CUndoManager::CanUndo(int nActions)const{
	return nActions <= int(m_undoStack.size());
}

void CUndoManager::Undo(int nActions){
	if(!CanUndo(nActions))
		return;

	for(int i = 0; i < nActions; ++i){
		IAction *lastAction = m_undoStack.back();
		m_undoStack.pop_back();
		lastAction->Undo();
		if(m_undoStack.empty()){
			GetTarget()->SetDirtyFlag(false);
		}

		// redo size is limited by the undo stack and its
		// limitations are enforced by SetLastAction()
		push_back_to_redo(lastAction);
	}
}

bool CUndoManager::CanRedo(int nActions) const{
	return nActions <= int(m_redoStack.size());
}

//Push_back the action to redo or undo stack.
void CUndoManager::push_back_to_redo(IAction* action){
	action->set_as_redo_stack();
	m_redoStack.push_back(action);
}
void CUndoManager::push_back_to_undo(IAction* action){
	action->set_as_undo_stack();
	m_undoStack.push_back(action);
}

void CUndoManager::Redo(int nActions){
	if(!CanRedo(nActions))
		return;

	for(int i = 0; i < nActions; ++i){
		IAction *lastAction = m_redoStack.back();

		m_redoStack.pop_back();
		lastAction->SetAutoNotify(false);
		lastAction->Redo();

		if(m_undoStack.empty()){
			GetTarget()->SetDirtyFlag();
		}

		// undo size is limited by the redo stack, which is
		// limited in turn by the original undo stack, which
		// is limited by SetLastAction() (whew!)
		push_back_to_undo(lastAction);
	}
}

void CUndoManager::TargetCleaned(){
	// set the marker to the size of the stack
	// (we must be at that point for the target to be clean)
	m_cleanMarker = (int)m_undoStack.size();
}
