// This file is part of the Coral project.
// Please read the copyright notice in the LICENSE file.

#include <coral/core/util/TransactionManager.h>
#include <coral/lib/struct_helpers.h>
#include <coral/lib/timedebug.h>
#include <coral/lib/restore_value.h>
#include <map>

#include <Python.h>

#include <sys/time.h>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <sstream>

#include <coral/lib/debugstream.h>

#include <functional>

NAMESPACE_BEGIN(coral);
NAMESPACE_BEGIN(Transaction);

namespace {
	int pause_recursion = 0;
	const int HISTORY_LIMIT = 20;
}

Manager* theTM = coral::Transaction::getTransactionManager();

Manager* getTransactionManager() {
	if (!theTM) {
		theTM = new Manager();
	}
	return theTM;
}

Transaction::Transaction()
	: ref_counted(), List(), m_commands() {
	;
}

Transaction::~Transaction() throw () {
	dlog << DEBUGLEVEL_DEBUG << "Delete transaction " << this << std::endl;
}

size_t Transaction::size() const throw () {
	return m_commands.size();
}

Command* Transaction::operator[](int pos) {
	if (pos < 0 || static_cast<size_t>(pos) >= m_commands.size()) {
		throw std::out_of_range("Transaction.operator[] outside range!");
	}
	commands_type::iterator i = m_commands.begin();
	std::advance(i, pos);
	return *i;
}

std::string Transaction::__str__() const {
	std::string result;
	char buf[1024];
	snprintf(buf, 1024, "Items: %d\n",
		 m_commands.size());
	result += buf;

	for (commands_type::const_iterator i
		     = m_commands.begin();
	     i != m_commands.end();
	     ++i) {
		result += (*i)->__str__();
	}
	return result;
}


Manager::Manager() {
	m_active = false;
	m_inModifyCmd = 0;
	m_currentCmd =
		static_cast<Transaction*>(&m_transactions);
	m_n_transactions = 0;
	m_history_limit = HISTORY_LIMIT;
	m_current_tr_in_progress = TRANSACTION_ORDINARY;
}

Manager::~Manager() throw () {
	/* Now trimFuture will remove all transactions. */
	m_currentCmd = static_cast<Transaction*>(&m_transactions);
	trimFuture();
	theTM = NULL;
}
		
bool Manager::internalIsActive() throw () {
	return (m_active) && (pause_recursion == 0);
}

bool Manager::isCreatingTransaction() throw () {
	return m_active;
}

bool Manager::isUndoOrRedo() throw () {
	return m_current_tr_in_progress
		!= TRANSACTION_ORDINARY;
}

void Manager::beginModifyCmd(bool mystrict) throw () {
	if (m_inModifyCmd == 0) {
		coral_assert(m_current_tr_in_progress == TRANSACTION_ORDINARY);
		trimFuture();
		Transaction* t;
		MUST_WORK(t = new Transaction());
		MUST_WORK(m_transactions.add_back(t));
		m_active = true;
		m_currentCmd = t;
		m_strict = mystrict;
		m_watch.clear();
		DEBUG_MAY_FAIL(dlog << DEBUGLEVEL_INFO << "INFO ********** Begin transaction " << t << std::endl);
		callBusyObservers(true);
	}
	++m_inModifyCmd;
}

void Manager::partialModifyCmd(Command* c) throw () {
	/* We must be in a transaction */
	coral_assert(!m_transactions.isEmpty());
	coral_assert(m_active == true);
	coral_assert(m_current_tr_in_progress == TRANSACTION_ORDINARY);
	MUST_WORK(m_currentCmd->m_commands.push_back(c));
	if (ElementChangeSlotCommand* cc = dynamic_cast<ElementChangeSlotCommand*>(c)) {
		Slot* s = cc->getSlot();
		coral_assert(!already_modified(s->getElement(), s->getProperty()));
		m_watch.insert(std::make_pair(s->getElement(), s->getProperty()));
	}
}

void Manager::__forget_previous_Cmd() throw () {
	coral_assert(m_active);
	coral_assert(m_currentCmd != &m_transactions);
	coral_assert(m_inModifyCmd != 0);
	coral_assert(!m_currentCmd->m_commands.empty());
	m_currentCmd->m_commands.back()->unref();
	m_currentCmd->m_commands.pop_back();
}

bool Manager::canUndo() throw () {
	if (m_current_tr_in_progress == TRANSACTION_UNDO) {
		/*
		 * Special-case when undoing and calling observers.
		 * We must return the value
		 * which will be true _after_ the undo is finished.
		 */
		return (m_currentCmd->getPrev() != &m_transactions);
	}
	return m_currentCmd != &m_transactions;
}

bool Manager::canRedo() throw () {
	if (m_current_tr_in_progress == TRANSACTION_UNDO) {
		/*
		 * Special-case when undoing and calling observers.
		 * We must return the value
		 * which will be true _after_ the undo is finished.
		 */
		return true;
	}
	return m_currentCmd->getNext() != &m_transactions;
}

bool Manager::endModifyCmd() throw (std::exception) {
	--m_inModifyCmd;
	if (m_inModifyCmd) return false;

	/*
	 * Don't do anything for empty transactions.
	 */
	Transaction* o = m_currentCmd;
	DEBUG_MAY_FAIL(dlog << DEBUGLEVEL_INFO
		       << "INFO ********** End transaction "
		       << o << std::endl);
	if (o->size() == 0) {
		m_currentCmd = static_cast<Transaction*>(o->getPrev());
		o->drop();
		o->unref();
		m_active = false;
		callBusyObservers(false);
		return m_strict;
	}


	++m_inModifyCmd;
	try {
		restore_value<int>
			r_inmodifycmd(m_inModifyCmd, m_inModifyCmd-1);
		restore_value<bool>
			r_active(m_active, false);
		restore_value<int>
			r_n_transactions(m_n_transactions, m_n_transactions+1);
		callObservers(TRANSACTION_ORDINARY);
	} catch (...) {
		trimPast_w_history(m_history_limit);
		callBusyObservers(false);
		throw;
	}
	trimPast_w_history(m_history_limit);
	callBusyObservers(false);
	return m_strict;
}

void Manager::beginTransaction(bool strict) throw () {
	beginModifyCmd(strict);
}
bool Manager::endTransaction() throw () {
	return endModifyCmd();
}

void Manager::addObserver(PyObject* p) {
	coral_assert(p);
	m_observers.push_back(p);
	Py_INCREF(p);
}

void Manager::removeObserver(PyObject* p) {
	coral_assert(p);
	PyObject* func = NULL;
	PyObject* self = NULL;
	if (PyMethod_Check(p)) {
		func = PyMethod_GET_FUNCTION(p);
		self = PyMethod_GET_SELF(p);
	}

	for (observers_type::iterator i = m_observers.begin();
	     i != m_observers.end();
	     ++i) {
		if (*i != p) {
			if (PyMethod_Check(*i)) {
				PyObject* fi = PyMethod_GET_FUNCTION(*i);
				PyObject* si = PyMethod_GET_SELF(*i);
				if (fi != func || si != self) continue;
			}
		}
		m_observers.erase(i);
		Py_DECREF(p);
		return;
	}

	dlog << DEBUGLEVEL_WARNING
	     << "WARNING Couldn't remove non-existent observer "
	     << p
	     << std::endl;
}

void Manager::addBusyObserver(PyObject* p) {
	coral_assert(p);
	m_busyObservers.push_back(p);
	Py_INCREF(p);
}

void Manager::removeBusyObserver(PyObject* p) {
	coral_assert(p);
	busyObservers_type::iterator i =
		std::find(m_busyObservers.begin(),
			  m_busyObservers.end(),
			  p);
	if (i != m_busyObservers.end()) {
		m_busyObservers.erase(i);
		Py_DECREF(p);
	}
}

void Manager::orphanManager() {
	if (m_current_tr_in_progress != TRANSACTION_ORDINARY) {
		dlog << DEBUGLEVEL_DEBUG << "DEBUG Skipping orphanManager (we're in undo/redo)" << std::endl;
		return;
	}
	dlog << DEBUGLEVEL_DEBUG << "DEBUG Running orphanManager" << std::endl;
	typedef std::set<Element*, std::less<Element*> > updated_t;
	updated_t updated;
	Transaction* o = m_currentCmd;
	typedef std::set<Element*> detach_t;
	detach_t detach_roots;
	for (Transaction::commands_type::iterator i
		     = o->m_commands.begin();
	     i != o->m_commands.end();
	     ++i) {
		Command* c = *i;
		Element* e = NULL;
		if (ElementChangeSlotCommand* s
		    = dynamic_cast<ElementChangeSlotCommand*>(c)) {
			e = s->getSlot()->getElement();
		} else if (ProjectChangeCommand* ps
			   = dynamic_cast<ProjectChangeCommand*>(c)) {
			e = dynamic_cast<Element*>(ps->getAtom());
			if (!e || e->__getRootsProject__()) {
				continue;
			}
		} else {
			continue;
		}
		coral_assert(e);
		std::pair<updated_t::iterator, bool>
			p = updated.insert(e);
		if (!p.second) continue;
		// dlog << DEBUGLEVEL_DEBUG << "DEBUG orphanManager: checking " << e << " in " << e->__getRootsProject__() << std::endl;
		Element* root = e->__getRoot__();
		coral_assert(root);
		if (root->__type__()->isRootDefinition()) continue;
		Project* st = root->__getProject__();
		if (st) continue;
		// dlog << DEBUGLEVEL_DEBUG << "DEBUG orphanManager: I am going to kill " << root << " soon." << std::endl;
		detach_roots.insert(root);
	}

	detach_t detach;

	dlog << DEBUGLEVEL_DEBUG
	     << "DEBUG Going to add concerned citizens..."
	     << std::endl;
	/*
	 * Add all concerned citizens
	 */
	for (detach_t::iterator i = detach_roots.begin();
	     i != detach_roots.end();
	     ++i) {
		Element* e = *i;

		coral::autounref<coral::Set>
			parts(e->__getAllParts__());
		for (Set::iterator c = parts->begin();
		     c != parts->end();
		     ++c) {
			detach.insert(const_cast<Element*>(static_cast<const Element*>(*c)));
		}
		coral_assert(detach.find(e) != detach.end());
	}
	/*
	 * Finally, for each element, call special deletion
	 * NOTE! The elements killed are in a random order.
	 * So algorithms that work better when the elements in
	 * transaction commands occur top-down might be slow.
	 * This is the case e.g. when deleting QCanvasItems in
	 * coral/modeler/diagram/diagrammodeler.py#DiagramElementHandler
	 */
	dlog << DEBUGLEVEL_DEBUG << "DEBUG Killing..." << std::endl;
	for (detach_t::iterator i = detach.begin();
	     i != detach.end();
	     ++i) {
		Element* e = *i;
		dlog << DEBUGLEVEL_DEBUG << "DEBUG Detaching " << e->__str__() << std::endl;
		e->__detachUsFromEverybodyElse__(detach);
	}

	dlog << DEBUGLEVEL_DEBUG << "DEBUG Ending orphanManager" << std::endl;
}

void Manager::callBusyObservers(bool isBusy) {
	if (m_busyObservers.empty())
		return;

	PyObject* args = PyTuple_New(1);
	if (!args) return;
	PyObject* dir = PyInt_FromLong(isBusy);
	if (!dir) {
		Py_DECREF(args);
		return;
	}
	PyTuple_SET_ITEM(args, 0, dir);

	for (busyObservers_type::const_iterator i = m_busyObservers.begin();
	     i != m_busyObservers.end();
	     ++i) {
		dlog << DEBUGLEVEL_DEBUG
		     << "DEBUG Calling busyobserver "
		     << *i
		     << "."
		     << std::endl;
		PyObject* ret =
			PyObject_Call(*i, args, NULL);
		Py_XDECREF(ret);
		if (PyErr_Occurred()) {
			dlog << DEBUGLEVEL_WARNING <<
				"WARNING OBSERVER RAISED AN EXCEPTION (ignoring)" << std::endl;
			PyErr_Print();
			dlog << DEBUGLEVEL_WARNING << "***" << std::endl;
		}
	}
	Py_DECREF(args);
}


void Manager::callObservers(transaction_type forwards) {
	dlog << DEBUGLEVEL_DEBUG << "DEBUG in TransactionManager.callObservers" << std::endl;
	m_amount_of_items_in_current_trans
		= m_currentCmd->size();

	m_current_tr_in_progress = forwards;

	/*
	 * This is for debugging purposes.
	 * If any observer changes the model
	 * when we are not supposed to,
	 * this will catch it (in partialModifyCmd())
	 *
	 */
	bool old_active_value = m_active;
	if (forwards != TRANSACTION_ORDINARY) {
		m_active = true;
	} else {
		coral_assert(m_active == true);
	}


	restore_value<bool> fix_active(m_active, old_active_value);
	restore_value<transaction_type> fix_tr(m_current_tr_in_progress,
					       TRANSACTION_ORDINARY);

	orphanManager();


	/*
	 * If Python isn't initialized,
	 * we can't call any Pyxxx functions
	 */
	if (m_observers.empty())
		return;
	PyObject* args = PyTuple_New(1);
	if (!args) return;
	PyObject* dir = PyInt_FromLong(forwards);
	if (!dir) {
		Py_DECREF(args);
		return;
	}
	PyTuple_SET_ITEM(args, 0, dir);
	timedebug timer(dlog, "Observer");
	for (observers_type::const_iterator i =
		     m_observers.begin();
	     i != m_observers.end();
	     ++i) {
		dlog << DEBUGLEVEL_DEBUG
			<< "DEBUG Calling observer "
			<< *i
			<< "."
			<< std::endl;
		PyObject* ret =
			PyObject_Call(*i, args, NULL);
		Py_XDECREF(ret);
		if (PyErr_Occurred()) {
			dlog << DEBUGLEVEL_WARNING <<
				"WARNING * OBSERVER RAISED AN EXCEPTION *" << std::endl;
			PyErr_Print();
			dlog << DEBUGLEVEL_WARNING << "***" << std::endl;
		}
		timer.mark("Previous observer");
	}
	Py_DECREF(args);
}

int Manager::getAmountOfTransactions() const throw () {
	return m_transactions.size();
}
		
int Manager::getCurrentTransactionPosition() const throw () {
	const List_Base* a = m_currentCmd;
	int size = -1;
	while (a != &m_transactions) {
		++size;
		a = a->getPrev();
	}
	return size;
}
Transaction* Manager::getCurrentTransaction()
	const throw () {
	if (m_currentCmd == &m_transactions) return NULL;
	return m_currentCmd;
}
Transaction* Manager::getTransaction(int pos)
	throw () {
	coral_assert(pos >= 0);
	coral_assert(pos < m_transactions.size());
	List_Base* l = &m_transactions;
	while (pos-- >= 0) {
		l = l->getNext();
	}
	Transaction* o
		= static_cast<Transaction*>(l);
	coral_assert(o != &m_transactions);
	return o;
}

void Manager::trimPast_w_history(int limit) throw () {
	coral_assert(m_active == false);
	while (m_n_transactions > limit) {
		DEBUG_MAY_FAIL(dlog << DEBUGLEVEL_DEBUG
			       << "DEBUG Trimming one past transaction..."
			       << std::endl);
		Transaction* o =
			static_cast<Transaction*>(*(m_transactions.begin()));
		if (o == m_currentCmd) {
			m_currentCmd = static_cast<Transaction*>(m_currentCmd->getNext());
		}
		o->drop();
		o->unref();
		--m_n_transactions;
	}
}

void Manager::clear() throw () {
	this->trimPast_w_history(0);
}

void Manager::trimFuture() throw () {
	/* Trims everything from our current position
	   _forward_ */
	coral_assert(m_inModifyCmd == 0);
	coral_assert(m_current_tr_in_progress == TRANSACTION_ORDINARY);
	while (canRedo()) {
		DEBUG_MAY_FAIL(dlog << DEBUGLEVEL_DEBUG
			       << "DEBUG Trimming one future transaction..."
			       << std::endl);
		/* Move back. */
		Transaction* o =
			static_cast<Transaction*>(m_currentCmd->getNext());
		coral_assert(o != &m_transactions);
		o->drop();
		o->unref();
		--m_n_transactions;
	}
}

void Manager::undo() throw () {
	coral_assert(this->canUndo());
	coral_assert(m_inModifyCmd == 0);

	Transaction* o = m_currentCmd;
	coral_assert(o != &m_transactions);
	dlog << DEBUGLEVEL_DEBUG
	     << "DEBUG New cur " << o
	     << " (base is " << &m_transactions << ")" << std::endl;
	for (Transaction::commands_type::reverse_iterator i
		     = o->m_commands.rbegin();
	     i != o->m_commands.rend();
	     ++i) {
		Command* c = *i;
		c->unexecute();
	}
			
	this->callObservers(TRANSACTION_UNDO);
	m_currentCmd = static_cast<Transaction*>(o->getPrev());

}

void Manager::redo() throw () {
	coral_assert(canRedo());
	coral_assert(m_inModifyCmd == 0);

	Transaction* o = static_cast<Transaction*>(m_currentCmd->getNext());

	for (Transaction::commands_type::iterator i
		     = o->m_commands.begin();
	     i != o->m_commands.end();
	     ++i) {
		Command* c = *i;
		c->execute();
	}

	/* Move one step forwards. */
	m_currentCmd = static_cast<Transaction*>(o);
	callObservers(TRANSACTION_REDO);

}


bool Manager::already_modified(const coral::Element* e,
			       const coral::Property* p) const {
        return m_watch.find(std::make_pair(e, p)) != m_watch.end();
}


ForgetPreviousCommand::~ForgetPreviousCommand() throw () {
	if (!m_destroy
	    || !theTM
	    || !theTM->internalIsActive()) return;
	theTM->__forget_previous_Cmd();
}




Command::~Command() throw () {
	;
}
		

/* default execute() */
void Command::execute() throw () {
	this->unexecute();
}

/*
 * Project change
 */
ProjectChangeCommand::ProjectChangeCommand(Element* a, Project* old)
	: ElementCommand(a) {
	m_old = old;
	if (m_old) m_old->ref();
}
ProjectChangeCommand::~ProjectChangeCommand() throw () {
	if (m_old) m_old->unref();
}

std::string ProjectChangeCommand::__str__() const {
	std::ostringstream s;
	s << "Project Change for "
	  << e->__str__()
	  << " from Project "
	  << m_old
	  << "." << std::endl;
	return s.str();
}

void ProjectChangeCommand::unexecute() throw () {
	Element* ee = static_cast<Element*>(e);
	Project* s = ee->__getProject__();

	temp_ref t(s);
	MUST_WORK(coral::Project::setProject(ee, m_old));
	if (m_old) m_old->unref();
	m_old = s;
	if (m_old) m_old->ref();
}

/*
 * New Project
 */
std::string NewProjectCommand::__str__() const {
	std::ostringstream s;
	s << "New Project "
	  << m_newstorage
	  << "." << std::endl;
	return s.str();
}

/*
 * Project name change
 */

ProjectNameChangeCommand::ProjectNameChangeCommand(
	Project* s,
	const std::string& old_filename,
	const std::string& old_href_name) :
	m_storage(s),
	m_old_filename(old_filename),
	m_old_href_name(old_href_name)
{
	coral_assert(s); s->ref();
}

std::string ProjectNameChangeCommand::__str__() const {
	std::ostringstream s;
	s << "Project name Change for "
	  << m_storage->__str__()
	  << ": old name \"" << m_old_filename << "\""
	  << ", old href \"" << m_old_href_name << "\""
	  << "." << std::endl;
	return s.str();
}

void ProjectNameChangeCommand::unexecute() throw () {
	const std::string& sf(m_storage->getFilename());
	const std::string& sh(m_storage->getHrefFilename());
	std::string& f(const_cast<std::string&>(sf));
	std::string& h(const_cast<std::string&>(sh));
	f.swap(m_old_filename);
	h.swap(m_old_href_name);
}



/*
 * Object Commands
 */

void ChangeElementPrimitiveCommand::unexecute() throw () {
	static_cast<ElementPrimitive*>(this->e)->swapWithAtom(this->new_atom);
}

void ElementChangeSlotCommand::unexecute() throw () {
	/* Swap slot contents, but not the actual slot pointers */
	Slot* s1 = previous.get();
	Element* ee = static_cast<Element*>(this->e);
	const char* name = s1->getProperty()->getName();
	Slot& s2 = ee->__getSlotByName__(name);
	s2.swapSlotContents(s1);
}

void ElementNewCommand::unexecute() throw () {
	/* Do nothing? */
	;
}

void ElementUUIDCommand::unexecute() throw () {
	/* Swap XMI ids */
	static_cast<Element*>(this->e)->swapXMIid(this->XMIid);
}

/*
 * Object __str__ methods
 */

std::string ChangeElementPrimitiveCommand::__str__() const {
	std::string res;
	res += "ChangeElementPrimitiveCommand: old: ";
	res += this->e->__str__().toUTF8();
	res += " new: ";
	res += this->new_atom->__str__().toUTF8();
	res += "\n";
	return res;
}

std::string ElementChangeSlotCommand::__str__() const {
	std::string res;
	res += "Object: ";
	res += this->e->__str__().toUTF8();
	res += "\n Slot:    ";
	res += this->previous->getProperty()->getName();
	res += "\n";
	res += "  Old value: \"";
	res += this->previous->__str__().toUTF8();
	res += "\"\n";
	return res;
}

std::string ElementNewCommand::__str__() const {
	std::string res;
	res += "New object: ";
	res += this->e->__str__().toUTF8() + "\n";
	return res;
}

std::string ElementUUIDCommand::__str__() const {
	std::string res;
	res += "XMI change: ";
	res += this->e->__str__().toUTF8();
	res += "\n Old value: ";
	res += this->XMIid;
	res += "\n";
	return res;
}

PauseTransaction::PauseTransaction(bool yes) {
	m_pause = false;
	setPause(yes);
}

PauseTransaction::~PauseTransaction() throw () {
	if (!m_pause) return;
	if (--pause_recursion) return;
	if (theTM && theTM->m_inModifyCmd != 0)
		theTM->m_active = true;
}

void PauseTransaction::setPause(bool yes) throw () {
	if (yes && theTM && theTM->m_active) {
		if (m_pause) return;
		m_pause = true;
		++pause_recursion;
	} else {
		if (!m_pause) return;
		m_pause = false;
		--pause_recursion;
	}
}

MakeTransaction::MakeTransaction() {
	if (coral::Transaction::theTM) {
		coral::Transaction::theTM->beginModifyCmd();
	}
}

MakeTransaction::~MakeTransaction() {
	if (coral::Transaction::theTM) {
		coral::Transaction::theTM->endModifyCmd();
	}
}


NAMESPACE_END();
NAMESPACE_END();
