// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Heavily inspired by Inkscape::XML::CompositeNodeObserver.
 *
 * Authors:
 * David Yip <yipdw@rose-hulman.edu>
 *
 * Copyright (c) 2005 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <algorithm>
#include <functional>

#include "composite-undo-stack-observer.h"
#include "xml/event.h"

namespace Inkscape {

CompositeUndoStackObserver::CompositeUndoStackObserver() : _iterating(0) { }
CompositeUndoStackObserver::~CompositeUndoStackObserver() = default;

void
CompositeUndoStackObserver::add(UndoStackObserver& observer)
{
	if (!this->_iterating) {
		this->_active.push_back(UndoStackObserverRecord(observer));
	} else {
		this->_pending.push_back(UndoStackObserverRecord(observer));
	}
}

void
CompositeUndoStackObserver::remove(UndoStackObserver& observer)
{
	if (!this->_iterating) {
		// logical-or operator short-circuits
		this->_remove_one(this->_active, observer) || this->_remove_one(this->_pending, observer);
	} else {
		this->_mark_one(this->_active, observer) || this->_mark_one(this->_pending, observer);
	}
}

void
CompositeUndoStackObserver::notifyUndoEvent(Event* log)
{
	this->_lock();
	for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) {
		if (!i->to_remove) {
			i->issueUndo(log);
		}
	}
	this->_unlock();
}

void
CompositeUndoStackObserver::notifyRedoEvent(Event* log)
{

	this->_lock();
	for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) {
		if (!i->to_remove) {
			i->issueRedo(log);
		}
	}
	this->_unlock();
}

void
CompositeUndoStackObserver::notifyUndoCommitEvent(Event* log)
{
	this->_lock();
	for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) {
		if (!i->to_remove) {
			i->issueUndoCommit(log);
		}
	}
	this->_unlock();
}

void
CompositeUndoStackObserver::notifyClearUndoEvent()
{
	this->_lock();
	for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) {
		if (!i->to_remove) {
			i->issueClearUndo();
		}
	}
	this->_unlock();
}

void
CompositeUndoStackObserver::notifyClearRedoEvent()
{
	this->_lock();
	for(UndoObserverRecordList::iterator i = this->_active.begin(); i != _active.end(); ++i) {
		if (!i->to_remove) {
			i->issueClearRedo();
		}
	}
	this->_unlock();
}

bool
CompositeUndoStackObserver::_remove_one(UndoObserverRecordList& list, UndoStackObserver& o)
{
	UndoStackObserverRecord eq_comp(o);

	UndoObserverRecordList::iterator i = std::find_if(list.begin(), list.end(), std::bind(std::equal_to< UndoStackObserverRecord >(), std::placeholders::_1, eq_comp ));

	if (i != list.end()) {
		list.erase(i);
		return true;
	} else {
		return false;
	}
}

bool
CompositeUndoStackObserver::_mark_one(UndoObserverRecordList& list, UndoStackObserver& o)
{
	UndoStackObserverRecord eq_comp(o);

	UndoObserverRecordList::iterator i = std::find_if(list.begin(), list.end(), std::bind(std::equal_to< UndoStackObserverRecord >(), std::placeholders::_1, eq_comp));

	if (i != list.end()) {
		(*i).to_remove = true;
		return true;
	} else {
		return false;
	}
}

void
CompositeUndoStackObserver::_unlock()
{
	if (!--this->_iterating) {
		// Remove marked observers
		UndoObserverRecordList::iterator i = this->_active.begin();
		for(; i != this->_active.begin(); ) {
			if (i->to_remove) {
				i = this->_active.erase(i);
			}
			else{
				++i;
			}
		}

		i = this->_pending.begin();
		for(; i != this->_pending.begin(); ) {
			if (i->to_remove) {
				i = this->_active.erase(i);
			}
			else {
				++i;
			}
		}

		// Merge pending and active
		this->_active.insert(this->_active.end(), this->_pending.begin(), this->_pending.end());
		this->_pending.clear();
	}
}

}
