/*
    PIP - Platform Independent Primitives
    Object, base class of some PIP classes, provide EVENT -> EVENT_HANDLER mechanism
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "piobject.h"

#include "piconditionvar.h"
#include "pithread.h"
#ifndef MICRO_PIP
#  include "pifile.h"
#  include "piiostream.h"
#  include "pisysteminfo.h"
#endif


//! \~\class PIObject piobject.h
//! \~\details
//! \~english \section PIObject_sec0 Events and Event handlers
//! \~russian \section PIObject_sec0 События и Обработчики событий
//!
//! \~english
//! %PIObject provide notification mechanism similar Qt but implemented
//! on language capabilities without any special preprocessors or compilers.
//! Any class inherits %PIObject should use macro \a PIOBJECT() immediate
//! after declaration to proper compile.
//!
//! Event is a some abstract event that can be raised at any time as common method.
//! Event is a function but declared with special macro \a EVENT() and don`t need definition.
//! To raise event simply execute event function.
//!
//! Event handler is a function but declared with special macro
//! \a EVENT_HANDLER(). It need definition as common method.
//! You can use event handlers as ordinary functions.
//!
//! Main goal of this mechanism is perform abstract connections between
//! various objects. This functionality provide macro \a CONNECT(), \a CONNECTU() and \a CONNECTL() which
//! connect some event of first object to some event handler or event of
//! second object. Each event can be connected any times to any event handlers.
//!
//! * \a CONNECT() macros family work with explicit subclasses of %PIObject with compile-time check of events and handlers
//! * \a CONNECTU() macro can work implicit subclasses of %PIObject with run-time check of events and handlers
//! * \a CONNECTU_QUEUED() macro similar to \a CONNECTU() macro but queue execution with performer object instead of handler direct call
//! * \a CONNECTL() macro work with implicit subclasses of %PIObject and lambda-function
//!
//! \~russian
//! %PIObject предоставляет механизм событий и их обработчиков, реализованный без
//! дополнительного препроцессора или метакомпилятора. Любой класс, наследованный
//! от %PIObject должен использовать макрос \a PIOBJECT() сразу после объявления
//! класса для корректной работы.
//!
//! Событием является сигнал, который может быть вызван как обычный метод в любое время.
//! Это метод, объявленный с помощью макроса \a EVENT() и не требует описания.
//! Для его вызова просто вызывается метод события.
//!
//! Обработчик события это метод, объявленный с помощью макроса \a EVENT_HANDLER()
//! и он требует описания, как и обычный метод. Можно его использовать как обычный метод.
//!
//! Основной функцией этого механизма является реализация соединений между различными объектами.
//! Её предоставляют макросы \a CONNECT(), \a CONNECTU() и \a CONNECTL(), которые соединяют
//! события одних объектов с обработчиками или событиями других объектов. Каждое событие
//! может быть присоеденино неограниченное количество раз к любым обработчикам.
//!
//! * \a CONNECT() семейство макросов работает с явными наследниками %PIObject, и проверяет соединение во время компиляции
//! * \a CONNECTU() макрос может работать с неявными наследниками %PIObject, и проверяет соединение во время исполнения
//! * \a CONNECTU_QUEUED() макрос подобен \a CONNECTU(), но планирует вызов обработчика у объекта performer вместо прямого вызова
//! * \a CONNECTL() макрос может работать с неявными наследниками %PIObject и лямбда-функцией
//!
//! \~\image html events_handlers.png
//!
//! \~english Example:
//! \~russian Пример:
//!
//! \~\snippet piobject.cpp main
//! \~english Result:
//! \~russian Результат:
//! \~\code{.cpp}
//! handler B: 2 , 0.5
//! handler A: event to handler
//! handler A: event to event
//! event to lambda
//! \endcode
//!


//! \~\class PIObject::Connection piobject.h
//! \~\details
//!


PIObject::__MetaFunc::__MetaFunc() {
	for (int i = 0; i < __PIOBJECT_MAX_ARGS__; ++i) {
		types[i] = names[i] = nullptr;
		types_id[i]         = 0;
	}
}


int PIObject::__MetaFunc::argumentsCount() const {
	for (int i = 0; i < __PIOBJECT_MAX_ARGS__; ++i)
		if (!types[i]) return i;
	return __PIOBJECT_MAX_ARGS__;
}


PIString PIObject::__MetaFunc::arguments() const {
	PIString ret;
	for (int i = 0; i < __PIOBJECT_MAX_ARGS__; ++i) {
		if (!types[i]) break;
		if (!ret.isEmpty()) ret += ',';
		ret += PIStringAscii(types[i]);
	}
	return ret;
}


PIString PIObject::__MetaFunc::fullFormat() const {
	PIString ret = PIStringAscii(type_ret) + " " + PIStringAscii(scope) + "::" + PIStringAscii(func_name) + "(";
	for (int i = 0; i < __PIOBJECT_MAX_ARGS__; ++i) {
		if (!types[i]) break;
		if (i > 0) ret += ", ";
		ret += PIStringAscii(types[i]) + " " + PIStringAscii(names[i]);
	}
	ret += ")";
	return ret;
}


void PIObject::__MetaFunc::__setFuncName(const char * n) {
	func_name    = n;
	func_name_id = PIStringAscii(n).hash();
}


void PIObject::__MetaFunc::__addArgument(const char * t, const char * n) {
	for (int i = 0; i < __PIOBJECT_MAX_ARGS__; ++i) {
		if (types[i]) continue;
		types[i]    = t;
		names[i]    = n;
		types_id[i] = PIObject::simplifyType(t, false).hash();
		break;
	}
	// PICout(PICoutManipulators::DefaultControls | PICoutManipulators::AddQuotes)
	//		<< "__addArgument" << t << n << PIObject::simplifyType(t) << types_id.back();
}


bool PIObject::__MetaFunc::canConnectTo(const __MetaFunc & dst, int & args_count) const {
	for (int i = 0; i < __PIOBJECT_MAX_ARGS__; ++i) {
		// piCout << "canConnectTo" << i << types[i] << dst.types[i];
		args_count = i;
		if (!dst.types[i]) break;
		if (!types[i]) return false;
		if (types_id[i] != dst.types_id[i]) return false;
	}
	return true;
}


PIObject::PIObject(const PIString & name): _signature_(__PIOBJECT_SIGNATURE__), emitter_(0), thread_safe_(false), proc_event_queue(false) {
	in_event_cnt = 0;
	setName(name);
	setDebug(true);
	mutexObjects().lock();
	objects() << this;
	mutexObjects().unlock();
	// piCout << "new" << this;
}


PIObject::~PIObject() {
	in_event_cnt = 0;
	// piCout << "delete" << this;
	mutexObjects().lock();
	objects().removeAll(this);
	mutexObjects().unlock();
	deleted(this);
	piDisconnectAll();
	_signature_ = 0;
}


bool PIObject::execute(const PIString & method, const PIVector<PIVariantSimple> & vl) {
	if (method.isEmpty()) return false;
	if (!isPIObject()) {
		piCout << "Error: \"execute(" << method << ")\":" << (void *)this << "is not PIObject!";
		return false;
	}
	int ac = 0;
	__MetaFunc func;
	bool ok = findSuitableMethodV(method, vl.size_s(), ac, func);
	if (!ok) return false;
	callAddrV(func.addrV, toThis(), ac, vl);
	return true;
}


bool PIObject::executeQueued(PIObject * performer, const PIString & method, const PIVector<PIVariantSimple> & vl) {
	if (!isPIObject()) {
		piCout << "Error: \"executeQueued(" << method << ")\": this(" << (void *)this << ") is not PIObject!";
		return false;
	}
	if (!performer->isPIObject()) {
		piCout << "Error: \"executeQueued(" << method << ")\": performer(" << (void *)performer << ") is not PIObject!";
		return false;
	}
	int ac = 0;
	__MetaFunc func;
	bool ok = findSuitableMethodV(method, vl.size_s(), ac, func);
	if (!ok) return false;
	performer->postQueuedEvent(__QueuedEvent(func.addrV, toThis(), this, performer, vl));
	performer->proc_event_queue = true;
	return true;
}


PIStringList PIObject::scopeList() const {
	PIStringList ret;
	ret.reserve(2);
	PIMutexLocker ml(__meta_mutex());
	const PIVector<const char *> & scope(__meta_data()[classNameID()].scope_list);
	for (const char * c: scope)
		ret << PIStringAscii(c);
	return ret;
}


PIStringList PIObject::methodsEH() const {
	PIMutexLocker ml(__meta_mutex());
	PIStringList ret;
	const __MetaData & ehd(__meta_data()[classNameID()]);
	for (auto eh = ehd.eh_func.begin(); eh != ehd.eh_func.end(); eh++)
		ret << eh.value().fullFormat();
	return ret;
}


bool PIObject::isMethodEHContains(const PIString & name) const {
	uint search_id = name.hash();
	PIMutexLocker ml(__meta_mutex());
	const __MetaData & ehd(__meta_data()[classNameID()]);
	for (auto eh = ehd.eh_func.begin(); eh != ehd.eh_func.end(); eh++) {
		if (eh.value().func_name_id == search_id) return true;
	}
	return false;
}


PIString PIObject::methodEHArguments(const PIString & name) const {
	uint search_id = name.hash();
	PIMutexLocker ml(__meta_mutex());
	const __MetaData & ehd(__meta_data()[classNameID()]);
	for (auto eh = ehd.eh_func.begin(); eh != ehd.eh_func.end(); eh++) {
		if (eh.value().func_name_id == search_id) return eh.value().arguments();
	}
	return PIString();
}


PIString PIObject::methodEHFullFormat(const PIString & name) const {
	uint search_id = name.hash();
	PIMutexLocker ml(__meta_mutex());
	const __MetaData & ehd(__meta_data()[classNameID()]);
	for (auto eh = ehd.eh_func.begin(); eh != ehd.eh_func.end(); eh++) {
		if (eh.value().func_name_id == search_id) return eh.value().fullFormat();
	}
	return PIString();
}


PIString PIObject::methodEHFromAddr(const void * addr) const {
	return methodEH(addr).func_name;
}


PIVector<PIObject::__MetaFunc> PIObject::findEH(const PIString & name) const {
	uint search_id = name.hash();
	PIVector<__MetaFunc> ret;
	const __MetaData & ehd(__meta_data()[classNameID()]);
	for (auto eh = ehd.eh_func.begin(); eh != ehd.eh_func.end(); eh++) {
		if (eh.value().func_name_id == search_id) ret << eh.value();
	}
	return ret;
}


PIObject::__MetaFunc PIObject::methodEH(const void * addr) const {
	PIMutexLocker ml(__meta_mutex());
	return __meta_data()[classNameID()].eh_func.value(addr);
}


PIObject::Connection PIObject::piConnect(PIObject * src,
                                         const PIString & sig,
                                         PIObject * dest_o,
                                         void * dest,
                                         void * ev_h,
                                         void * e_h,
                                         int args,
                                         const char * loc) {
	// piCout << "piConnect ...";
	// piCout << "piConnect" << src << (void*)(dest) << sig;
	// piCout << "piConnect" << src->className() << "->" << ((PIObject*)dest)->className();
	PIMutexLocker _ml(src->mutex_connect);
	PIMutexLocker _mld(dest_o->mutex_connect, src != dest_o);

	Connection conn(ev_h, e_h, sig, src, dest_o, dest, args);
	src->connections << conn;
	// piCout << "piConnect" << ((PIObject*)dest) << sig << ((PIObject*)dest)->connectors.size_s() << "...";
	// piCout << "addConnector" << dest_o << src;
	dest_o->connectors << src;
	// piCout << "piConnect" << ((PIObject*)dest) << sig << ((PIObject*)dest)->connectors.size_s();
	// piCout << "piConnect ok";
	return conn;
}


PIObject::Connection PIObject::piConnectU(PIObject * src,
                                          const PIString & sig,
                                          PIObject * dest_o,
                                          void * dest,
                                          const PIString & hname,
                                          const char * loc,
                                          PIObject * performer) {
	if (src == 0 || dest_o == 0 || dest == 0) return Connection();
	if (!src->isPIObject()) {
		piCout << "[piConnectU] \"" << sig << "\" -> \"" << hname << "\" error: source object is not PIObject! (" << loc << ")";
		return Connection();
	}
	if (!dest_o->isPIObject()) {
		piCout << "[piConnectU] \"" << sig << "\" -> \"" << hname << "\" error: destination object is not PIObject! (" << loc << ")";
		return Connection();
	}
	PIMutexLocker ml(__meta_mutex());
	PIMutexLocker mls(src->mutex_connect);
	PIMutexLocker mld(dest_o->mutex_connect, src != dest_o);
	PIVector<__MetaFunc> m_src = src->findEH(sig), m_dest = dest_o->findEH(hname);
	if (m_src.isEmpty()) {
		piCout << "[piConnectU] Error: can`t find event \"" << sig << "\" in class \"" << src->className() << "\"! (" << loc << ")";
		return Connection();
	}
	if (m_dest.isEmpty()) {
		piCout << "[piConnectU] Error: can`t find handler \"" << hname << "\" in class \"" << dest_o->className() << "\"! (" << loc << ")";
		return Connection();
	}
	void *addr_src(0), *addr_dest(0);
	int args(0);
	bool que = (performer != 0);
	for (const auto & fs: m_src) {
		if (addr_src != 0) break;
		for (const auto & fd: m_dest) {
			if (addr_src != 0) break;
			if (fs.canConnectTo(fd, args)) {
				addr_src  = fs.addr;
				addr_dest = que ? fd.addrV : fd.addr;
			}
		}
	}
	if (addr_src == 0) {
		piCout << "[piConnectU] Error: can`t find suitable pair of event \"" << sig << "\" in class \"" << src->className()
			   << "\" and handler \"" << hname << "\" in class \"" << dest_o->className() << "\"! (" << loc << ")";
		return Connection();
	}
	Connection conn(addr_dest, addr_src, sig, src, dest_o, dest, args, performer);
	src->connections << conn;
	if (que) performer->proc_event_queue = true;
	dest_o->connectors << src;
	// piCout << cc << cq << _ol.size();//"connect" << src << "->" << dest_o << ", dest.connectors.size() =" << dest_o->connectors.size();
	return conn;
}


PIObject::Connection PIObject::piConnectLS(PIObject * src, const PIString & sig, std::function<void()> * f, const char * loc) {
	if (src == 0) {
		delete f;
		return Connection();
	}
	if (!src->isPIObject()) {
		piCout << "[piConnectLS] \"" << sig << "\" -> [lambda] error: source object is not PIObject! (" << loc << ")";
		delete f;
		return Connection();
	}
	PIMutexLocker ml(__meta_mutex());
	PIMutexLocker mls(src->mutex_connect);
	// piCout << "locked";
	PIVector<__MetaFunc> m_src = src->findEH(sig);
	if (m_src.isEmpty()) {
		piCout << "[piConnectLS] Error: can`t find event \"" << sig << "\" in class \"" << src->className() << "\"! (" << loc << ")";
		delete f;
		return Connection();
	}
	if (m_src.size() != 1) {
		PIString ffm = m_src[0].fullFormat();
		for (int i = 1; i < m_src.size_s(); ++i) {
			if (ffm != m_src[i].fullFormat()) {
				piCout << "[piConnectLS] Error: can`t connect overloaded event \"" << sig << "\" in class \"" << src->className()
					   << "\", found " << m_src.size() << " events! (" << loc << ")";
				delete f;
				return Connection();
			}
		}
	}
	PIObject::Connection conn(0, m_src[0].addr, sig, src);
	// piCout << "found";
	conn.functor = f;
	src->connections << conn;
	// piCout << "finished";
	return conn;
}


void PIObject::piDisconnect(PIObject * src, const PIString & sig, PIObject * dest, void * ev_h) {
	PIMutexLocker _ml(src->mutex_connect);
	PIMutexLocker _mld(dest->mutex_connect, src != dest);
	for (int i = 0; i < src->connections.size_s(); ++i) {
		Connection & cc(src->connections[i]);
		if (cc.event == sig && cc.dest_o == dest && cc.slot == ev_h) {
			src->connections[i].destroy();
			src->connections.remove(i);
			i--;
		}
	}
	dest->updateConnectors();
}


void PIObject::piDisconnect(PIObject * src, const PIString & sig, PIObject * dest) {
	PIMutexLocker _ml(src->mutex_connect);
	PIMutexLocker _mld(dest->mutex_connect, src != dest);
	for (int i = 0; i < src->connections.size_s(); ++i) {
		Connection & cc(src->connections[i]);
		if (cc.event == sig && cc.dest_o == dest) {
			src->connections[i].destroy();
			src->connections.remove(i);
			i--;
		}
	}
	dest->updateConnectors();
}


void PIObject::piDisconnect(PIObject * src, const PIString & sig) {
	PIMutexLocker _ml(src->mutex_connect);
	for (int i = 0; i < src->connections.size_s(); ++i) {
		Connection & cc(src->connections[i]);
		if (cc.event == sig) {
			PIObject * dest = cc.dest_o;
			src->connections[i].destroy();
			src->connections.remove(i);
			i--;
			if (dest) {
#if !defined(ANDROID) && !defined(MAC_OS) && !defined(MICRO_PIP)
				PIMutexLocker _mld(dest->mutex_connect, src != dest);
#endif
				dest->updateConnectors();
			}
		}
	}
}


void PIObject::piDisconnectAll() {
	PIMutexLocker _ml(mutex_connect);
	PIVector<PIObject *> cv = connectors.toVector();
	//	piCout << "disconnect connectors =" << connectors.size();
	for (auto * o: cv) {
		//		piCout << "disconnect"<< src << o;
		if (!o || (o == this)) continue;
		if (!o->isPIObject()) continue;
#if !defined(ANDROID) && !defined(MAC_OS) && !defined(MICRO_PIP)
		PIMutexLocker _mld(o->mutex_connect, this != o);
#endif
		PIVector<Connection> & oc(o->connections);
		for (int i = 0; i < oc.size_s(); ++i) {
			if (oc[i].functor) continue;
			// piCout << "    check" << (void*)(oc[i].dest_o) << "==" << (void*)(src);
			if (oc[i].dest_o == this) {
				oc[i].destroy();
				oc.remove(i);
				--i;
			}
		}
	}
	//	piCout << "disconnect connections =" << connections.size();
	for (const auto & c: connections) {
		if (c.functor) continue;
		if (!c.dest_o) continue;
		if (!c.dest_o->isPIObject()) continue;
		c.dest_o->connectors.remove(this);
	}
	for (int i = 0; i < connections.size_s(); ++i)
		connections[i].destroy();
	connections.clear();
}


void PIObject::updateConnectors() {
	// piCout << "*** updateConnectors" << this;
	connectors.clear();
	PIMutexLocker _ml(mutexObjects());
	for (auto * o: objects()) {
		if (o == this) continue;
		PIVector<Connection> & oc(o->connections);
		for (auto & c: oc)
			if (c.dest == this) connectors << o;
	}
}


void PIObject::postQueuedEvent(const PIObject::__QueuedEvent & e) {
	mutex_queue.lock();
	events_queue << e;
	mutex_queue.unlock();
}


void * PIObject::toThis() const {
	// piCout << ptrOffset() << (void*)this << (void*)((char*)this - ptrOffset());
	return (void *)((char *)this - ptrOffset());
}


PIMutex & PIObject::__meta_mutex() {
	static PIMutex ret;
	return ret;
}


PIMap<uint, PIObject::__MetaData> & PIObject::__meta_data() {
	static PIMap<uint, PIObject::__MetaData> ret;
	return ret;
}


void PIObject::callQueuedEvents() {
	mutex_queue.lock();
	PIVector<__QueuedEvent> qe = events_queue;
	events_queue.clear();
	mutex_queue.unlock();
	for (const auto & e: qe) {
		if (e.dest_o->thread_safe_) e.dest_o->mutex_.lock();
		e.dest_o->emitter_ = e.src;
		callAddrV(e.slot, e.dest, e.values.size_s(), e.values);
		e.dest_o->emitter_ = 0;
		if (e.dest_o->thread_safe_) e.dest_o->mutex_.unlock();
	}
}


//! \details
//! \~english
//! On first call background thread started to delete objects.
//! Each object deletes when it`s outside from any events and hadlers.
//! \~russian
//! При первом вызове стартует фоновый поток для удаления объектов.
//! Каждый объект из очереди удаляется только когда выйдет из всех
//! событий и обработок.
void PIObject::deleteLater() {
	Deleter::instance()->post(this);
}


bool PIObject::findSuitableMethodV(const PIString & method, int args, int & ret_args, PIObject::__MetaFunc & ret) {
	PIVector<__MetaFunc> ml = findEH(method);
	if (ml.isEmpty()) {
		piCoutObj << "Error: no such method \"" << method << "\"!";
		return false;
	}
	int mfi = -1, ac = -1, mac = -1;
	for (int i = 0; i < ml.size_s(); ++i) {
		__MetaFunc & m(ml[i]);
		int j = m.argumentsCount();
		if (mac < 0 || mac > j) mac = j;
		if ((j <= args) && (ac < j)) {
			ac  = j;
			mfi = i;
		}
	}
	if (mfi < 0) {
		piCoutObj << "Error: no such suitable method \"" << method << "\", need at least" << mac << "arguments!";
		return false;
	}
	ret_args = ac;
	ret      = ml[mfi];
	return true;
}


PIVector<PIObject *> & PIObject::objects() {
	static PIVector<PIObject *> * ret = new PIVector<PIObject *>();
	return *ret;
}


PIMutex & PIObject::mutexObjects() {
	static PIMutex ret;
	return ret;
}


void PIObject::callAddrV(void * slot, void * obj, int args, const PIVector<PIVariantSimple> & vl) {
	args = piMini(args, vl.size_s());
	switch (args) {
	case 0: ((void (*)(void *))slot)(obj); break;
	case 1: ((void (*)(void *, const PIVariantSimple &))slot)(obj, vl[0]); break;
	case 2: ((void (*)(void *, const PIVariantSimple &, const PIVariantSimple &))slot)(obj, vl[0], vl[1]); break;
	case 3:
		((void (*)(void *, const PIVariantSimple &, const PIVariantSimple &, const PIVariantSimple &))slot)(obj, vl[0], vl[1], vl[2]);
		break;
	case 4:
		((void (*)(void *, const PIVariantSimple &, const PIVariantSimple &, const PIVariantSimple &, const PIVariantSimple &))
		     slot)(obj, vl[0], vl[1], vl[2], vl[3]);
		break;
	default: break;
	}
}


PIString PIObject::simplifyType(const char * a, bool readable) {
	PIString ret = PIStringAscii(a).trim();
	if (readable) {
		int white = -1;
		for (int i = 0; i < ret.size_s(); ++i) {
			bool iw = ret[i] == ' ' || ret[i] == '\t' || ret[i] == '\r' || ret[i] == '\n';
			// piCout << i << iw << white;
			if (white < 0) {
				if (iw) {
					white = i;
					continue;
				}
			} else {
				if (!iw) {
					ret.replace(white, i - white, " ");
					i     = white;
					white = -1;
					// piCout << i;
				}
			}
		}
		ret.replaceAll(" [", '[');
		ret.replaceAll(" ]", ']');
		ret.replaceAll(" <", '<');
		ret.replaceAll(" >", '>');
		ret.replaceAll(" &", '&');
		ret.replaceAll(" *", '*');
		ret.replaceAll("[ ", '[');
		ret.replaceAll("] ", ']');
		ret.replaceAll("< ", '<');
		ret.replaceAll("> ", '>');
		ret.replaceAll("& ", '&');
		ret.replaceAll("* ", '*');
		if (ret.startsWith("const ") && ret.endsWith("&")) ret.cutLeft(6).cutRight(1).trim();
	} else {
		if (ret.startsWith("const ") && ret.endsWith("&")) ret.cutLeft(6).cutRight(1).trim();
		ret.removeAll(' ').removeAll('\t').removeAll('\r').removeAll('\n');
	}
	return ret;
}


bool PIObject::isPIObject(const PIObject * o) {
	if (!o) return false;
	return o->_signature_ == __PIOBJECT_SIGNATURE__;
}


void PIObject::dump(const PIString & line_prefix) const {
	// printf("dump %s \"%s\"\n", className(), name().data());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "class " << className() << " (" << (const void *)this << ", \"" << name()
										   << "\") {";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    scope: " << scopeList().join(" -> ");
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    properties {";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "        count: " << properties_.size_s();
	// printf("dump %d properties\n", properties_.size());
	const char * o_name = "name";
	auto it             = properties_.makeIterator();
	while (it.next()) {
		if (it.key() != piHashData((const uchar *)o_name, strlen(o_name)))
			PICout(PICoutManipulators::AddNewLine) << line_prefix << "        " << it.key() << ": " << it.value();
	}
	// printf("dump %d properties ok\n", properties_.size());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    }";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    methods {";
	const __MetaData & ehd(__meta_data()[classNameID()]);
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "        count: " << ehd.eh_func.size_s();
	// printf("dump %d methods\n", ehd.eh_func.size());
	for (auto eh = ehd.eh_func.begin(); eh != ehd.eh_func.end(); eh++) {
		PICout(PICoutManipulators::AddNewLine) << line_prefix << "        " << eh.value().fullFormat();
	}
	// printf("dump %d methods ok\n", ehd.eh_func.size());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    }";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    connections {";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "        count: " << connections.size_s();
	// printf("dump %d connections\n",connections.size());
	for (const Connection & c: connections) {
		PIObject * dst = c.dest_o;
		__MetaFunc ef  = methodEH(c.signal);
		PIString src(c.event);
		if (ef.func_name) src = PIStringAscii(ef.func_name) + "(" + ef.arguments() + ")";
		if (dst) {
			__MetaFunc hf = dst->methodEH(c.slot);
			PIString hf_fn;
			if (!hf.func_name)
				hf_fn = "[BROKEN]";
			else
				hf_fn = PIStringAscii(hf.func_name) + "(" + hf.arguments() + ")";
			PICout(PICoutManipulators::AddNewLine) << line_prefix << "        " << src << " -> " << dst->className() << " (" << c.dest
												   << ", \"" << dst->name() << "\")::" << hf_fn;
		} else {
			PICout(PICoutManipulators::AddNewLine) << line_prefix << "        " << src << " -> "
												   << "[lambda]";
		}
	}
	// printf("dump %d connections ok\n",connections.size());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    }";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "}";
}


#ifndef MICRO_PIP
void dumpApplication(bool with_objects) {
	PIMutexLocker _ml(PIObject::mutexObjects());
	// printf("dump application ...\n");
	PIDateTime cd     = PIDateTime::current();
	PISystemInfo * pi = PISystemInfo::instance();
	PICout(PICoutManipulators::AddNewLine) << "application {";
	PICout(PICoutManipulators::AddNewLine) << "    PIP version: " << PIPVersion();
	PICout(PICoutManipulators::AddNewLine) << "    OS name: \"" << pi->OS_name << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    OS version: \"" << pi->OS_version << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    processors: " << pi->processorsCount;
	PICout(PICoutManipulators::AddNewLine) << "    architecture: \"" << pi->architecture << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    hostname: \"" << pi->hostname << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    username: \"" << pi->user << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    exec command: \"" << pi->execCommand << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    started: " << pi->execDateTime.toString();
	PICout(PICoutManipulators::AddNewLine) << "    uptime: "
										   << PITime::fromSystemTime(cd.toSystemTime() - pi->execDateTime.toSystemTime()).toString();
	PICout(PICoutManipulators::AddNewLine) << "    PIObjects {";
	PICout(PICoutManipulators::AddNewLine) << "        count: " << PIObject::objects().size_s();
	if (with_objects) {
		for (const PIObject * o: PIObject::objects())
			o->dump("        ");
	}
	PICout(PICoutManipulators::AddNewLine) << "    }";
	PICout(PICoutManipulators::AddNewLine) << "}";
	// printf("dump application done\n");
}


bool dumpApplicationToFile(const PIString & path, bool with_objects) {
	PIFile f(path + "_tmp");
	f.setName("_S.DumpFile");
	f.clear();
	if (!f.open(PIIODevice::WriteOnly)) return false;
	auto out_devs = PICout::currentOutputDevices();
	PICout::setOutputDevices(PICout::Buffer);
	PICout::clearBuffer();
	dumpApplication(with_objects);
	PIIOTextStream ts(&f);
	ts << PICout::getBuffer();
	f.close();
	PICout::setOutputDevices(out_devs);
	PIFile::rename(path + "_tmp", path);
	return true;
}
#endif


void PIObject::__MetaData::addScope(const char * s, uint shash) {
	if (!scope_id.contains(shash)) {
		scope_list << s;
		scope_id << shash;
	}
}


void PIObject::Connection::destroy() {
	if (functor) delete functor;
	functor = nullptr;
}


PIObject::Connection::Connection() {
	slot = signal = dest = nullptr;
	src_o = dest_o = performer = nullptr;
	functor                    = nullptr;
	eventID                    = 0;
	args_count                 = 0;
}


bool PIObject::Connection::disconnect() const {
	if (!isValid() || !src_o) return false;
	if (!src_o->isPIObject()) return false;
	bool ndm = dest_o && (src_o != dest_o), ret = false, found = false;
	if (dest_o) {
		if (!dest_o->isPIObject()) ndm = false;
	}
	PIMutexLocker _ml(src_o->mutex_connect);
	if (ndm) dest_o->mutex_connect.lock();
	for (int i = 0; i < src_o->connections.size_s(); ++i) {
		Connection & cc(src_o->connections[i]);
		if (cc.eventID == eventID) {
			if (dest_o && (cc.dest_o == dest_o)) {
				if (cc.slot == slot) found = true;
			}
			if (functor && (cc.functor == functor)) found = true;
		}
		if (found) {
			src_o->connections[i].destroy();
			src_o->connections.remove(i);
			ret = true;
			break;
		}
	}
	if (dest_o) {
		if (dest_o->isPIObject()) dest_o->updateConnectors();
	}
	if (ndm) dest_o->mutex_connect.unlock();
	return ret;
}


PRIVATE_DEFINITION_START(PIObject::Deleter)
	PIThread thread;
	PIConditionVariable cond_var;
	PIVector<PIObject *> obj_queue;
PRIVATE_DEFINITION_END(PIObject::Deleter)


PIObject::Deleter::Deleter() {
	// piCout << "Deleter start ...";
	PRIVATE->thread.setSlot([this]() {
		PIVector<PIObject *> oq;
		PRIVATE->thread.lock();
		if (PRIVATE->obj_queue.isEmpty()) PRIVATE->cond_var.wait(PRIVATE->thread.mutex());
		oq.swap(PRIVATE->obj_queue);
		PRIVATE->thread.unlock();
		for (PIObject * o: oq)
			deleteObject(o);
	});
	PRIVATE->thread.start();
}


PIObject::Deleter::~Deleter() {
	// piCout << "~Deleter ...";
	PRIVATE->thread.stop();
	PRIVATE->cond_var.notifyAll();
	PRIVATE->thread.waitForFinish();
	for (PIObject * o: PRIVATE->obj_queue)
		deleteObject(o);
	// piCout << "~Deleter ok";
}


PIObject::Deleter * PIObject::Deleter::instance() {
	static Deleter ret;
	return &ret;
}


void PIObject::Deleter::post(PIObject * o) {
	if (!o->isPIObject()) return;
	// piCout << "[Deleter] post" << o << "...";
	PRIVATE->thread.lock();
	if (!PRIVATE->obj_queue.contains(o)) {
		PRIVATE->obj_queue << o;
		PRIVATE->cond_var.notifyAll();
	}
	PRIVATE->thread.unlock();
	// piCout << "[Deleter] post" << o << "done";
}


void PIObject::Deleter::deleteObject(PIObject * o) {
	// piCout << "[Deleter] delete" << (uintptr_t)o << "...";
	if (o->isPIObject()) {
		// piCout << "[Deleter] delete" << (uintptr_t)o << "wait atomic ...";
		while (o->isInEvent())
			piMinSleep();
		// piCout << "[Deleter] delete" << (uintptr_t)o << "wait atomic done";
		delete o;
	}
	// piCout << "[Deleter] delete" << (uintptr_t)o << "done";
}
